optimization
[toast/confclerk.git] / src / gui / mainwindow.cpp
1 /*
2  * Copyright (C) 2010 Ixonos Plc.
3  *
4  * This file is part of fosdem-schedule.
5  *
6  * fosdem-schedule is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the Free
8  * Software Foundation, either version 2 of the License, or (at your option)
9  * any later version.
10  *
11  * fosdem-schedule is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
14  * more details.
15  *
16  * You should have received a copy of the GNU General Public License along with
17  * fosdem-schedule.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 #include "mainwindow.h"
20
21 #include <QTreeView>
22 #include <QFile>
23 #include <QNetworkProxy>
24 #include <QNetworkAccessManager>
25 #include <QNetworkReply>
26
27 #include <sqlengine.h>
28
29 #include <track.h>
30 #include <eventmodel.h>
31 #include <delegate.h>
32
33 #include <conference.h>
34
35 #include <QDialog>
36 #include <QMessageBox>
37
38 #include "ui_about.h"
39 #include <eventdialog.h>
40 #include "daynavigatorwidget.h"
41 #include "mapwindow.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     if(AppSettings::isDirectConnection())
73     {
74         qDebug() << "Setting-up proxy: " << AppSettings::proxyAddress() << ":" << AppSettings::proxyPort();
75     }
76     QNetworkProxy proxy(
77             AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
78             AppSettings::proxyAddress(),
79             AppSettings::proxyPort(),
80             PROXY_USERNAME,
81             PROXY_PASSWD);
82     QNetworkProxy::setApplicationProxy(proxy);
83
84     #if 0
85     // list of conferences must be maintained by ConferenceEditor
86     // here must be one of the signals from the closing ConferenceEditor (or model):
87     // selectedConf(conference), noConf()
88     connect(importScheduleWidget, SIGNAL(scheduleImported(int)), SLOT(scheduleImported(int)));
89     connect(importScheduleWidget, SIGNAL(scheduleDeleted(const QString&)), SLOT(scheduleDeleted(const QString&)));
90     #endif
91
92     // event details have changed
93     connect(dayTabContainer, SIGNAL(eventHasChanged(int,bool)), SLOT(eventHasChanged(int,bool)));
94     connect(favsTabContainer, SIGNAL(eventHasChanged(int,bool)), SLOT(eventHasChanged(int,bool)));
95     connect(tracksTabContainer, SIGNAL(eventHasChanged(int,bool)), SLOT(eventHasChanged(int,bool)));
96     connect(roomsTabContainer, SIGNAL(eventHasChanged(int,bool)), SLOT(eventHasChanged(int,bool)));
97     connect(nowTabContainer, SIGNAL(eventHasChanged(int,bool)), SLOT(eventHasChanged(int,bool)));
98     connect(searchTabContainer, SIGNAL(eventHasChanged(int,bool)), SLOT(eventHasChanged(int,bool)));
99
100     // event conference map button clicked
101     #if 0
102     // TODO: think about it when return to maps
103     connect(showMapButton, SIGNAL(clicked()), SLOT(conferenceMapClicked()));
104     #endif
105
106     connect(aboutAction, SIGNAL(triggered()), SLOT(aboutApp()));
107     connect(settingsAction, SIGNAL(triggered()), SLOT(setup()));
108     connect(conferencesAction, SIGNAL(triggered()), SLOT(showConferences()));
109
110     useConference(Conference::activeConference());
111     // optimization, see useConference() code
112     try {
113         initTabs();
114     } catch (OrmException) {
115         clearTabs();
116     }
117
118     #if 0
119     // TODO: remove GUI
120     // initialisation of model and pick active conference from there and call conferenceChanged()
121     // selectConference->setDuplicatesEnabled(false);
122     int confCount = Conference::getAll().count();
123     if(confCount)
124     {
125         initTabs();
126         // fillAndShowConferenceHeader();
127         setWindowTitle(Conference::getById(confId).title());
128
129         QList<Conference> confs = Conference::getAll();
130         QListIterator<Conference> i(confs);
131         while(i.hasNext())
132         {
133             Conference conf = i.next();
134             // TODO: remove GUI
135             // selectConference->addItem(conf.title(),conf.id());
136         }
137         // TODO: remove GUI
138         // int idx = selectConference->findText(Conference::getById(Conference::activeConference()).title());
139         // selectConference->setCurrentIndex(idx);
140         // connect(selectConference, SIGNAL(currentIndexChanged(int)), SLOT(conferenceChanged(int)));
141         // conferenceChanged(idx);
142     }
143     else
144     {
145         // TODO: remove GUI
146         // conferenceHeader->hide();
147         // selectConferenceWidget->hide();
148         // // go to the 'conferenceTab', so the user can import the schedule
149         // tabWidget->setCurrentIndex(6); // 6 - conference tab
150     }
151     #endif
152
153     // open dialog for given Event ID
154     // this is used in case Alarm Dialog request application to start
155     if(aEventId)
156     {
157         try
158         {
159             EventDialog dialog(aEventId,this);
160             dialog.exec();
161         }
162         catch(OrmNoObjectException&) {} // just start application
163         catch(...) {} // just start application
164     }
165
166     connect(mNetworkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(networkQueryFinished(QNetworkReply*)));
167
168     connect(mXmlParser, SIGNAL(parsingScheduleBegin()), conferenceModel, SLOT(newConferenceBegin()));
169     connect(mXmlParser, SIGNAL(parsingScheduleEnd(const QString&)), conferenceModel, SLOT(newConferenceEnd(const QString&)));
170 }
171
172 void MainWindow::scheduleImported(int aConfId)
173 {
174     Q_UNUSED(aConfId);
175
176     // TODO: this all goes to ConferenceEditor and model of conferences
177     #if 0
178
179     Conference conf = Conference::getById(aConfId);
180     if( selectConference->findText(conf.title()) < 0 ) // item doesn't exist
181     {
182         disconnect(selectConference, SIGNAL(currentIndexChanged(int)), this, SLOT(conferenceChanged(int)));
183         selectConference->addItem(conf.title(),conf.id());
184         connect(selectConference, SIGNAL(currentIndexChanged(int)), SLOT(conferenceChanged(int)));
185     }
186     int confCount = Conference::getAll().count();
187     if(confCount)
188     {
189         int idx = selectConference->findText(conf.title());
190         selectConference->setCurrentIndex(idx);
191
192         selectConferenceWidget->show();
193
194         conferenceChanged(idx);
195     }
196     #endif
197 }
198
199 void MainWindow::scheduleDeleted(const QString& title)
200 {
201     Q_UNUSED(title);
202     // TODO: this all goes to ConferenceEditor and model of conferences
203     #if 0
204     int idx = selectConference->findText(title);
205
206     if (idx == -1) {
207         // should not happen
208         qWarning() << __PRETTY_FUNCTION__ << "removed non-existent item:" << title;
209         // this happens when you remove the only conference (the list is not ptoperly inited in this case)
210         // now make sure that conferencet
211         if (selectConference->count() > 0) {
212             selectConference->setCurrentIndex(0);
213             conferenceChanged(0);
214         } else {
215             conferenceChanged(-1);
216         }
217     } else {
218         // will it signal "changed"?
219         selectConference->removeItem(idx);
220     }
221     #endif
222 }
223
224 void MainWindow::aboutApp()
225 {
226     QDialog dialog(this);
227     Ui::AboutDialog ui;
228     ui.setupUi(&dialog);
229 #ifdef N810
230     dialog.setFixedWidth(width());
231 #endif
232     dialog.exec();
233 }
234
235 void MainWindow::conferenceMapClicked()
236 {
237     QString mapPath = QString(":/maps/campus.png");
238     if(!QFile::exists(mapPath))
239         mapPath = QString(":/maps/rooms/not-available.png");
240
241     QString roomName;
242
243     QPixmap map(mapPath);
244     MapWindow window(map,roomName,this);
245     window.exec();
246 }
247
248 void MainWindow::eventHasChanged(int aEventId, bool aReloadModel)
249 {
250     dayTabContainer->updateTreeViewModel(aEventId);
251     favsTabContainer->updateTreeViewModel(aEventId,aReloadModel);
252     tracksTabContainer->updateTreeViewModel(aEventId);
253     nowTabContainer->updateTreeViewModel(aEventId);
254     roomsTabContainer->updateTreeViewModel(aEventId);
255     searchTabContainer->updateTreeViewModel(aEventId);
256 }
257
258 void MainWindow::useConference(int id)
259 {
260     try {
261         Conference::getById(Conference::activeConference()).update("active",0);
262         Conference new_active = Conference::getById(id);
263         new_active.update("active",1);
264
265         // looks like it does not work at n900
266         setWindowTitle(new_active.title());
267
268         // optimization.
269         // dont run initTabs() here
270         // it takes much CPU, making travelling between conferences in ConferenceEditor longer
271         // and is not seen in maemo WM anyway
272         // instead run it explicitly where needed
273         // dont forget to protect the calls by try-catch!
274
275         // just in case, clear conference selection instead
276         clearTabs();
277
278         // end of optimization
279         // initTabs();
280     } catch (OrmException& e) {
281         // cannon set an active conference
282         unsetConference();
283         return;
284     }
285
286 }
287
288 void MainWindow::initTabs()
289 {
290     int confId = Conference::activeConference();
291     Conference active = Conference::getById(confId);
292     QDate startDate = active.start();
293     QDate endDate = active.end();
294
295     // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
296     dayTabContainer->setDates(startDate, endDate);
297     tracksTabContainer->setDates(startDate, endDate);
298     roomsTabContainer->setDates(startDate, endDate);
299     favsTabContainer->setDates(startDate, endDate);
300     searchTabContainer->setDates(startDate, endDate);
301     searchTabContainer->searchAgainClicked();
302     nowTabContainer->updateTreeView(QDate::currentDate());
303 }
304
305 void MainWindow::clearTabs()
306 {
307     dayTabContainer->clearModel();
308     tracksTabContainer->clearModel();
309     roomsTabContainer->clearModel();
310     favsTabContainer->clearModel();
311     searchTabContainer->clearModel();
312     searchTabContainer->searchAgainClicked();
313     nowTabContainer->clearModel();
314 }
315
316 void MainWindow::unsetConference()
317 {
318     clearTabs();
319     setWindowTitle(saved_title);
320 }
321
322 void MainWindow::setup()
323 {
324     SettingsDialog dialog;
325     dialog.exec();
326
327     qDebug() << "Setting-up proxy: " << AppSettings::proxyAddress() << ":" << AppSettings::proxyPort();
328     QNetworkProxy proxy(
329             AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
330             AppSettings::proxyAddress(),
331             AppSettings::proxyPort(),
332             PROXY_USERNAME,
333             PROXY_PASSWD);
334     QNetworkProxy::setApplicationProxy(proxy);
335 }
336
337 /** Create and run ConferenceEditor dialog, making required connections for it.
338
339 This method manages, which classes actually perform changes in conference list.
340
341 There are several classes that modify the conferences:
342 this:
343  deletion and URL update.
344 this, mXmlParser and mNetworkAccessManager:
345  addition and refresh.
346 */
347 void MainWindow::showConferences()
348 {
349     ConferenceEditor dialog(conferenceModel, this);
350
351     // TODO: connect signals about progress of network and parsing
352
353     connect(&dialog, SIGNAL(haveConferenceUrl(const QString&)), SLOT(importFromNetwork(const QString&)));
354     connect(&dialog, SIGNAL(haveConferenceFile(const QString&)), SLOT(importFromFile(const QString&)));
355     connect(&dialog, SIGNAL(removeConferenceRequested(int)), SLOT(removeConference(int)));
356     connect(&dialog, SIGNAL(changeUrlRequested(int, const QString&)),
357                     SLOT(changeConferenceUrl(int, const QString&)));
358
359     connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
360     connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
361
362     connect(mXmlParser, SIGNAL(parsingScheduleBegin()), &dialog, SLOT(importStarted()));
363     connect(mXmlParser, SIGNAL(progressStatus(int)), &dialog, SLOT(showParsingProgress(int)));
364     connect(mXmlParser, SIGNAL(parsingScheduleEnd(const QString&)), &dialog, SLOT(importFinished(const QString&)));
365
366     connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
367
368     dialog.exec();
369
370     // optimization, see useConference() code
371     try {
372         initTabs();
373     } catch (OrmException) {
374         clearTabs();
375     }
376 }
377
378 void MainWindow::networkQueryFinished(QNetworkReply *aReply)
379 {
380     if ( aReply->error() != QNetworkReply::NoError )
381     {
382         error_message(QString("Error occured during download: ") + aReply->errorString());
383     }
384     else
385     {
386         qDebug() << __PRETTY_FUNCTION__ << ": have data";
387         importData(aReply->readAll(), aReply->url().toEncoded());
388     }
389 }
390
391 void MainWindow::importData(const QByteArray &aData, const QString& url)
392 {
393     // TODO: remove GUI
394     // instead send signals to the child dialog
395     #if 0
396     browse->hide();
397     online->hide();
398     progressBar->show();
399     // proxySettings->hide();
400     #endif
401
402     int confId = mXmlParser->parseData(aData, url);
403
404     #if 0
405     progressBar->hide();
406     browse->show();
407     online->show();
408     // proxySettings->show();
409     importScheduleLabel->setText("Schedule:");
410
411     #endif
412     if (confId > 0) {
413         emit(scheduleImported(confId));
414     }
415 }
416
417 void MainWindow::importFromNetwork(const QString& url)
418 {
419     qDebug() << __PRETTY_FUNCTION__;
420     QNetworkRequest request;
421     request.setUrl(QUrl(url));
422
423     mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
424     mNetworkAccessManager->get(request);
425 }
426
427 void MainWindow::importFromFile(const QString& filename)
428 {
429     qDebug() << __PRETTY_FUNCTION__;
430     QFile file(filename);
431     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {    
432         static const QString format("Cannot read \"%1\": error %2");
433         error_message(format.arg(filename, QString::number(file.error())));
434     }
435
436     importData(file.readAll(), "");
437 }
438
439 void MainWindow::removeConference(int id)
440 {
441     Conference::deleteConference(id);
442     conferenceModel->conferenceRemoved();
443
444     emit conferenceRemoved();
445 }
446
447 void MainWindow::changeConferenceUrl(int id, const QString& url)
448 {
449     Conference::getById(id).setUrl(url);
450 }
451