Adding magazine covers
[watersofshiloah] / src / windows / magazines.py
1 import logging
2
3 import gobject
4 import gtk
5
6 import hildonize
7 import util.go_utils as go_utils
8 import util.misc as misc_utils
9 import banners
10 import presenter
11 import stream_index
12
13 import windows
14
15
16 _moduleLogger = logging.getLogger(__name__)
17
18
19 class MagazinesWindow(windows._base.ListWindow):
20
21         def __init__(self, player, store, node):
22                 windows._base.ListWindow.__init__(self, player, store, node)
23                 self._window.set_title(self._node.title)
24
25         @classmethod
26         def _get_columns(cls):
27                 yield gobject.TYPE_PYOBJECT, None
28
29                 pixrenderer = gtk.CellRendererPixbuf()
30                 column = gtk.TreeViewColumn("Covers")
31                 column.pack_start(pixrenderer, expand=True)
32                 column.add_attribute(pixrenderer, "pixbuf", 1)
33                 yield gobject.TYPE_OBJECT, column
34
35                 textrenderer = gtk.CellRendererText()
36                 column = gtk.TreeViewColumn("Magazine")
37                 column.pack_start(textrenderer, expand=True)
38                 column.add_attribute(textrenderer, "text", 2)
39                 yield gobject.TYPE_STRING, column
40
41         def _refresh(self):
42                 windows._base.ListWindow._refresh(self)
43                 self._node.get_children(
44                         self._on_magazines,
45                         self._on_error,
46                 )
47
48         @misc_utils.log_exception(_moduleLogger)
49         def _on_magazines(self, programs):
50                 if self._isDestroyed:
51                         _moduleLogger.info("Download complete but window destroyed")
52                         return
53
54                 self._hide_loading()
55                 for i, programNode in enumerate(programs):
56                         program = programNode.get_properties()
57                         img = self._store.get_pixbuf_from_store(self._store.STORE_LOOKUP["nomagazineimage"])
58                         row = programNode, img, program["title"]
59                         self._model.append(row)
60
61                         programNode.get_children(self._create_on_issues(i), self._on_error)
62
63                 self._select_row()
64
65         def _create_on_issues(self, row):
66                 return lambda issues: self._on_issues(row, issues)
67
68         @misc_utils.log_exception(_moduleLogger)
69         def _on_issues(self, row, issues):
70                 for issue in issues:
71                         self._store.get_pixbuf_from_url(
72                                 issue.get_properties()["pictureURL"],
73                                 lambda pix: self._on_image(row, pix),
74                                 self._on_error,
75                         )
76                         break
77                 else:
78                         _moduleLogger.info("No issues for magazine %s" % row)
79
80         @misc_utils.log_exception(_moduleLogger)
81         def _on_image(self, row, pix):
82                 treeiter = self._model.iter_nth_child(None, row)
83                 self._model.set_value(treeiter, 1, pix)
84                 treeiter = self._model.iter_nth_child(None, row)
85                 self._model.row_changed((row, ), treeiter)
86
87         @misc_utils.log_exception(_moduleLogger)
88         def _on_error(self, exception):
89                 self._hide_loading()
90                 self._errorBanner.push_message(str(exception))
91
92         def _window_from_node(self, node):
93                 issuesWindow = MagazineIssuesWindow(self._player, self._store, node)
94                 issuesWindow.window.set_modal(True)
95                 issuesWindow.window.set_transient_for(self._window)
96                 issuesWindow.window.set_default_size(*self._window.get_size())
97                 issuesWindow.connect("quit", self._on_quit)
98                 issuesWindow.connect("home", self._on_home)
99                 issuesWindow.connect("jump-to", self._on_jump)
100                 issuesWindow.show()
101                 return issuesWindow
102
103         @misc_utils.log_exception(_moduleLogger)
104         def _on_row_activated(self, view, path, column):
105                 itr = self._model.get_iter(path)
106                 node = self._model.get_value(itr, 0)
107                 self._window_from_node(node)
108
109
110 gobject.type_register(MagazinesWindow)
111
112
113 class MagazineIssuesWindow(windows._base.ListWindow):
114
115         def __init__(self, player, store, node):
116                 windows._base.ListWindow.__init__(self, player, store, node)
117                 self._window.set_title(self._node.title)
118
119         @classmethod
120         def _get_columns(cls):
121                 yield gobject.TYPE_PYOBJECT, None
122
123                 pixrenderer = gtk.CellRendererPixbuf()
124                 column = gtk.TreeViewColumn("Covers")
125                 column.pack_start(pixrenderer, expand=True)
126                 column.add_attribute(pixrenderer, "pixbuf", 1)
127                 yield gobject.TYPE_OBJECT, column
128
129                 textrenderer = gtk.CellRendererText()
130                 column = gtk.TreeViewColumn("Issue")
131                 column.pack_start(textrenderer, expand=True)
132                 column.add_attribute(textrenderer, "text", 2)
133                 yield gobject.TYPE_STRING, column
134
135         def _refresh(self):
136                 windows._base.ListWindow._refresh(self)
137                 self._node.get_children(
138                         self._on_magazine_issues,
139                         self._on_error,
140                 )
141
142         @misc_utils.log_exception(_moduleLogger)
143         def _on_magazine_issues(self, programs):
144                 if self._isDestroyed:
145                         _moduleLogger.info("Download complete but window destroyed")
146                         return
147
148                 self._hide_loading()
149                 for programNode in programs:
150                         program = programNode.get_properties()
151                         img = self._store.get_pixbuf_from_store(self._store.STORE_LOOKUP["nomagazineimage"])
152                         row = programNode, img, program["title"]
153                         self._model.append(row)
154
155                         self._store.get_pixbuf_from_url(
156                                 program["pictureURL"],
157                                 self._create_on_image(programNode),
158                                 self._on_error,
159                         )
160
161                 self._select_row()
162
163         @misc_utils.log_exception(_moduleLogger)
164         def _on_error(self, exception):
165                 self._hide_loading()
166                 self._errorBanner.push_message(str(exception))
167
168         def _create_on_image(self, programNode):
169                 return lambda pix: self._on_image(programNode, pix)
170
171         @misc_utils.log_exception(_moduleLogger)
172         def _on_image(self, childNode, pix):
173                 for i, row in enumerate(self._model):
174                         if row[0] is childNode:
175                                 break
176                 else:
177                         raise RuntimeError("Could not find %r" % childNode)
178                 treeiter = self._model.iter_nth_child(None, i)
179                 self._model.set_value(treeiter, 1, pix)
180                 treeiter = self._model.iter_nth_child(None, i)
181                 self._model.row_changed((i, ), treeiter)
182
183         def _window_from_node(self, node):
184                 issuesWindow = MagazineArticlesWindow(self._player, self._store, node)
185                 issuesWindow.window.set_modal(True)
186                 issuesWindow.window.set_transient_for(self._window)
187                 issuesWindow.window.set_default_size(*self._window.get_size())
188                 issuesWindow.connect("quit", self._on_quit)
189                 issuesWindow.connect("home", self._on_home)
190                 issuesWindow.connect("jump-to", self._on_jump)
191                 issuesWindow.show()
192                 return issuesWindow
193
194         @misc_utils.log_exception(_moduleLogger)
195         def _on_row_activated(self, view, path, column):
196                 itr = self._model.get_iter(path)
197                 node = self._model.get_value(itr, 0)
198                 self._window_from_node(node)
199
200
201 gobject.type_register(MagazineIssuesWindow)
202
203
204 class MagazineArticlesWindow(windows._base.ListWindow):
205
206         def __init__(self, player, store, node):
207                 windows._base.ListWindow.__init__(self, player, store, node)
208                 self._window.set_title(self._node.title)
209
210         @classmethod
211         def _get_columns(cls):
212                 yield gobject.TYPE_PYOBJECT, None
213
214                 textrenderer = gtk.CellRendererText()
215                 column = gtk.TreeViewColumn("Article")
216                 column.pack_start(textrenderer, expand=True)
217                 column.add_attribute(textrenderer, "text", 1)
218                 yield gobject.TYPE_STRING, column
219
220         def _refresh(self):
221                 windows._base.ListWindow._refresh(self)
222                 self._node.get_children(
223                         self._on_magazine_articles,
224                         self._on_error,
225                 )
226
227         @misc_utils.log_exception(_moduleLogger)
228         def _on_magazine_articles(self, programs):
229                 if self._isDestroyed:
230                         _moduleLogger.info("Download complete but window destroyed")
231                         return
232
233                 self._hide_loading()
234                 for programNode in programs:
235                         program = programNode.get_properties()
236                         row = programNode, "%s\n%s" % (program["title"], program["author"])
237                         self._model.append(row)
238
239                 self._select_row()
240
241         @misc_utils.log_exception(_moduleLogger)
242         def _on_error(self, exception):
243                 self._hide_loading()
244                 self._errorBanner.push_message(str(exception))
245
246         def _window_from_node(self, node):
247                 issuesWindow = MagazineArticleWindow(self._player, self._store, node)
248                 issuesWindow.window.set_modal(True)
249                 issuesWindow.window.set_transient_for(self._window)
250                 issuesWindow.window.set_default_size(*self._window.get_size())
251                 issuesWindow.connect("quit", self._on_quit)
252                 issuesWindow.connect("home", self._on_home)
253                 issuesWindow.connect("jump-to", self._on_jump)
254                 issuesWindow.show()
255                 return issuesWindow
256
257         @misc_utils.log_exception(_moduleLogger)
258         def _on_row_activated(self, view, path, column):
259                 itr = self._model.get_iter(path)
260                 node = self._model.get_value(itr, 0)
261                 self._window_from_node(node)
262
263
264 gobject.type_register(MagazineArticlesWindow)
265
266
267 class MagazineArticleWindow(windows._base.BasicWindow):
268
269         def __init__(self, player, store, node):
270                 windows._base.BasicWindow.__init__(self, player, store)
271                 self._node = node
272                 self._playerNode = self._player.node
273                 self._nextSearch = None
274                 self._updateSeek = None
275
276                 self.connect_auto(self._player, "state-change", self._on_player_state_change)
277                 self.connect_auto(self._player, "title-change", self._on_player_title_change)
278                 self.connect_auto(self._player, "error", self._on_player_error)
279
280                 self._loadingBanner = banners.GenericBanner()
281
282                 self._presenter = presenter.StreamPresenter(self._store)
283                 self._presenter.set_context(
284                         self._store.STORE_LOOKUP["magazine_background"],
285                         self._node.title,
286                         self._node.subtitle,
287                 )
288                 self._presenterNavigation = presenter.NavigationBox()
289                 self._presenterNavigation.toplevel.add(self._presenter.toplevel)
290                 self._presenterNavigation.connect("action", self._on_nav_action)
291                 self._presenterNavigation.connect("navigating", self._on_navigating)
292
293                 self._seekbar = hildonize.create_seekbar()
294                 self._seekbar.connect("change-value", self._on_user_seek)
295
296                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
297                 self._layout.pack_start(self._presenterNavigation.toplevel, True, True)
298                 self._layout.pack_start(self._seekbar, False, False)
299
300                 self._window.set_title(self._node.title)
301
302         def show(self):
303                 windows._base.BasicWindow.show(self)
304                 self._window.show_all()
305                 self._errorBanner.toplevel.hide()
306                 self._loadingBanner.toplevel.hide()
307                 self._set_context(self._player.state)
308                 self._seekbar.hide()
309
310         def jump_to(self, node):
311                 assert self._node is node
312
313         @property
314         def _active(self):
315                 return self._playerNode is self._node
316
317         def _show_loading(self):
318                 animationPath = self._store.STORE_LOOKUP["loading"]
319                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
320                 self._loadingBanner.show(animation, "Loading...")
321
322         def _hide_loading(self):
323                 self._loadingBanner.hide()
324
325         def _set_context(self, state):
326                 if state == self._player.STATE_PLAY:
327                         if self._active:
328                                 self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
329                         else:
330                                 self._presenter.set_state(self._store.STORE_LOOKUP["play"])
331                 elif state == self._player.STATE_PAUSE:
332                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
333                 elif state == self._player.STATE_STOP:
334                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
335                 else:
336                         _moduleLogger.info("Unhandled player state %s" % state)
337
338         @misc_utils.log_exception(_moduleLogger)
339         def _on_user_seek(self, widget, scroll, value):
340                 self._player.seek(value / 100.0)
341
342         @misc_utils.log_exception(_moduleLogger)
343         def _on_player_update_seek(self):
344                 self._seekbar.set_value(self._player.percent_elapsed * 100)
345                 return True if not self._isDestroyed else False
346
347         @misc_utils.log_exception(_moduleLogger)
348         def _on_player_state_change(self, player, newState):
349                 if self._active and self._player.state == self._player.STATE_PLAY:
350                         self._seekbar.show()
351                         assert self._updateSeek is None
352                         self._updateSeek = go_utils.Timeout(self._on_player_update_seek, once=False)
353                         self._updateSeek.start(seconds=1)
354                 else:
355                         self._seekbar.hide()
356                         self._updateSeek.cancel()
357                         self._updateSeek = None
358
359                 if not self._presenterNavigation.is_active():
360                         self._set_context(newState)
361
362         @misc_utils.log_exception(_moduleLogger)
363         def _on_player_title_change(self, player, node):
364                 if not self._active or node in [None, self._node]:
365                         self._playerNode = player.node
366                         return
367                 self._playerNode = player.node
368                 self.emit("jump-to", node)
369                 self._window.destroy()
370
371         @misc_utils.log_exception(_moduleLogger)
372         def _on_player_error(self, player, err, debug):
373                 _moduleLogger.error("%r - %r" % (err, debug))
374
375         @misc_utils.log_exception(_moduleLogger)
376         def _on_navigating(self, widget, navState):
377                 if navState == "clicking":
378                         if self._player.state == self._player.STATE_PLAY:
379                                 if self._active:
380                                         imageName = "pause_pressed"
381                                 else:
382                                         imageName = "play_pressed"
383                         elif self._player.state == self._player.STATE_PAUSE:
384                                 imageName = "play_pressed"
385                         elif self._player.state == self._player.STATE_STOP:
386                                 imageName = "play_pressed"
387                         else:
388                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
389                 elif navState == "down":
390                         imageName = "home"
391                 elif navState == "up":
392                         if self._player.state == self._player.STATE_PLAY:
393                                 if self._active:
394                                         imageName = "pause"
395                                 else:
396                                         imageName = "play"
397                         elif self._player.state == self._player.STATE_PAUSE:
398                                 imageName = "play"
399                         elif self._player.state == self._player.STATE_STOP:
400                                 imageName = "play"
401                         else:
402                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
403                 elif navState == "left":
404                         imageName = "next"
405                 elif navState == "right":
406                         imageName = "prev"
407
408                 self._presenter.set_state(self._store.STORE_LOOKUP[imageName])
409
410         @misc_utils.log_exception(_moduleLogger)
411         def _on_nav_action(self, widget, navState):
412                 self._set_context(self._player.state)
413
414                 if navState == "clicking":
415                         if self._player.state == self._player.STATE_PLAY:
416                                 if self._active:
417                                         self._player.pause()
418                                 else:
419                                         self._player.set_piece_by_node(self._node)
420                                         self._player.play()
421                         elif self._player.state == self._player.STATE_PAUSE:
422                                 self._player.play()
423                         elif self._player.state == self._player.STATE_STOP:
424                                 self._player.set_piece_by_node(self._node)
425                                 self._player.play()
426                         else:
427                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
428                 elif navState == "down":
429                         self.emit("home")
430                         self._window.destroy()
431                 elif navState == "up":
432                         pass
433                 elif navState == "left":
434                         if self._active:
435                                 self._player.next()
436                         else:
437                                 assert self._nextSearch is None
438                                 self._nextSearch = stream_index.AsyncWalker(stream_index.get_next)
439                                 self._nextSearch.start(self._node, self._on_next_node, self._on_node_search_error)
440                 elif navState == "right":
441                         if self._active:
442                                 self._player.back()
443                         else:
444                                 assert self._nextSearch is None
445                                 self._nextSearch = stream_index.AsyncWalker(stream_index.get_previous)
446                                 self._nextSearch.start(self._node, self._on_next_node, self._on_node_search_error)
447
448         @misc_utils.log_exception(_moduleLogger)
449         def _on_next_node(self, node):
450                 self._nextSearch = None
451                 self.emit("jump-to", node)
452                 self._window.destroy()
453
454         @misc_utils.log_exception(_moduleLogger)
455         def _on_node_search_error(self, e):
456                 self._nextSearch = None
457                 self._errorBanner.push_message(str(e))
458
459
460 gobject.type_register(MagazineArticleWindow)