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