Initial commit (software version 0.2.0)
[movie-schedule] / src / control / maincontroller.cpp
1 // Copyright 2010 Jochen Becher
2 //
3 // This file is part of MovieSchedule.
4 //
5 // MovieSchedule is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // MovieSchedule is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with MovieSchedule.  If not, see <http://www.gnu.org/licenses/>.
17
18 #include "maincontroller.h"
19
20 #include "data/settings.h"
21 #include "data/cinemaschedule.h"
22 #include "data/movie.h"
23 #include "data/cinema.h"
24 #include "control/theatercontroller.h"
25 #include "control/moviecontroller.h"
26 #include "control/actioncontroller.h"
27 #include "control/locationcontroller.h"
28 #include "control/itemmodelsortclient.h"
29 #include "ui/aboutdialog.h"
30 #include "ui/mainwindow.h"
31 #include "ui/theatermodel.h"
32 #include "ui/moviemodel.h"
33 #include "ui/uiutils.h"
34 #include "searchclients/theatersearchclient.h"
35 #include "searchclients/moviesearchclient.h"
36 #include "utils/assertedlocker.h"
37 #include "utils/asynccall.h"
38 #include "utils/connectivitymanager.h"
39
40 #include <QSortFilterProxyModel>
41 #include <iostream>
42
43 static const char *MSG_NETWORK_ERROR = QT_TRANSLATE_NOOP("MainController", "Unable to access network.");
44 static const char *MSG_NO_THEATERS_FOUND = QT_TRANSLATE_NOOP("MainController", "No theaters found near %1.");
45 static const char *MSG_NO_MOVIES_FOUND = QT_TRANSLATE_NOOP("MainController", "No movies found near %1.");
46 static const char *MSG_THEATERS_ERROR = QT_TRANSLATE_NOOP("MainController", "Error on fetching theaters.");
47 static const char *MSG_MOVIES_ERROR = QT_TRANSLATE_NOOP("MainController", "Error on fetching movies.");
48
49 MainController::MainController(MainWindow *main_window, Settings *settings, CinemaSchedule *cinema_schedule,
50                                TheaterController *theater_controller,
51                                MovieController *movie_controller,
52                                LocationController *location_controller,
53                                ActionController *action_controller,
54                                ItemModelSortController *sort_controller,
55                                QThread *search_worker)
56                                    : _main_window(main_window),
57                                    _settings(settings),
58                                    _cinema_schedule(cinema_schedule),
59                                    _theater_controller(theater_controller),
60                                    _movie_controller(movie_controller),
61                                    _location_controller(location_controller),
62                                    _action_controller(action_controller),
63                                    _sort_controller(sort_controller),
64                                    _search_worker(search_worker),
65                                    _connectivity_manager(new ConnectivityManager(this)),
66                                    _current_theater_search_task_id(TheaterSearchClient::INVALID_SEARCH_TASK_ID),
67                                    _current_movies_search_task_id(MovieSearchClient::INVALID_SEARCH_TASK_ID),
68                                    _theater_model(new TheaterModel(cinema_schedule, this)),
69                                    _movie_model(new MovieModel(cinema_schedule, this)),
70                                    _theater_proxy_model(new QSortFilterProxyModel(this)),
71                                    _movie_proxy_model(new QSortFilterProxyModel(this)),
72                                    _last_search_settings()
73 {
74     connect(_main_window, SIGNAL(SearchTheaters()), this, SLOT(SearchTheaters()));
75     connect(_main_window, SIGNAL(SearchMovies()), this, SLOT(SearchMovies()));
76     connect(_main_window, SIGNAL(TheaterSelected(CinemaKey)), _theater_controller, SLOT(ShowTheater(CinemaKey)));
77     connect(_main_window, SIGNAL(CallTheaterByPhone(CinemaKey)), _action_controller, SLOT(CallTheaterByPhone(CinemaKey)));
78     connect(_main_window, SIGNAL(FindRouteToTheater(CinemaKey)), _action_controller, SLOT(FindRouteToTheater(CinemaKey)));
79     connect(_main_window, SIGNAL(SearchTheaterInWeb(CinemaKey)), _action_controller, SLOT(SearchTheaterInWeb(CinemaKey)));
80     connect(_main_window, SIGNAL(MovieSelected(MovieKey)), _movie_controller, SLOT(ShowMovie(MovieKey)));
81     connect(_main_window, SIGNAL(SearchMovieInWeb(MovieKey)), _action_controller, SLOT(SearchMovieInWeb(MovieKey)));
82     connect(_main_window, SIGNAL(OpenLocationDialog()), _location_controller, SLOT(OpenLocationDialog()));
83     connect(_main_window, SIGNAL(OpenAboutDialog()), this, SLOT(OpenAboutDialog()));
84     connect(_location_controller, SIGNAL(Search(Location)), this, SLOT(Search(Location)));
85     connect(_connectivity_manager, SIGNAL(Connected()), this, SLOT(NetworkConnected()));
86     connect(_connectivity_manager, SIGNAL(Disconnected()), this, SLOT(NetworkDisconnected()));
87     connect(_connectivity_manager, SIGNAL(Error()), this, SLOT(NetworkError()));
88 }
89
90 MainController::~MainController()
91 {
92 }
93
94 void MainController::Run()
95 {
96     _connectivity_manager->Connect();
97 }
98
99 void MainController::Search()
100 {
101     _location_controller->CancelAllGpsSearchs();
102     // TODO add some timer condition? How old are the loaded information
103     if (_settings->GetLocation() != _last_search_settings.GetLocation()) {
104         CancelTheaterSearch();
105         CancelMovieSearch();
106         _movie_controller->Cancel();
107         _theater_controller->Cancel();
108         AssertedWriteLocker locker(_cinema_schedule->GetLock());
109         _cinema_schedule->Clear();
110     }
111     if (_settings->GetLocation().IsValid()) {
112         // Cancel searchs before _cinema_schedule is locked to avoid dead-locks
113         switch (_settings->GetSearchObjectsType()) {
114         case Settings::THEATERS:
115             CancelTheaterSearch();
116             break;
117         case Settings::MOVIES:
118             CancelMovieSearch();
119             break;
120         }
121         AssertedReadLocker locker(_cinema_schedule->GetLock());
122         _main_window->SetLocation(_settings->GetLocation());
123         switch (_settings->GetSearchObjectsType()) {
124         case Settings::THEATERS:
125             _main_window->SetTheaterModel(_theater_proxy_model);
126             if (!_cinema_schedule->AreAllCinemasLoaded()) {
127                 TheaterSearchClient *client = new TheaterSearchClient(_cinema_schedule);
128                 _current_theater_search_task_id = client->GetSearchTaskId();
129                 connect(client, SIGNAL(SearchStarted(int)), this, SLOT(TheatersSearchStarted(int)));
130                 connect(client, SIGNAL(Reply(int, bool)), this, SLOT(TheatersSearchReply(int, bool)));
131                 connect(client, SIGNAL(Error(int)), this, SLOT(TheatersSearchError(int)));
132                 connect(client, SIGNAL(SearchFinished(int, bool)), this, SLOT(TheatersSearchFinished(int, bool)));
133                 client->moveToThread(_search_worker);
134                 CallAsync(client, &TheaterSearchClient::SearchTheater, _settings->GetLocation().GetLocationName());
135             } else {
136                 TheatersSearchStarted(_current_theater_search_task_id);
137                 TheatersSearchReply(_current_theater_search_task_id, true);
138                 TheatersSearchFinished(_current_theater_search_task_id, true);
139             }
140             break;
141         case Settings::MOVIES:
142             _main_window->SetMovieModel(_movie_proxy_model);
143             if (!_cinema_schedule->AreAllMoviesLoaded()) {
144                 MovieSearchClient *client = new MovieSearchClient(_cinema_schedule);
145                 _current_movies_search_task_id = client->GetSearchTaskId();
146                 connect(client, SIGNAL(SearchStarted(int)), this, SLOT(MoviesSearchStarted(int)));
147                 connect(client, SIGNAL(Reply(int, bool)), this, SLOT(MoviesSearchReply(int, bool)));
148                 connect(client, SIGNAL(Error(int)), this, SLOT(MoviesSearchError(int)));
149                 connect(client, SIGNAL(SearchFinished(int, bool)), this, SLOT(MoviesSearchFinished(int, bool)));
150                 client->moveToThread(_search_worker);
151                 CallAsync(client, &MovieSearchClient::SearchMovie, _settings->GetLocation().GetLocationName());
152             } else {
153                 MoviesSearchStarted(_current_movies_search_task_id);
154                 MoviesSearchReply(_current_movies_search_task_id, true);
155                 MoviesSearchFinished(_current_movies_search_task_id, true);
156             }
157             break;
158         }
159         _last_search_settings = *_settings;
160     } else {
161         _location_controller->OpenLocationDialog();
162     }
163 }
164
165 void MainController::Search(Location location)
166 {
167     _settings->SetLocation(location);
168     Search();
169 }
170
171 void MainController::NetworkConnected()
172 {
173     Search();
174 }
175
176 void MainController::NetworkDisconnected()
177 {
178 }
179
180 void MainController::NetworkError()
181 {
182     _main_window->SetError(tr(MSG_NETWORK_ERROR));
183 }
184
185 void MainController::SearchTheaters()
186 {
187     _settings->SetSearchObjectsType(Settings::THEATERS);
188     Search();
189 }
190
191 void MainController::SearchMovies()
192 {
193     _settings->SetSearchObjectsType(Settings::MOVIES);
194     Search();
195 }
196
197 void MainController::OpenAboutDialog()
198 {
199     AboutDialog *about_dialog = new AboutDialog(_main_window);
200     connect(about_dialog, SIGNAL(ContactAuthor()), _action_controller, SLOT(ContactAuthor()));
201     about_dialog->show();
202     // dialog deletes itself
203 }
204
205 void MainController::CancelTheaterSearch()
206 {
207     AssertedWriteLocker locker(_cinema_schedule->GetLock());
208     _current_theater_search_task_id = TheaterSearchClient::INVALID_SEARCH_TASK_ID;
209     TheaterSearchClient::CancelAllRunningSearchs();
210 }
211
212 void MainController::CancelMovieSearch()
213 {
214     AssertedWriteLocker locker(_cinema_schedule->GetLock());
215     _current_movies_search_task_id = MovieSearchClient::INVALID_SEARCH_TASK_ID;
216     MovieSearchClient::CancelAllRunningSearchs();
217 }
218
219 void MainController::TheatersSearchStarted(int search_task_id)
220 {
221     if (search_task_id != _current_theater_search_task_id) {
222         return;
223     }
224     _main_window->SetBusy(true);
225     SortTheaters(true, SLOT(TheatersSortFinished(QAbstractItemModel*,int,bool)));
226 }
227
228 void MainController::TheatersSearchReply(int search_task_id, bool intermediate)
229 {
230     if (search_task_id != _current_theater_search_task_id) {
231         return;
232     }
233     SortTheaters(intermediate, SLOT(TheatersSortFinished(QAbstractItemModel*,int,bool)));
234 }
235
236 void MainController::TheatersSearchError(int search_task_id)
237 {
238     if (search_task_id != _current_theater_search_task_id) {
239         return;
240     }
241     SortTheaters(false, SLOT(TheatersSortErrorFinished(QAbstractItemModel*,int,bool)));
242 }
243
244 void MainController::TheatersSearchFinished(int search_task_id, bool success)
245 {
246     Q_UNUSED(success);
247     if (search_task_id != _current_theater_search_task_id) {
248         return;
249     }
250     _main_window->SetBusy(false);
251 }
252
253 void MainController::SortTheaters(bool intermediate, const char *slot)
254 {
255     TheaterModel *theater_model = new TheaterModel(_cinema_schedule, this);
256     QSortFilterProxyModel *sort_model = new QSortFilterProxyModel(this);
257     sort_model->setSortCaseSensitivity(Qt::CaseInsensitive);
258     sort_model->setSortRole(TheaterModel::SortRole);
259     sort_model->setDynamicSortFilter(false);
260     sort_model->setSourceModel(theater_model);
261     ItemModelSortClient *sort_client = new ItemModelSortClient(_sort_controller, this);
262     connect(sort_client, SIGNAL(SortFinished(QAbstractItemModel*,int,bool)), this, slot);
263     sort_client->Sort(sort_model, _current_theater_search_task_id, intermediate);
264     // proxy deletes itself
265 }
266
267 void MainController::TheatersSortFinished(QAbstractItemModel *model, int search_task_id, bool intermediate)
268 {
269     if (search_task_id != _current_theater_search_task_id) {
270         return;
271     }
272     SetTheaterModel(model);
273     if (!intermediate) {
274         AssertedWriteLocker locker(_cinema_schedule->GetLock());
275         _cinema_schedule->SetAllCinemasLoaded(true);
276         if (_theater_model->rowCount() == 0) {
277             _main_window->SetError(tr(MSG_NO_THEATERS_FOUND).arg(_settings->GetLocation().GetLocationName()));
278         }
279     }
280 }
281
282 void MainController::TheatersSortErrorFinished(QAbstractItemModel *model, int search_task_id, bool intermediate)
283 {
284     Q_UNUSED(intermediate);
285     if (search_task_id != _current_theater_search_task_id) {
286         return;
287     }
288     SetTheaterModel(model);
289     if (_theater_model->rowCount() == 0) {
290         _main_window->SetError(tr(MSG_THEATERS_ERROR));
291     } else {
292         UiUtils::ShowError(tr(MSG_THEATERS_ERROR));
293     }
294 }
295
296 void MainController::SetTheaterModel(QAbstractItemModel *model)
297 {
298     delete _theater_proxy_model->sourceModel();
299     _theater_proxy_model->setSourceModel(model);
300     delete _theater_model;
301     _theater_model = (TheaterModel *) ((QSortFilterProxyModel *) model)->sourceModel();
302 }
303
304 void MainController::MoviesSearchStarted(int search_task_id)
305 {
306     if (search_task_id != _current_movies_search_task_id) {
307         return;
308     }
309     SortMovies(true, SLOT(MoviesSortFinished(QAbstractItemModel*,int,bool)));
310 }
311
312 void MainController::MoviesSearchReply(int search_task_id, bool intermediate)
313 {
314     if (search_task_id != _current_movies_search_task_id) {
315         return;
316     }
317     SortMovies(intermediate, SLOT(MoviesSortFinished(QAbstractItemModel*,int,bool)));
318 }
319
320 void MainController::MoviesSearchError(int search_task_id)
321 {
322     if (search_task_id != _current_movies_search_task_id) {
323         return;
324     }
325     SortMovies(false, SLOT(MoviesSortErrorFinished(QAbstractItemModel*,int,bool)));
326 }
327
328 void MainController::MoviesSearchFinished(int search_task_id, bool success)
329 {
330     Q_UNUSED(success);
331     if (search_task_id != _current_movies_search_task_id) {
332         return;
333     }
334     _main_window->SetBusy(false);
335 }
336
337 void MainController::SortMovies(bool intermediate, const char *slot)
338 {
339     MovieModel *movie_model = new MovieModel(_cinema_schedule, this);
340     QSortFilterProxyModel *sort_model = new QSortFilterProxyModel(this);
341     sort_model->setSortCaseSensitivity(Qt::CaseInsensitive);
342     sort_model->setSortRole(MovieModel::SortRole);
343     sort_model->setDynamicSortFilter(false);
344     sort_model->setSourceModel(movie_model);
345     ItemModelSortClient *sort_client = new ItemModelSortClient(_sort_controller, this);
346     connect(sort_client, SIGNAL(SortFinished(QAbstractItemModel*,int,bool)), this, slot);
347     sort_client->Sort(sort_model, _current_movies_search_task_id, intermediate);
348     // proxy deletes itself
349 }
350
351 void MainController::MoviesSortFinished(QAbstractItemModel *model, int search_task_id, bool intermediate)
352 {
353     if (search_task_id != _current_movies_search_task_id) {
354         return;
355     }
356     SetMovieModel(model);
357     if (!intermediate) {
358         AssertedWriteLocker locker(_cinema_schedule->GetLock());
359         _cinema_schedule->SetAllMoviesLoaded(true);
360         if (_movie_model->rowCount() == 0) {
361             _main_window->SetError(tr(MSG_NO_MOVIES_FOUND).arg(_settings->GetLocation().GetLocationName()));
362         }
363     }
364 }
365
366 void MainController::MoviesSortErrorFinished(QAbstractItemModel *model, int search_task_id, bool intermediate)
367 {
368     Q_UNUSED(intermediate);
369     if (search_task_id != _current_movies_search_task_id) {
370         return;
371     }
372     SetMovieModel(model);
373     if (_movie_model->rowCount() == 0) {
374         _main_window->SetError(tr(MSG_MOVIES_ERROR));
375     } else {
376         UiUtils::ShowError(tr(MSG_MOVIES_ERROR));
377     }
378 }
379
380 void MainController::SetMovieModel(QAbstractItemModel *model)
381 {
382     delete _movie_proxy_model->sourceModel();
383     _movie_proxy_model->setSourceModel(model);
384     delete _movie_model;
385     _movie_model = (MovieModel *) ((QSortFilterProxyModel *) model)->sourceModel();
386 }