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