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