Various bug fixes to work on Maemo
[watersofshiloah] / src / windows / scriptures.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 ScripturesWindow(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                 textrenderer = gtk.CellRendererText()
30                 column = gtk.TreeViewColumn("Scripture")
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_scriptures,
39                         self._on_error,
40                 )
41
42         @misc_utils.log_exception(_moduleLogger)
43         def _on_scriptures(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                 booksWindow = ScriptureBooksWindow(self._app, self._player, self._store, node)
63                 booksWindow.window.set_modal(True)
64                 booksWindow.window.set_transient_for(self._window)
65                 booksWindow.window.set_default_size(*self._window.get_size())
66                 booksWindow.connect("quit", self._on_quit)
67                 booksWindow.connect("home", self._on_home)
68                 booksWindow.connect("jump-to", self._on_jump)
69                 booksWindow.show()
70                 return booksWindow
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(ScripturesWindow)
80
81
82 class ScriptureBooksWindow(windows._base.ListWindow):
83
84         def __init__(self, app, player, store, node):
85                 windows._base.ListWindow.__init__(self, app, 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("Book")
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_scripture_books,
102                         self._on_error,
103                 )
104
105         @misc_utils.log_exception(_moduleLogger)
106         def _on_scripture_books(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                 booksWindow = ScriptureChaptersWindow(self._app, self._player, self._store, node)
126                 booksWindow.window.set_modal(True)
127                 booksWindow.window.set_transient_for(self._window)
128                 booksWindow.window.set_default_size(*self._window.get_size())
129                 booksWindow.connect("quit", self._on_quit)
130                 booksWindow.connect("home", self._on_home)
131                 booksWindow.connect("jump-to", self._on_jump)
132                 booksWindow.show()
133                 return booksWindow
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(ScriptureBooksWindow)
143
144
145 class ScriptureChaptersWindow(windows._base.ListWindow):
146
147         def __init__(self, app, player, store, node):
148                 windows._base.ListWindow.__init__(self, app, 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("Chapter")
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_scripture_chapters,
165                         self._on_error,
166                 )
167
168         @misc_utils.log_exception(_moduleLogger)
169         def _on_scripture_chapters(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, program["title"]
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                 booksWindow = ScriptureChapterWindow(self._app, self._player, self._store, node)
189                 booksWindow.window.set_modal(True)
190                 booksWindow.window.set_transient_for(self._window)
191                 booksWindow.window.set_default_size(*self._window.get_size())
192                 booksWindow.connect("quit", self._on_quit)
193                 booksWindow.connect("home", self._on_home)
194                 booksWindow.connect("jump-to", self._on_jump)
195                 booksWindow.show()
196                 return booksWindow
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(ScriptureChaptersWindow)
206
207
208 class ScriptureChapterWindow(windows._base.BasicWindow):
209
210         def __init__(self, app, player, store, node):
211                 windows._base.BasicWindow.__init__(self, app, 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["scripture_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                 if self._isDestroyed:
286                         return False
287                 self._seekbar.set_value(self._player.percent_elapsed * 100)
288                 return True
289
290         @misc_utils.log_exception(_moduleLogger)
291         def _on_player_state_change(self, player, newState):
292                 if self._active and self._player.state == self._player.STATE_PLAY:
293                         self._seekbar.show()
294                         assert self._updateSeek is None
295                         self._updateSeek = go_utils.Timeout(self._on_player_update_seek, once=False)
296                         self._updateSeek.start(seconds=1)
297                 else:
298                         self._seekbar.hide()
299                         if self._updateSeek is not None:
300                                 self._updateSeek.cancel()
301                                 self._updateSeek = None
302
303                 if not self._presenterNavigation.is_active():
304                         self._set_context(newState)
305
306         @misc_utils.log_exception(_moduleLogger)
307         def _on_player_title_change(self, player, node):
308                 if not self._active or node in [None, self._node]:
309                         self._playerNode = player.node
310                         return
311                 self._playerNode = player.node
312                 self.emit("jump-to", node)
313                 self._window.destroy()
314
315         @misc_utils.log_exception(_moduleLogger)
316         def _on_player_error(self, player, err, debug):
317                 _moduleLogger.error("%r - %r" % (err, debug))
318
319         @misc_utils.log_exception(_moduleLogger)
320         def _on_navigating(self, widget, navState):
321                 if navState == "clicking":
322                         if self._player.state == self._player.STATE_PLAY:
323                                 if self._active:
324                                         imageName = "pause_pressed"
325                                 else:
326                                         imageName = "play_pressed"
327                         elif self._player.state == self._player.STATE_PAUSE:
328                                 imageName = "play_pressed"
329                         elif self._player.state == self._player.STATE_STOP:
330                                 imageName = "play_pressed"
331                         else:
332                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
333                 elif navState == "down":
334                         imageName = "home"
335                 elif navState == "up":
336                         if self._player.state == self._player.STATE_PLAY:
337                                 if self._active:
338                                         imageName = "pause"
339                                 else:
340                                         imageName = "play"
341                         elif self._player.state == self._player.STATE_PAUSE:
342                                 imageName = "play"
343                         elif self._player.state == self._player.STATE_STOP:
344                                 imageName = "play"
345                         else:
346                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
347                 elif navState == "left":
348                         imageName = "next"
349                 elif navState == "right":
350                         imageName = "prev"
351
352                 self._presenter.set_state(self._store.STORE_LOOKUP[imageName])
353
354         @misc_utils.log_exception(_moduleLogger)
355         def _on_nav_action(self, widget, navState):
356                 self._set_context(self._player.state)
357
358                 if navState == "clicking":
359                         if self._player.state == self._player.STATE_PLAY:
360                                 if self._active:
361                                         self._player.pause()
362                                 else:
363                                         self._player.set_piece_by_node(self._node)
364                                         self._player.play()
365                         elif self._player.state == self._player.STATE_PAUSE:
366                                 self._player.play()
367                         elif self._player.state == self._player.STATE_STOP:
368                                 self._player.set_piece_by_node(self._node)
369                                 self._player.play()
370                         else:
371                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
372                 elif navState == "down":
373                         self.emit("home")
374                         self._window.destroy()
375                 elif navState == "up":
376                         pass
377                 elif navState == "left":
378                         if self._active:
379                                 self._player.next()
380                         else:
381                                 assert self._nextSearch is None
382                                 self._nextSearch = stream_index.AsyncWalker(stream_index.get_next)
383                                 self._nextSearch.start(self._node, self._on_next_node, self._on_node_search_error)
384                 elif navState == "right":
385                         if self._active:
386                                 self._player.back()
387                         else:
388                                 assert self._nextSearch is None
389                                 self._nextSearch = stream_index.AsyncWalker(stream_index.get_previous)
390                                 self._nextSearch.start(self._node, self._on_next_node, self._on_node_search_error)
391
392         @misc_utils.log_exception(_moduleLogger)
393         def _on_next_node(self, node):
394                 self._nextSearch = None
395                 self.emit("jump-to", node)
396                 self._window.destroy()
397
398         @misc_utils.log_exception(_moduleLogger)
399         def _on_node_search_error(self, e):
400                 self._nextSearch = None
401                 self._errorBanner.push_message(str(e))
402
403
404 gobject.type_register(ScriptureChapterWindow)