ui: Added support for displaying WATC service results
[maevies] / ui / maeviesui / maeviesui / gui.py
1 # -*- coding: utf-8 -*-
2
3 ###########################################################################
4 #    Maevies
5 #    Copyright (C) 2010 Simón Pena <spenap@gmail.com>
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU General Public License as published by
9 #    the Free Software Foundation, either version 3 of the License, or
10 #    (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU General Public License for more details.
16 #
17 #    You should have received a copy of the GNU General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 ###########################################################################
20
21 import pygtk
22 import os
23 pygtk.require('2.0')
24 import gtk
25 import hildon
26 import pango
27 import gobject
28
29 from maeviesui.util import constants
30 from maeviesui.util.asyncworker import AsyncWorker, AsyncItem
31 from maeviesui.util.util import image_downloader
32 from maeviesui.util.moviemanager import MovieManager, TmdbMovie, TmdbMovieImage
33
34 moviemanager = MovieManager()
35
36 class Maevies(hildon.StackableWindow):
37
38     ACTION_SEARCH = 0
39     ACTION_ABOUT = 1
40     ACTION_THEATERS = 2
41     ACTION_FAVORITES = 3
42
43     def __init__(self):
44         super(Maevies, self).__init__()
45         self.set_title('Maevies - 0.1')
46         self.connect('delete-event',
47                      lambda widget, event: gtk.main_quit())
48
49         self.add(self._create_contents())
50         self.set_app_menu(self._create_app_menu())
51
52         self.show_all()
53
54     def _create_button(self, title, subtitle, action):
55         box = gtk.VBox()
56         box.set_border_width(20)
57
58         button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT,
59                                hildon.BUTTON_ARRANGEMENT_VERTICAL,
60                                title, subtitle)
61         button.connect('clicked', self._button_clicked, action)
62
63         box.pack_start(button, expand=True, fill=False)
64
65         return box
66
67     def _create_contents(self):
68         contents = gtk.HBox()
69         contents.set_border_width(60)
70         contents.set_homogeneous(True)
71         contents.pack_start(self._create_button('On Theaters',
72                                                 'Movies playing',
73                                                 self.ACTION_THEATERS),
74                             expand=True, fill=True)
75         contents.pack_start(self._create_button('Favorites',
76                                                 'Your saved searches',
77                                                 self.ACTION_FAVORITES),
78                             expand=True, fill=True)
79         contents.pack_start(self._create_button('Search',
80                                                 'Enter a new search',
81                                                 self.ACTION_SEARCH),
82                             expand=True, fill=True)
83
84         return contents;
85
86     def _button_clicked(self, button, action):
87         if action == self.ACTION_SEARCH:
88             search_dialog = SearchDialog(self)
89             if search_dialog.run() == gtk.RESPONSE_ACCEPT:
90                 results_window = ResultsWindow()
91                 results_window.start_search(search_dialog.get_search_term(),
92                                             search_dialog.get_search_category())
93             search_dialog.destroy()
94         elif action == self.ACTION_ABOUT:
95             about_dialog = AboutDialog(self)
96             about_dialog.run()
97             about_dialog.destroy()
98
99     def _create_app_menu(self):
100         menu = hildon.AppMenu()
101
102         about = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
103         about.set_label('About')
104         about.connect('clicked', self._button_clicked, self.ACTION_ABOUT)
105
106         menu.append(about)
107
108         menu.show_all()
109
110         return menu
111
112     def run(self):
113         gtk.main()
114
115 class SearchDialog(gtk.Dialog):
116
117     TMDB_SEARCH = 0
118     WATC_SEARCH = 1
119     search_fields = {TMDB_SEARCH:'TMDb',
120                       WATC_SEARCH:'WATC'}
121
122     def __init__(self, parent):
123         super(SearchDialog, self).__init__(parent=parent,
124                                            flags=gtk.DIALOG_DESTROY_WITH_PARENT)
125         self.set_title('Enter search terms')
126
127         self.vbox.pack_start(self._create_contents(), True, False, 0)
128         self.add_button(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
129
130         self.show_all()
131
132     def _create_contents(self):
133         self._search_entry = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
134         search_button = self._create_picker_button()
135
136         search_contents = gtk.VBox()
137
138         search_contents.pack_start(self._search_entry,
139                                    expand=True, fill=True)
140         search_contents.pack_start(search_button,
141                                    expand=True, fill=True)
142
143         return search_contents
144
145     def _create_picker_button(self):
146         self._picker_button = hildon.PickerButton(gtk.HILDON_SIZE_FINGER_HEIGHT,
147                                                   hildon.BUTTON_ARRANGEMENT_HORIZONTAL)
148         self._picker_button.set_title('Search on')
149
150         selector = hildon.TouchSelector(text=True)
151         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
152
153         for search_method in [self.TMDB_SEARCH, self.WATC_SEARCH]:
154             selector.append_text(self.search_fields[search_method])
155
156         self._picker_button.set_selector(selector)
157         self._picker_button.set_active(0)
158
159         return self._picker_button
160
161     def get_search_term(self):
162         return self._search_entry.get_text()
163
164     def get_search_category(self):
165         return self._picker_button.get_active()
166
167 class ResultsWindow(hildon.StackableWindow):
168
169     def __init__(self):
170         super(ResultsWindow, self).__init__()
171         self.set_title('Search results')
172
173         self.add(self._create_contents())
174
175         moviemanager.response_received_cb = self._response_received_cb
176         self.show_all()
177
178     def _create_contents(self):
179         content_area = hildon.PannableArea()
180         self._movies_view = MoviesView()
181         self._movies_view.connect('row-activated', self._row_activated_cb)
182
183         content_area.add(self._movies_view)
184         return content_area
185
186     def _row_activated_cb(self, view, path, column):
187         movie = view.get_movie_from_path(path)
188         if isinstance(movie, TmdbMovie):
189             MovieWindow(movie)
190
191     def start_search(self, search_term, search_category):
192         self._show_banner(search_term, search_category)
193         hildon.hildon_gtk_window_set_progress_indicator(self, True)
194         moviemanager.query(search_term, search_category)
195
196     def _response_received_cb(self, movies):
197         self._movies_view.add_movies(movies)
198         hildon.hildon_gtk_window_set_progress_indicator(self, False)
199
200     def _show_banner(self, search_term, search_category):
201         message = ('Searching on <i>%(category)s</i> for <i>%(term)s</i>' %
202                    {'category': SearchDialog.search_fields[search_category],
203                     'term' : search_term})
204         banner = hildon.hildon_banner_show_information_with_markup(self,
205                                                                    'ignored',
206                                                                    message)
207         banner.set_timeout(constants.TIMEOUT_TIME_MILLIS)
208
209 class MoviesView(gtk.TreeView):
210
211     def __init__(self):
212         super(MoviesView, self).__init__()
213         model = MoviesListStore()
214         self.set_model(model)
215
216         movie_image_renderer = gtk.CellRendererPixbuf()
217         column = gtk.TreeViewColumn('Image', movie_image_renderer,
218                                     pixbuf=model.IMAGE_COLUMN)
219         self.append_column(column)
220
221         movie_text_renderer = gtk.CellRendererText()
222         movie_text_renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
223         column = gtk.TreeViewColumn('Name', movie_text_renderer,
224                                     markup=model.INFO_COLUMN)
225         self.append_column(column)
226
227         self.show_all()
228
229     def add_movies(self, movie_list):
230         model = self.get_model()
231         if model:
232             model.add(movie_list)
233
234     def get_movie_from_path(self, path):
235         model = self.get_model()
236         return model[path][model.MOVIE_COLUMN]
237
238 class MoviesListStore(gtk.ListStore):
239
240     IMAGE_COLUMN = 0
241     INFO_COLUMN = 1
242     MOVIE_COLUMN = 2
243
244     def __init__(self):
245         super(MoviesListStore, self).__init__(gtk.gdk.Pixbuf,
246                                               str,
247                                               gobject.TYPE_PYOBJECT)
248
249     def add(self, movies_found):
250         self.clear()
251         for movie in movies_found:
252             row = {self.IMAGE_COLUMN: movie.get_placeholder_image(),
253                    self.INFO_COLUMN: movie.get_info(),
254                    self.MOVIE_COLUMN: movie,
255                   }
256             self.append(row.values())
257
258 class AboutDialog(gtk.Dialog):
259
260     def __init__(self, parent):
261         super(AboutDialog, self).__init__(parent=parent,
262                                           flags=gtk.DIALOG_DESTROY_WITH_PARENT)
263         self.set_title('About Maevies')
264
265         self.show_all()
266
267 class MovieWindow(hildon.StackableWindow):
268
269     def _fetch_movie_image(self, movie):
270         image = gtk.Image()
271         image.set_from_pixbuf(gtk.IconTheme().load_icon('general_video',
272                                                         256, 0))
273         movie_image = movie.get_image('poster', 'mid')
274         if isinstance(movie_image, TmdbMovieImage):
275             image_file = os.path.abspath('/tmp/' + movie_image.get_id() + '.jpg')
276             if os.path.isfile(image_file):
277                 image.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file_at_size(image_file,
278                                                                        256,
279                                                                        256))
280             else:
281                 banner = hildon.hildon_banner_show_information_with_markup(self,
282                                                                            'ignored',
283                                                                            'Fetching movie poster')
284                 banner.set_timeout(constants.TIMEOUT_TIME_MILLIS)
285                 hildon.hildon_gtk_window_set_progress_indicator(self, True)
286
287                 async_item = AsyncItem(image_downloader, (movie_image.get_url(),
288                                                           '/tmp/' + movie_image.get_id()),
289                                        self._set_fetched_image, (image,))
290                 self.async_worker.queue.put(async_item)
291                 self.async_worker.start()
292
293         return image
294
295     def _set_fetched_image(self, image, target, error):
296         if not error:
297             image_file = os.path.abspath(target)
298             image.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file_at_size(image_file,
299                                                                        256,
300                                                                        256))
301         hildon.hildon_gtk_window_set_progress_indicator(self, False)
302
303     def _create_contents(self, movie):
304         main_area = hildon.PannableArea()
305
306         main_box = gtk.VBox(False, 20)
307         main_box.set_border_width(20)
308         upper_content = gtk.HBox(False, 40)
309         upper_content.set_border_width(20)
310
311         image = self._fetch_movie_image(movie)
312
313         side_content = gtk.VBox(False, 30)
314
315         for key in movie.fields:
316             label = gtk.Label()
317             label.set_markup('<b>%(field)s:</b> <small>%(value)s</small>' %
318                              {'field' : key,
319                               'value' : movie.get_value(key)})
320             label.set_alignment(constants.LEFT_ALIGNMENT,
321                                 constants.CENTER_ALIGNMENT)
322             side_content.pack_start(label, False, False)
323
324         upper_content.pack_start(image, False, False)
325         upper_content.pack_start(side_content, False, False)
326
327         movie_overview = hildon.TextView()
328         movie_overview.set_placeholder('Overview')
329         movie_overview.set_wrap_mode(gtk.WRAP_WORD)
330         movie_overview.get_buffer().set_text(movie.get_overview())
331
332         label = gtk.Label()
333         label.set_markup('<b>Overview:</b>')
334         label.set_alignment(constants.LEFT_ALIGNMENT,
335                             constants.CENTER_ALIGNMENT)
336
337         main_box.pack_start(upper_content, False, False)
338         main_box.pack_start(label, False, False)
339         main_box.pack_start(movie_overview, False, False)
340
341         main_area.add_with_viewport(main_box)
342         main_area.set_size_request_policy(hildon.SIZE_REQUEST_CHILDREN)
343
344         return main_area
345
346     def __init__(self, movie):
347         super(MovieWindow, self).__init__()
348         self.async_worker = AsyncWorker()
349         self.set_title('Movie info')
350         self.add(self._create_contents(movie))
351         self.show_all()
352
353 if __name__ == '__main__':
354     maevies = Maevies()
355     maevies.run()