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