Fixed WATC movie service interface
[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
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         MovieWindow(movie)
189
190     def start_search(self, search_term, search_category):
191         self._show_banner(search_term, search_category)
192         hildon.hildon_gtk_window_set_progress_indicator(self, True)
193         moviemanager.query(search_term, search_category)
194
195     def _response_received_cb(self, movies):
196         self._movies_view.add_movies(movies)
197         hildon.hildon_gtk_window_set_progress_indicator(self, False)
198
199     def _show_banner(self, search_term, search_category):
200         message = ('Searching on <i>%(category)s</i> for <i>%(term)s</i>' %
201                    {'category': SearchDialog.search_fields[search_category],
202                     'term' : search_term})
203         banner = hildon.hildon_banner_show_information_with_markup(self,
204                                                                    'ignored',
205                                                                    message)
206         banner.set_timeout(constants.TIMEOUT_TIME_MILLIS)
207
208 class MoviesView(gtk.TreeView):
209
210     def __init__(self):
211         super(MoviesView, self).__init__()
212         model = MoviesListStore()
213         self.set_model(model)
214
215         movie_image_renderer = gtk.CellRendererPixbuf()
216         column = gtk.TreeViewColumn('Image', movie_image_renderer,
217                                     pixbuf=model.IMAGE_COLUMN)
218         self.append_column(column)
219
220         movie_text_renderer = gtk.CellRendererText()
221         movie_text_renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
222         column = gtk.TreeViewColumn('Name', movie_text_renderer,
223                                     markup=model.INFO_COLUMN)
224         self.append_column(column)
225
226         self.show_all()
227
228     def add_movies(self, movie_list):
229         model = self.get_model()
230         if model:
231             model.add(movie_list)
232
233     def get_movie_from_path(self, path):
234         model = self.get_model()
235         return model[path][model.MOVIE_COLUMN]
236
237 class MoviesListStore(gtk.ListStore):
238
239     IMAGE_COLUMN = 0
240     INFO_COLUMN = 1
241     MOVIE_COLUMN = 2
242
243     def __init__(self):
244         super(MoviesListStore, self).__init__(gtk.gdk.Pixbuf,
245                                               str,
246                                               gobject.TYPE_PYOBJECT)
247
248     def add(self, movies_found):
249         self.clear()
250         for movie in movies_found:
251             row = {self.IMAGE_COLUMN: movie.get_placeholder_image(),
252                    self.INFO_COLUMN: movie.get_info(),
253                    self.MOVIE_COLUMN: movie,
254                   }
255             self.append(row.values())
256
257 class AboutDialog(gtk.Dialog):
258
259     def __init__(self, parent):
260         super(AboutDialog, self).__init__(parent=parent,
261                                           flags=gtk.DIALOG_DESTROY_WITH_PARENT)
262         self.set_title('About Maevies')
263
264         self.show_all()
265
266 class MovieWindow(hildon.StackableWindow):
267
268     def _fetch_movie_image(self, movie):
269         image = gtk.Image()
270
271         movie_image = movie.get_image('poster', 'mid')
272         image_file = os.path.abspath('/tmp/' + movie_image.get_id() + '.jpg')
273
274         if os.path.isfile(image_file):
275             image.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file_at_size(image_file,
276                                                                        256,
277                                                                        256))
278         else:
279             image.set_from_pixbuf(gtk.IconTheme().load_icon('general_video',
280                                                             256, 0))
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()