ui: Added support for fetching movie images
[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_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 _create_movie_image(self, movie):
267         image = gtk.Image()
268         image.set_from_pixbuf(gtk.IconTheme().load_icon('general_video',
269                                                         256, 0))
270         banner = hildon.hildon_banner_show_information_with_markup(self,
271                                                                    'ignored',
272                                                                    'Fetching movie cover')
273         banner.set_timeout(constants.TIMEOUT_TIME_MILLIS)
274         hildon.hildon_gtk_window_set_progress_indicator(self, True)
275
276         async_item = AsyncItem(image_downloader, (movie.get_images()[0].get_url(), '/tmp/' + movie.get_title()),
277                                self._set_downloaded_image, (image,))
278         self.async_worker.queue.put(async_item)
279         self.async_worker.start()
280
281         return image
282
283     def _set_downloaded_image(self, image, target, error):
284         image_file = os.path.abspath(target)
285         image.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file_at_size(image_file,
286                                                                    256,
287                                                                    256))
288         hildon.hildon_gtk_window_set_progress_indicator(self, False)
289
290     def _create_contents(self, movie):
291         main_area = hildon.PannableArea()
292
293         main_box = gtk.VBox(False, 20)
294         main_box.set_border_width(20)
295         upper_content = gtk.HBox(False, 40)
296         upper_content.set_border_width(20)
297
298         image = self._create_movie_image(movie)
299
300         side_content = gtk.VBox(False, 30)
301
302         for key in movie.fields:
303             label = gtk.Label()
304             label.set_markup('<b>%(field)s:</b> <small>%(value)s</small>' %
305                              {'field' : key,
306                               'value' : movie.get_value(key)})
307             label.set_alignment(constants.LEFT_ALIGNMENT,
308                                 constants.CENTER_ALIGNMENT)
309             side_content.pack_start(label, False, False)
310
311         upper_content.pack_start(image, False, False)
312         upper_content.pack_start(side_content, False, False)
313
314         movie_overview = hildon.TextView()
315         movie_overview.set_placeholder('Overview')
316         movie_overview.set_wrap_mode(gtk.WRAP_WORD)
317         movie_overview.get_buffer().set_text(movie.get_overview())
318
319         label = gtk.Label()
320         label.set_markup('<b>Overview:</b>')
321         label.set_alignment(constants.LEFT_ALIGNMENT,
322                             constants.CENTER_ALIGNMENT)
323
324         main_box.pack_start(upper_content, False, False)
325         main_box.pack_start(label, False, False)
326         main_box.pack_start(movie_overview, False, False)
327
328         main_area.add_with_viewport(main_box)
329         main_area.set_size_request_policy(hildon.SIZE_REQUEST_CHILDREN)
330
331         return main_area
332
333     def __init__(self, movie):
334         super(MovieWindow, self).__init__()
335         self.async_worker = AsyncWorker()
336         self.set_title('Movie info')
337         self.add(self._create_contents(movie))
338         self.show_all()
339
340 if __name__ == '__main__':
341     maevies = Maevies()
342     maevies.run()