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