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