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