ui: Added webkit view
[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 from webkit import WebView
29
30 from maeviesui.util import constants
31 from maeviesui.util.asyncworker import AsyncWorker, AsyncItem
32 from maeviesui.util.util import image_downloader
33 from maeviesui.util.moviemanager import MovieManager, TmdbMovie, TmdbMovieImage, \
34     WatcMovie
35
36 moviemanager = MovieManager()
37
38 class Maevies(hildon.StackableWindow):
39
40     ACTION_SEARCH = 0
41     ACTION_ABOUT = 1
42     ACTION_THEATERS = 2
43     ACTION_FAVORITES = 3
44
45     def __init__(self):
46         super(Maevies, self).__init__()
47         self.set_title('Maevies - 0.1')
48         self.connect('delete-event',
49                      lambda widget, event: gtk.main_quit())
50
51         self.add(self._create_contents())
52         self.set_app_menu(self._create_app_menu())
53
54         self.show_all()
55
56     def _create_button(self, title, subtitle, action):
57         box = gtk.VBox()
58         box.set_border_width(20)
59
60         button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT,
61                                hildon.BUTTON_ARRANGEMENT_VERTICAL,
62                                title, subtitle)
63         button.connect('clicked', self._button_clicked, action)
64
65         box.pack_start(button, expand=True, fill=False)
66
67         return box
68
69     def _create_contents(self):
70         contents = gtk.HBox()
71         contents.set_border_width(60)
72         contents.set_homogeneous(True)
73         contents.pack_start(self._create_button('On Theaters',
74                                                 'Movies playing',
75                                                 self.ACTION_THEATERS),
76                             expand=True, fill=True)
77         contents.pack_start(self._create_button('Favorites',
78                                                 'Your saved searches',
79                                                 self.ACTION_FAVORITES),
80                             expand=True, fill=True)
81         contents.pack_start(self._create_button('Search',
82                                                 'Enter a new search',
83                                                 self.ACTION_SEARCH),
84                             expand=True, fill=True)
85
86         return contents;
87
88     def _button_clicked(self, button, action):
89         if action == self.ACTION_THEATERS:
90             theaters_view = TheatersWindow()
91             theaters_view.display_shows()
92         elif action == self.ACTION_SEARCH:
93             search_dialog = SearchDialog(self)
94             if search_dialog.run() == gtk.RESPONSE_ACCEPT:
95                 results_window = ResultsWindow()
96                 results_window.start_search(search_dialog.get_search_term(),
97                                             search_dialog.get_search_category())
98             search_dialog.destroy()
99         elif action == self.ACTION_ABOUT:
100             about_dialog = AboutDialog(self)
101             about_dialog.run()
102             about_dialog.destroy()
103
104     def _create_app_menu(self):
105         menu = hildon.AppMenu()
106
107         about = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
108         about.set_label('About')
109         about.connect('clicked', self._button_clicked, self.ACTION_ABOUT)
110
111         menu.append(about)
112
113         menu.show_all()
114
115         return menu
116
117     def run(self):
118         gtk.main()
119
120 class SearchDialog(gtk.Dialog):
121
122     TMDB_SEARCH = 0
123     WATC_SEARCH = 1
124     search_fields = {TMDB_SEARCH:'TMDb',
125                       WATC_SEARCH:'WATC'}
126
127     def __init__(self, parent):
128         super(SearchDialog, self).__init__(parent=parent,
129                                            flags=gtk.DIALOG_DESTROY_WITH_PARENT)
130         self.set_title('Enter search terms')
131
132         self.vbox.pack_start(self._create_contents(), True, False, 0)
133         self.add_button(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
134
135         self.show_all()
136
137     def _create_contents(self):
138         self._search_entry = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
139         search_button = self._create_picker_button()
140
141         search_contents = gtk.VBox()
142
143         search_contents.pack_start(self._search_entry,
144                                    expand=True, fill=True)
145         search_contents.pack_start(search_button,
146                                    expand=True, fill=True)
147
148         return search_contents
149
150     def _create_picker_button(self):
151         self._picker_button = hildon.PickerButton(gtk.HILDON_SIZE_FINGER_HEIGHT,
152                                                   hildon.BUTTON_ARRANGEMENT_HORIZONTAL)
153         self._picker_button.set_title('Search on')
154
155         selector = hildon.TouchSelector(text=True)
156         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
157
158         for search_method in [self.TMDB_SEARCH, self.WATC_SEARCH]:
159             selector.append_text(self.search_fields[search_method])
160
161         self._picker_button.set_selector(selector)
162         self._picker_button.set_active(0)
163
164         return self._picker_button
165
166     def get_search_term(self):
167         return self._search_entry.get_text()
168
169     def get_search_category(self):
170         return self._picker_button.get_active()
171
172 class ResultsWindow(hildon.StackableWindow):
173
174     def __init__(self):
175         super(ResultsWindow, self).__init__()
176         self.set_title('Search results')
177
178         self.add(self._create_contents())
179
180         moviemanager.response_received_cb = self._response_received_cb
181         self.show_all()
182
183     def _create_contents(self):
184         content_area = hildon.PannableArea()
185         self._movies_view = MoviesView()
186         self._movies_view.connect('row-activated', self._row_activated_cb)
187
188         content_area.add(self._movies_view)
189         return content_area
190
191     def _row_activated_cb(self, view, path, column):
192         movie = view.get_movie_from_path(path)
193         if isinstance(movie, TmdbMovie):
194             MovieWindow(movie)
195         elif isinstance(movie, WatcMovie):
196             WatcWindow(movie)
197
198     def start_search(self, search_term, search_category):
199         self._show_banner(search_term, search_category)
200         hildon.hildon_gtk_window_set_progress_indicator(self, True)
201         moviemanager.query(search_term, search_category)
202
203     def _response_received_cb(self, movies):
204         self._movies_view.add_movies(movies)
205         hildon.hildon_gtk_window_set_progress_indicator(self, False)
206
207     def _show_banner(self, search_term, search_category):
208         message = ('Searching on <i>%(category)s</i> for <i>%(term)s</i>' %
209                    {'category': SearchDialog.search_fields[search_category],
210                     'term' : search_term})
211         banner = hildon.hildon_banner_show_information_with_markup(self,
212                                                                    'ignored',
213                                                                    message)
214         banner.set_timeout(constants.TIMEOUT_TIME_MILLIS)
215
216 class MoviesView(gtk.TreeView):
217
218     def __init__(self):
219         super(MoviesView, self).__init__()
220         model = MoviesListStore()
221         self.set_model(model)
222
223         movie_image_renderer = gtk.CellRendererPixbuf()
224         column = gtk.TreeViewColumn('Image', movie_image_renderer,
225                                     pixbuf=model.IMAGE_COLUMN)
226         self.append_column(column)
227
228         movie_text_renderer = gtk.CellRendererText()
229         movie_text_renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
230         column = gtk.TreeViewColumn('Name', movie_text_renderer,
231                                     markup=model.INFO_COLUMN)
232         self.append_column(column)
233
234         self.show_all()
235
236     def add_movies(self, movie_list):
237         model = self.get_model()
238         if model:
239             model.add(movie_list)
240
241     def get_movie_from_path(self, path):
242         model = self.get_model()
243         return model[path][model.MOVIE_COLUMN]
244
245 class MoviesListStore(gtk.ListStore):
246
247     IMAGE_COLUMN = 0
248     INFO_COLUMN = 1
249     MOVIE_COLUMN = 2
250
251     def __init__(self):
252         super(MoviesListStore, self).__init__(gtk.gdk.Pixbuf,
253                                               str,
254                                               gobject.TYPE_PYOBJECT)
255
256     def add(self, movies_found):
257         self.clear()
258         for movie in movies_found:
259             row = {self.IMAGE_COLUMN: movie.get_placeholder_image(),
260                    self.INFO_COLUMN: movie.get_info(),
261                    self.MOVIE_COLUMN: movie,
262                   }
263             self.append(row.values())
264
265 class AboutDialog(gtk.Dialog):
266
267     def __init__(self, parent):
268         super(AboutDialog, self).__init__(parent=parent,
269                                           flags=gtk.DIALOG_DESTROY_WITH_PARENT)
270         self.set_title('About Maevies')
271
272         self.show_all()
273
274 class MovieWindow(hildon.StackableWindow):
275
276     def _fetch_movie_image(self, movie):
277         image = gtk.Image()
278         image.set_from_pixbuf(gtk.IconTheme().load_icon('general_video',
279                                                         256, 0))
280         movie_image = movie.get_image('poster', 'mid')
281         if isinstance(movie_image, TmdbMovieImage):
282             image_file = os.path.abspath('/tmp/' + movie_image.get_id() + '.jpg')
283             if os.path.isfile(image_file):
284                 image.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file_at_size(image_file,
285                                                                        256,
286                                                                        256))
287             else:
288                 banner = hildon.hildon_banner_show_information_with_markup(self,
289                                                                            'ignored',
290                                                                            'Fetching movie poster')
291                 banner.set_timeout(constants.TIMEOUT_TIME_MILLIS)
292                 hildon.hildon_gtk_window_set_progress_indicator(self, True)
293
294                 async_item = AsyncItem(image_downloader, (movie_image.get_url(),
295                                                           '/tmp/' + movie_image.get_id()),
296                                        self._set_fetched_image, (image,))
297                 self.async_worker.queue.put(async_item)
298                 self.async_worker.start()
299
300         return image
301
302     def _set_fetched_image(self, image, target, error):
303         if not error:
304             image_file = os.path.abspath(target)
305             image.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file_at_size(image_file,
306                                                                        256,
307                                                                        256))
308         hildon.hildon_gtk_window_set_progress_indicator(self, False)
309
310     def _create_contents(self, movie):
311         main_area = hildon.PannableArea()
312
313         main_box = gtk.VBox(False, 20)
314         main_box.set_border_width(20)
315         upper_content = gtk.HBox(False, 40)
316         upper_content.set_border_width(20)
317
318         image = self._fetch_movie_image(movie)
319
320         side_content = gtk.VBox(False, 30)
321
322         for key in movie.fields:
323             label = gtk.Label()
324             label.set_markup('<b>%(field)s:</b> <small>%(value)s</small>' %
325                              {'field' : key,
326                               'value' : movie.get_value(key)})
327             label.set_alignment(constants.LEFT_ALIGNMENT,
328                                 constants.CENTER_ALIGNMENT)
329             side_content.pack_start(label, False, False)
330
331         upper_content.pack_start(image, False, False)
332         upper_content.pack_start(side_content, False, False)
333
334         movie_overview = hildon.TextView()
335         movie_overview.set_placeholder('Overview')
336         movie_overview.set_wrap_mode(gtk.WRAP_WORD)
337         movie_overview.get_buffer().set_text(movie.get_overview())
338
339         label = gtk.Label()
340         label.set_markup('<b>Overview:</b>')
341         label.set_alignment(constants.LEFT_ALIGNMENT,
342                             constants.CENTER_ALIGNMENT)
343
344         main_box.pack_start(upper_content, False, False)
345         main_box.pack_start(label, False, False)
346         main_box.pack_start(movie_overview, False, False)
347
348         main_area.add_with_viewport(main_box)
349         main_area.set_size_request_policy(hildon.SIZE_REQUEST_CHILDREN)
350
351         return main_area
352
353     def __init__(self, movie):
354         super(MovieWindow, self).__init__()
355         self.async_worker = AsyncWorker()
356         self.set_title('Movie info')
357         self.add(self._create_contents(movie))
358         self.show_all()
359
360 class WebkitWindow(hildon.StackableWindow):
361
362     def __init__(self):
363         super(WebkitWindow, self).__init__()
364
365         self.view = WebView()
366         self.view.connect('load-started', self._load_start)
367         self.view.connect('load-finished', self._load_finished)
368         self.view.set_full_content_zoom(True)
369
370         wbk_settings = self.view.get_settings()
371         wbk_settings.set_property('auto-shrink-images', True)
372
373         self.pannable = hildon.PannableArea()
374         self.pannable.add(self.view)
375         self.pannable.set_size_request_policy(hildon.SIZE_REQUEST_CHILDREN)
376         self.pannable.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
377
378         self.add(self.pannable)
379         self.show_all()
380
381     def _load_start(self, view, data):
382         hildon.hildon_gtk_window_set_progress_indicator(self, True)
383
384     def _load_finished(self, view, data):
385         hildon.hildon_gtk_window_set_progress_indicator(self, False)
386
387     def load_url(self, url):
388         self.view.open(url)
389
390 class TheatersWindow(WebkitWindow):
391
392     def __init__(self):
393         super(TheatersWindow, self).__init__()
394         self.show_all()
395
396     def display_shows(self):
397         self.load_url('http://www.google.com/movies')
398
399 class WatcWindow(WebkitWindow):
400
401     def __init__(self, movie):
402         super(WatcWindow, self).__init__()
403         self.show_all()
404         has_stingers = movie.get_stingers()
405         if has_stingers == 0:
406             stingers = '?'
407         elif has_stingers == 1:
408             stingers = '*'
409         else:
410             stingers = ''
411         url = ('http://whatsafterthecredits.com/index.php?title=%(title)s_(%(year)s)%(stingers)s' %
412                       {'title':movie.get_title().strip(),
413                        'year':movie.get_year().strip(),
414                        'stingers':stingers})
415         print url
416         self.load_url(url)
417
418 if __name__ == '__main__':
419     maevies = Maevies()
420     maevies.run()