a54b7f1e3ef560dc93b33aad66e2afabd22f2975
[toast/confclerk.git] / src / gui / mainwindow.cpp
1 /*
2  * Copyright (C) 2010 Ixonos Plc.
3  * Copyright (C) 2011-2012 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
28 #include <sqlengine.h>
29
30 #include <track.h>
31 #include <eventmodel.h>
32 #include <delegate.h>
33
34 #include <conference.h>
35
36 #include <QDialog>
37 #include <QMessageBox>
38
39 #include "ui_about.h"
40 #include <eventdialog.h>
41 #include "daynavigatorwidget.h"
42 #include "settingsdialog.h"
43 #include "conferenceeditor.h"
44 #include "schedulexmlparser.h"
45 #include "errormessage.h"
46
47 #include <tabcontainer.h>
48 #include <appsettings.h>
49
50 const QString PROXY_USERNAME;
51 const QString PROXY_PASSWD;
52
53 MainWindow::MainWindow(int aEventId, QWidget *aParent)
54     : QMainWindow(aParent)
55     , conferenceModel(new ConferenceModel(this))
56     , mXmlParser(new ScheduleXmlParser(this))
57     , mNetworkAccessManager(new QNetworkAccessManager(this))
58 {
59     setupUi(this);
60
61     saved_title = windowTitle();
62
63 #ifdef N810
64     tabWidget->setTabText(1,"Favs");
65     //tabWidget->setTabText(2,"Day");
66 #endif
67
68     // first time run aplication: -> let's have it direct connection in this case
69     if(!AppSettings::contains("proxyIsDirectConnection"))
70         AppSettings::setDirectConnection(true);
71
72     /*
73     if(AppSettings::isDirectConnection())
74     {
75         qDebug() << "Setting-up proxy: " << AppSettings::proxyAddress() << ":" << AppSettings::proxyPort();
76     }
77     */
78     QNetworkProxy proxy(
79             AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
80             AppSettings::proxyAddress(),
81             AppSettings::proxyPort(),
82             PROXY_USERNAME,
83             PROXY_PASSWD);
84     QNetworkProxy::setApplicationProxy(proxy);
85
86     // event details have changed
87     connect(dayTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
88     connect(favsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
89     connect(tracksTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
90     connect(roomsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
91     connect(searchTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
92
93     // date has changed
94     connect(dayNavigator, SIGNAL(dateChanged(QDate)), dayTabContainer, SLOT(redisplayDate(QDate)));
95     connect(dayNavigator, SIGNAL(dateChanged(QDate)), favsTabContainer, SLOT(redisplayDate(QDate)));
96     connect(dayNavigator, SIGNAL(dateChanged(QDate)), tracksTabContainer, SLOT(redisplayDate(QDate)));
97     connect(dayNavigator, SIGNAL(dateChanged(QDate)), roomsTabContainer, SLOT(redisplayDate(QDate)));
98     connect(dayNavigator, SIGNAL(dateChanged(QDate)), searchTabContainer, SLOT(redisplayDate(QDate)));
99
100     // search result has changed
101     connect(searchTabContainer, SIGNAL(searchResultChanged()), SLOT(onSearchResultChanged()));
102
103
104     useConference(Conference::activeConference());
105     // optimization, see useConference() code
106     try {
107         initTabs();
108     } catch (const OrmException& e) {
109         qDebug() << "OrmException:" << e.text();
110         clearTabs();
111     }
112
113     // open dialog for given Event ID
114     // this is used in case Alarm Dialog request application to start
115     if(aEventId)
116     {
117         try
118         {
119             EventDialog dialog(aEventId,this);
120             dialog.exec();
121         }
122         catch(OrmNoObjectException&) {} // just start application
123         catch(...) {} // just start application
124     }
125
126     connect(mNetworkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(networkQueryFinished(QNetworkReply*)));
127
128     connect(mXmlParser, SIGNAL(parsingScheduleBegin()), conferenceModel, SLOT(newConferenceBegin()));
129     connect(mXmlParser, SIGNAL(parsingScheduleEnd(const QString&)), conferenceModel, SLOT(newConferenceEnd(const QString&)));
130 }
131
132 void MainWindow::on_aboutAction_triggered()
133 {
134     QDialog dialog(this);
135     Ui::AboutDialog ui;
136     ui.setupUi(&dialog);
137     ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion()));
138 #ifdef N810
139     dialog.setFixedWidth(width());
140 #endif
141     dialog.exec();
142 }
143
144
145 void MainWindow::on_reloadAction_triggered() {
146     int confId = Conference::activeConference();
147     if (confId== -1) return;
148     Conference active = Conference::getById(confId);
149     if (active.url().isEmpty()) return;
150     importFromNetwork(active.url(), confId);
151     setEnabled(false);
152 }
153
154
155 void MainWindow::on_nowAction_triggered() {
156     int confId = Conference::activeConference();
157     if (confId== -1) return;
158     dayNavigator->setCurDate(QDate::currentDate());
159     dayTabContainer->expandTimeGroup(QTime::currentTime(), confId);
160 }
161
162
163 void MainWindow::on_searchAction_triggered() {
164     if (tabWidget->currentWidget() == searchTab)
165         searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible());
166     else {
167         tabWidget->setCurrentWidget(searchTab);
168         searchTabContainer->showSearchDialog();
169     }
170 }
171
172
173 void MainWindow::on_expandAllAction_triggered() {
174     if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->expandAll();
175     if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->expandAll();
176     if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->expandAll();
177     if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->expandAll();
178     if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->expandAll();
179 }
180
181
182 void MainWindow::on_collapseAllAction_triggered() {
183     if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->collapseAll();
184     if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->collapseAll();
185     if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->collapseAll();
186     if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->collapseAll();
187     if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->collapseAll();
188 }
189
190
191 void MainWindow::onEventChanged(int aEventId, bool favouriteChanged) {
192     dayTabContainer->redisplayEvent(aEventId);
193     if (favouriteChanged) favsTabContainer->redisplayDate(dayNavigator->curDate());
194     else favsTabContainer->redisplayEvent(aEventId);
195     tracksTabContainer->redisplayEvent(aEventId);
196     roomsTabContainer->redisplayEvent(aEventId);
197     searchTabContainer->redisplayEvent(aEventId);
198 }
199
200
201 void MainWindow::onSearchResultChanged() {
202     // Are results found on the current date?
203     QDate date = dayNavigator->curDate();
204     int count = searchTabContainer->searchResultCount(date);
205     if (count > 0) {searchTabContainer->redisplayDate(date); return;}
206
207     // Are results found in the future?
208     for (date = date.addDays(1); date <= dayNavigator->endDate(); date = date.addDays(1)) {
209         int count = searchTabContainer->searchResultCount(date);
210         if (count > 0) {dayNavigator->setCurDate(date); return;}
211     }
212
213     // Are results found in the past?
214     for (date = dayNavigator->startDate(); date < dayNavigator->curDate(); date = date.addDays(1)) {
215         int count = searchTabContainer->searchResultCount(date);
216         if (count > 0) {dayNavigator->setCurDate(date); return;}
217     }
218     // No results were found
219     searchTabContainer->redisplayDate(dayNavigator->curDate());
220 }
221
222
223 void MainWindow::useConference(int id)
224 {
225     if (id == -1)  // in case no conference is active
226     {
227         unsetConference();
228         return;
229     }
230     try {
231         Conference::getById(Conference::activeConference()).update("active",0);
232         Conference new_active = Conference::getById(id);
233         new_active.update("active",1);
234
235         // looks like it does not work at n900
236         setWindowTitle(new_active.title());
237
238         // optimization.
239         // dont run initTabs() here
240         // it takes much CPU, making travelling between conferences in ConferenceEditor longer
241         // and is not seen in maemo WM anyway
242         // instead run it explicitly
243         // 1. at startup
244         // 2. when ConferenceEditor finished
245         // dont forget to protect the calls by try-catch!
246
247         // just in case, clear conference selection instead
248         clearTabs();
249
250         // end of optimization
251         // initTabs();
252     } catch (OrmException& e) {
253         // cannon set an active conference
254         unsetConference();   // TODO: as no active conference is now correctly managed this should be handled as a fatal error
255         return;
256     }
257
258 }
259
260 void MainWindow::initTabs()
261 {
262     int confId = Conference::activeConference();
263     if (confId != -1)   // only init tabs if a conference is active
264     {
265         Conference active = Conference::getById(confId);
266         QDate startDate = active.start();
267         QDate endDate = active.end();
268
269         // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
270         dayNavigator->setDates(startDate, endDate);
271     }
272 }
273
274 void MainWindow::clearTabs()
275 {
276     dayTabContainer->clearModel();
277     tracksTabContainer->clearModel();
278     roomsTabContainer->clearModel();
279     favsTabContainer->clearModel();
280     searchTabContainer->clearModel();
281 }
282
283 void MainWindow::unsetConference()
284 {
285     clearTabs();
286     dayNavigator->unsetDates();
287     setWindowTitle(saved_title);
288 }
289
290 void MainWindow::on_settingsAction_triggered()
291 {
292     SettingsDialog dialog;
293     dialog.loadDialogData();
294     if (dialog.exec() == QDialog::Accepted) {
295         dialog.saveDialogData();
296         QNetworkProxy proxy(
297                 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
298                 AppSettings::proxyAddress(),
299                 AppSettings::proxyPort(),
300                 PROXY_USERNAME,
301                 PROXY_PASSWD);
302         QNetworkProxy::setApplicationProxy(proxy);
303     }
304 }
305
306 /** Create and run ConferenceEditor dialog, making required connections for it.
307
308 This method manages, which classes actually perform changes in conference list.
309
310 There are several classes that modify the conferences:
311 this:
312  deletion and URL update.
313 this, mXmlParser and mNetworkAccessManager:
314  addition and refresh.
315 */
316 void MainWindow::on_conferencesAction_triggered()
317 {
318     ConferenceEditor dialog(conferenceModel, this);
319
320     connect(&dialog, SIGNAL(haveConferenceUrl(const QString&, int)), SLOT(importFromNetwork(const QString&, int)));
321     connect(&dialog, SIGNAL(haveConferenceFile(const QString&, int)), SLOT(importFromFile(const QString&, int)));
322     connect(&dialog, SIGNAL(removeConferenceRequested(int)), SLOT(removeConference(int)));
323     connect(&dialog, SIGNAL(changeUrlRequested(int, const QString&)),
324                     SLOT(changeConferenceUrl(int, const QString&)));
325
326     connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
327     connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
328
329     connect(mXmlParser, SIGNAL(parsingScheduleBegin()), &dialog, SLOT(importStarted()));
330     connect(mXmlParser, SIGNAL(progressStatus(int)), &dialog, SLOT(showParsingProgress(int)));
331     connect(mXmlParser, SIGNAL(parsingScheduleEnd(const QString&)), &dialog, SLOT(importFinished(const QString&)));
332
333     connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
334
335     dialog.exec();
336
337     // optimization, see useConference() code
338     try {
339         initTabs();
340     } catch (OrmException) {
341         clearTabs();
342     }
343 }
344
345 void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
346     if (aReply->error() != QNetworkReply::NoError) {
347         error_message(QString("Error occured during download: ") + aReply->errorString());
348     } else {
349         QUrl redirectUrl = aReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
350         if (!redirectUrl.isEmpty()) {
351             if (redirectUrl != aReply->request().url()) {
352                 importFromNetwork(redirectUrl.toString(), aReply->request().attribute(QNetworkRequest::User).toInt());
353                 return; // don't enable controls
354             } else {
355                 error_message(QString("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
356             }
357         } else {
358             importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt());
359         }
360     }
361     setEnabled(true);
362 }
363
364 void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId)
365 {
366     mXmlParser->parseData(aData, url, conferenceId);
367 }
368
369 void MainWindow::importFromNetwork(const QString& url, int conferenceId)
370 {
371     QNetworkRequest request;
372     request.setUrl(QUrl(url));
373     request.setAttribute(QNetworkRequest::User, conferenceId);
374
375     mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
376     mNetworkAccessManager->get(request);
377 }
378
379 void MainWindow::importFromFile(const QString& filename, int conferenceId)
380 {
381     QFile file(filename);
382     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {    
383         static const QString format("Cannot read \"%1\": error %2");
384         error_message(format.arg(filename, QString::number(file.error())));
385     }
386
387     importData(file.readAll(), "", conferenceId);
388 }
389
390 void MainWindow::removeConference(int id)
391 {
392     Conference::deleteConference(id);
393     conferenceModel->conferenceRemoved();
394
395     emit conferenceRemoved();
396 }
397
398 void MainWindow::changeConferenceUrl(int id, const QString& url)
399 {
400     Conference::getById(id).setUrl(url);
401 }
402