983be1bd8ccbe99087d8553bf84671c15984f2bf
[watersofshiloah] / src / windows.py
1 # @todo Add icons to buttons/rows to indicate that the currently playing track is coming from that
2
3 import ConfigParser
4 import datetime
5 import logging
6
7 import gobject
8 import gtk
9
10 import constants
11 import hildonize
12 import util.misc as misc_utils
13 import util.go_utils as go_utils
14
15 import stream_index
16 import banners
17 import playcontrol
18 import presenter
19
20
21 _moduleLogger = logging.getLogger(__name__)
22
23
24 class BasicWindow(gobject.GObject, go_utils.AutoSignal):
25
26         __gsignals__ = {
27                 'quit' : (
28                         gobject.SIGNAL_RUN_LAST,
29                         gobject.TYPE_NONE,
30                         (),
31                 ),
32                 'home' : (
33                         gobject.SIGNAL_RUN_LAST,
34                         gobject.TYPE_NONE,
35                         (),
36                 ),
37                 'jump-to' : (
38                         gobject.SIGNAL_RUN_LAST,
39                         gobject.TYPE_NONE,
40                         (gobject.TYPE_PYOBJECT, ),
41                 ),
42                 'rotate' : (
43                         gobject.SIGNAL_RUN_LAST,
44                         gobject.TYPE_NONE,
45                         (gobject.TYPE_BOOLEAN, ),
46                 ),
47                 'fullscreen' : (
48                         gobject.SIGNAL_RUN_LAST,
49                         gobject.TYPE_NONE,
50                         (gobject.TYPE_BOOLEAN, ),
51                 ),
52         }
53
54         def __init__(self, player, store):
55                 gobject.GObject.__init__(self)
56                 self._isDestroyed = False
57
58                 self._player = player
59                 self._store = store
60
61                 self._clipboard = gtk.clipboard_get()
62                 self._windowInFullscreen = False
63
64                 self._errorBanner = banners.StackingBanner()
65
66                 self._layout = gtk.VBox()
67                 self._layout.pack_start(self._errorBanner.toplevel, False, True)
68
69                 self._window = gtk.Window()
70                 go_utils.AutoSignal.__init__(self, self.window)
71                 self._window.add(self._layout)
72                 self._window = hildonize.hildonize_window(self, self._window)
73
74                 self._window.set_icon(self._store.get_pixbuf_from_store(self._store.STORE_LOOKUP["icon"]))
75                 self._window.connect("key-press-event", self._on_key_press)
76                 self._window.connect("window-state-event", self._on_window_state_change)
77                 self._window.connect("destroy", self._on_destroy)
78
79         @property
80         def window(self):
81                 return self._window
82
83         def show(self):
84                 self._window.show_all()
85
86         def save_settings(self, config, sectionName):
87                 config.add_section(sectionName)
88                 config.set(sectionName, "fullscreen", str(self._windowInFullscreen))
89
90         def load_settings(self, config, sectionName):
91                 try:
92                         self._windowInFullscreen = config.getboolean(sectionName, "fullscreen")
93                 except ConfigParser.NoSectionError, e:
94                         _moduleLogger.info(
95                                 "Settings file %s is missing section %s" % (
96                                         constants._user_settings_,
97                                         e.section,
98                                 )
99                         )
100
101                 if self._windowInFullscreen:
102                         self._window.fullscreen()
103                 else:
104                         self._window.unfullscreen()
105
106         def jump_to(self, node):
107                 raise NotImplementedError("On %s" % self)
108
109         @misc_utils.log_exception(_moduleLogger)
110         def _on_destroy(self, *args):
111                 self._isDestroyed = True
112
113         @misc_utils.log_exception(_moduleLogger)
114         def _on_window_state_change(self, widget, event, *args):
115                 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
116                         self._windowInFullscreen = True
117                 else:
118                         self._windowInFullscreen = False
119                 self.emit("fullscreen", self._windowInFullscreen)
120
121         @misc_utils.log_exception(_moduleLogger)
122         def _on_key_press(self, widget, event, *args):
123                 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
124                 isCtrl = bool(event.get_state() & gtk.gdk.CONTROL_MASK)
125                 if (
126                         event.keyval == gtk.keysyms.F6 or
127                         event.keyval in RETURN_TYPES and isCtrl
128                 ):
129                         # The "Full screen" hardware key has been pressed
130                         if self._windowInFullscreen:
131                                 self._window.unfullscreen ()
132                         else:
133                                 self._window.fullscreen ()
134                         return True
135                 elif (
136                         event.keyval in (gtk.keysyms.w, ) and
137                         event.get_state() & gtk.gdk.CONTROL_MASK
138                 ):
139                         self._window.destroy()
140                 elif (
141                         event.keyval in (gtk.keysyms.q, ) and
142                         event.get_state() & gtk.gdk.CONTROL_MASK
143                 ):
144                         self.emit("quit")
145                 elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
146                         with open(constants._user_logpath_, "r") as f:
147                                 logLines = f.xreadlines()
148                                 log = "".join(logLines)
149                                 self._clipboard.set_text(str(log))
150                         return True
151
152         @misc_utils.log_exception(_moduleLogger)
153         def _on_home(self, *args):
154                 self.emit("home")
155                 self._window.destroy()
156
157         @misc_utils.log_exception(_moduleLogger)
158         def _on_jump(self, source, node):
159                 raise NotImplementedError("On %s" % self)
160
161         @misc_utils.log_exception(_moduleLogger)
162         def _on_quit(self, *args):
163                 self.emit("quit")
164                 self._window.destroy()
165
166
167 class SourceSelector(BasicWindow):
168
169         def __init__(self, player, store, index):
170                 BasicWindow.__init__(self, player, store)
171                 self._languages = []
172                 self._index = index
173
174                 self._loadingBanner = banners.GenericBanner()
175
176                 self._radioButton = self._create_button("radio", "Radio")
177                 self._radioButton.connect("clicked", self._on_source_selected, stream_index.SOURCE_RADIO)
178                 self._radioWrapper = gtk.VBox()
179                 self._radioWrapper.pack_start(self._radioButton, False, True)
180
181                 self._conferenceButton = self._create_button("conferences", "Conferences")
182                 self._conferenceButton.connect("clicked", self._on_source_selected, stream_index.SOURCE_CONFERENCES)
183                 self._conferenceWrapper = gtk.VBox()
184                 self._conferenceWrapper.pack_start(self._conferenceButton, False, True)
185
186                 self._magazineButton = self._create_button("magazines", "Magazines")
187                 self._magazineButton.connect("clicked", self._on_source_selected, stream_index.SOURCE_MAGAZINES)
188                 self._magazineWrapper = gtk.VBox()
189                 self._magazineWrapper.pack_start(self._magazineButton, False, True)
190
191                 self._scriptureButton = self._create_button("scriptures", "Scriptures")
192                 self._scriptureButton.connect("clicked", self._on_source_selected, stream_index.SOURCE_SCRIPTURES)
193                 self._scriptureWrapper = gtk.VBox()
194                 self._scriptureWrapper.pack_start(self._scriptureButton, False, True)
195
196                 self._buttonLayout = gtk.VButtonBox()
197                 self._buttonLayout.set_layout(gtk.BUTTONBOX_SPREAD)
198                 self._buttonLayout.pack_start(self._radioWrapper, True, True)
199                 self._buttonLayout.pack_start(self._conferenceWrapper, True, True)
200                 self._buttonLayout.pack_start(self._magazineWrapper, True, True)
201                 self._buttonLayout.pack_start(self._scriptureWrapper, True, True)
202
203                 self._separator = gtk.HSeparator()
204                 self._playcontrol = playcontrol.NavControl(player, store)
205                 self._playcontrol.connect("jump-to", self._on_jump)
206
207                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
208                 self._layout.pack_start(self._buttonLayout, True, True)
209                 self._layout.pack_start(self._separator, False, True)
210                 self._layout.pack_start(self._playcontrol.toplevel, False, True)
211
212                 self._window.set_title(constants.__pretty_app_name__)
213
214         def show(self):
215                 BasicWindow.show(self)
216
217                 self._errorBanner.toplevel.hide()
218                 self._playcontrol.toplevel.hide()
219
220                 self._refresh()
221
222         def _show_loading(self):
223                 animationPath = self._store.STORE_LOOKUP["loading"]
224                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
225                 self._loadingBanner.show(animation, "Loading...")
226                 self._buttonLayout.set_sensitive(False)
227
228         def _hide_loading(self):
229                 self._loadingBanner.hide()
230                 self._buttonLayout.set_sensitive(True)
231
232         def _refresh(self):
233                 self._show_loading()
234                 self._index.get_languages(self._on_languages, self._on_error)
235
236         def _create_button(self, icon, message):
237                 image = self._store.get_image_from_store(self._store.STORE_LOOKUP[icon])
238
239                 label = gtk.Label()
240                 label.set_text(message)
241
242                 buttonLayout = gtk.HBox(False, 5)
243                 buttonLayout.pack_start(image, False, False)
244                 buttonLayout.pack_start(label, False, True)
245                 button = gtk.Button()
246                 button.add(buttonLayout)
247
248                 return button
249
250         @misc_utils.log_exception(_moduleLogger)
251         def _on_languages(self, languages):
252                 self._hide_loading()
253                 self._languages = list(languages)
254
255         @misc_utils.log_exception(_moduleLogger)
256         def _on_error(self, exception):
257                 self._hide_loading()
258                 self._errorBanner.push_message(str(exception))
259
260         def _window_from_node(self, node):
261                 if node.id == stream_index.SOURCE_RADIO:
262                         Source = RadioWindow
263                 elif node.id == stream_index.SOURCE_CONFERENCES:
264                         Source = ConferencesWindow
265                 elif node.id == stream_index.SOURCE_MAGAZINES:
266                         pass
267                 elif node.id == stream_index.SOURCE_SCRIPTURES:
268                         pass
269                 sourceWindow = Source(self._player, self._store, node)
270                 sourceWindow.window.set_modal(True)
271                 sourceWindow.window.set_transient_for(self._window)
272                 sourceWindow.window.set_default_size(*self._window.get_size())
273                 sourceWindow.connect("quit", self._on_quit)
274                 sourceWindow.connect("jump-to", self._on_jump)
275                 sourceWindow.show()
276                 return sourceWindow
277
278         @misc_utils.log_exception(_moduleLogger)
279         def _on_jump(self, source, node):
280                 targetNodePath = list(reversed(list(stream_index.walk_ancestors(node))))
281                 ancestor = targetNodePath[0]
282                 window = self._window_from_node(ancestor)
283                 window.jump_to(node)
284
285         @misc_utils.log_exception(_moduleLogger)
286         def _on_source_selected(self, widget, nodeName):
287                 node = self._index.get_source(nodeName, self._languages[0]["id"])
288                 self._window_from_node(node)
289
290
291 gobject.type_register(SourceSelector)
292
293
294 class RadioWindow(BasicWindow):
295
296         def __init__(self, player, store, node):
297                 BasicWindow.__init__(self, player, store)
298                 self._node = node
299                 self._childNode = None
300
301                 self.connect_auto(self._player, "state-change", self._on_player_state_change)
302                 self.connect_auto(self._player, "title-change", self._on_player_title_change)
303
304                 self._loadingBanner = banners.GenericBanner()
305
306                 headerPath = self._store.STORE_LOOKUP["radio_header"]
307                 self._header = self._store.get_image_from_store(headerPath)
308                 self._headerNavigation = presenter.NavigationBox()
309                 self._headerNavigation.toplevel.add(self._header)
310                 self._headerNavigation.connect("action", self._on_nav_action)
311                 self._headerNavigation.connect("navigating", self._on_navigating)
312
313                 self._programmingModel = gtk.ListStore(
314                         gobject.TYPE_STRING,
315                         gobject.TYPE_STRING,
316                 )
317
318                 textrenderer = gtk.CellRendererText()
319                 timeColumn = gtk.TreeViewColumn("Time")
320                 timeColumn.pack_start(textrenderer, expand=True)
321                 timeColumn.add_attribute(textrenderer, "text", 0)
322
323                 textrenderer = gtk.CellRendererText()
324                 titleColumn = gtk.TreeViewColumn("Program")
325                 titleColumn.pack_start(textrenderer, expand=True)
326                 titleColumn.add_attribute(textrenderer, "text", 1)
327
328                 self._treeView = gtk.TreeView()
329                 self._treeView.set_headers_visible(False)
330                 self._treeView.set_model(self._programmingModel)
331                 self._treeView.append_column(timeColumn)
332                 self._treeView.append_column(titleColumn)
333                 self._treeView.get_selection().connect("changed", self._on_row_changed)
334
335                 self._treeScroller = gtk.ScrolledWindow()
336                 self._treeScroller.add(self._treeView)
337                 self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
338
339                 self._presenter = presenter.StreamMiniPresenter(self._store)
340                 self._presenterNavigation = presenter.NavigationBox()
341                 self._presenterNavigation.toplevel.add(self._presenter.toplevel)
342                 self._presenterNavigation.connect("action", self._on_nav_action)
343                 self._presenterNavigation.connect("navigating", self._on_navigating)
344
345                 self._radioLayout = gtk.VBox(False)
346                 self._radioLayout.pack_start(self._headerNavigation.toplevel, False, False)
347                 self._radioLayout.pack_start(self._treeScroller, True, True)
348                 self._radioLayout.pack_start(self._presenterNavigation.toplevel, False, True)
349
350                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
351                 self._layout.pack_start(self._radioLayout, True, True)
352
353                 self._dateShown = datetime.datetime.now()
354                 self._update_title()
355
356         def show(self):
357                 BasicWindow.show(self)
358
359                 self._errorBanner.toplevel.hide()
360                 self._loadingBanner.toplevel.hide()
361
362                 self._refresh()
363
364         def jump_to(self, node):
365                 _moduleLogger.info("Only 1 channel, nothing to jump to")
366
367         def _update_title(self):
368                 self._window.set_title("%s - %s" % (self._node.title, self._dateShown.strftime("%m/%d")))
369
370         @property
371         def _active(self):
372                 return self._player.node is self._childNode
373
374         def _set_context(self, state):
375                 if state == self._player.STATE_PLAY:
376                         if self._active:
377                                 self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
378                         else:
379                                 self._presenter.set_state(self._store.STORE_LOOKUP["play"])
380                 elif state == self._player.STATE_PAUSE:
381                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
382                 elif state == self._player.STATE_STOP:
383                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
384                 else:
385                         _moduleLogger.info("Unhandled player state %s" % state)
386                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
387
388         def _show_loading(self):
389                 animationPath = self._store.STORE_LOOKUP["loading"]
390                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
391                 self._loadingBanner.show(animation, "Loading...")
392
393         def _hide_loading(self):
394                 self._loadingBanner.hide()
395
396         def _refresh(self):
397                 self._show_loading()
398                 self._programmingModel.clear()
399                 self._node.get_children(
400                         self._on_channels,
401                         self._on_load_error,
402                 )
403                 self._set_context(self._player.state)
404
405         def _get_current_row(self):
406                 nowTime = self._dateShown.strftime("%H:%M:%S")
407                 i = 0
408                 for i, row in enumerate(self._programmingModel):
409                         if nowTime < row[0]:
410                                 if i == 0:
411                                         return 0
412                                 else:
413                                         return i - 1
414                 else:
415                         return i
416
417         @misc_utils.log_exception(_moduleLogger)
418         def _on_player_state_change(self, player, newState):
419                 if self._headerNavigation.is_active() or self._presenterNavigation.is_active():
420                         return
421
422                 self._set_context(newState)
423
424         @misc_utils.log_exception(_moduleLogger)
425         def _on_player_title_change(self, player, node):
426                 if node is not self._childNode or node is None:
427                         _moduleLogger.info("Player title magically changed to %s" % player.title)
428                         return
429
430         @misc_utils.log_exception(_moduleLogger)
431         def _on_navigating(self, widget, navState):
432                 if navState == "clicking":
433                         if self._player.state == self._player.STATE_PLAY:
434                                 if self._active:
435                                         imageName = "pause_pressed"
436                                 else:
437                                         imageName = "play_pressed"
438                         elif self._player.state == self._player.STATE_PAUSE:
439                                 imageName = "play_pressed"
440                         elif self._player.state == self._player.STATE_STOP:
441                                 imageName = "play_pressed"
442                         else:
443                                 imageName = "play_pressed"
444                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
445                 elif navState == "down":
446                         imageName = "home"
447                 else:
448                         if self._player.state == self._player.STATE_PLAY:
449                                 imageName = "pause"
450                         else:
451                                 imageName = "play"
452
453                 self._presenter.set_state(self._store.STORE_LOOKUP[imageName])
454
455         @misc_utils.log_exception(_moduleLogger)
456         def _on_nav_action(self, widget, navState):
457                 self._set_context(self._player.state)
458
459                 if navState == "clicking":
460                         if self._player.state == self._player.STATE_PLAY:
461                                 if self._active:
462                                         self._player.pause()
463                                 else:
464                                         self._player.set_piece_by_node(self._childNode)
465                                         self._player.play()
466                         elif self._player.state == self._player.STATE_PAUSE:
467                                 self._player.play()
468                         elif self._player.state == self._player.STATE_STOP:
469                                 self._player.set_piece_by_node(self._childNode)
470                                 self._player.play()
471                         else:
472                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
473                 elif navState == "down":
474                         self.window.destroy()
475                 elif navState == "up":
476                         pass
477                 elif navState == "left":
478                         self._dateShown += datetime.timedelta(days=1)
479                         self._update_title()
480                         self._refresh()
481                 elif navState == "right":
482                         self._dateShown -= datetime.timedelta(days=1)
483                         self._update_title()
484                         self._refresh()
485
486         @misc_utils.log_exception(_moduleLogger)
487         def _on_channels(self, channels):
488                 if self._isDestroyed:
489                         _moduleLogger.info("Download complete but window destroyed")
490                         return
491
492                 channels = channels
493                 if 1 < len(channels):
494                         _moduleLogger.warning("More channels now available!")
495                 self._childNode = channels[0]
496                 self._childNode.get_programming(
497                         self._dateShown,
498                         self._on_channel,
499                         self._on_load_error,
500                 )
501
502         @misc_utils.log_exception(_moduleLogger)
503         def _on_channel(self, programs):
504                 if self._isDestroyed:
505                         _moduleLogger.info("Download complete but window destroyed")
506                         return
507
508                 self._hide_loading()
509                 for program in programs:
510                         row = program["time"], program["title"]
511                         self._programmingModel.append(row)
512
513                 currentDate = datetime.datetime.now()
514                 if currentDate.date() != self._dateShown.date():
515                         self._treeView.get_selection().set_mode(gtk.SELECTION_NONE)
516                 else:
517                         self._treeView.get_selection().set_mode(gtk.SELECTION_SINGLE)
518                         path = (self._get_current_row(), )
519                         self._treeView.scroll_to_cell(path)
520                         self._treeView.get_selection().select_path(path)
521
522         @misc_utils.log_exception(_moduleLogger)
523         def _on_load_error(self, exception):
524                 self._hide_loading()
525                 self._errorBanner.push_message(str(exception))
526
527         @misc_utils.log_exception(_moduleLogger)
528         def _on_row_changed(self, selection):
529                 if len(self._programmingModel) == 0:
530                         return
531
532                 rowIndex = self._get_current_row()
533                 path = (rowIndex, )
534                 if not selection.path_is_selected(path):
535                         # Undo the user's changing of the selection
536                         selection.select_path(path)
537
538
539 gobject.type_register(RadioWindow)
540
541
542 class ListWindow(BasicWindow):
543
544         def __init__(self, player, store, node):
545                 BasicWindow.__init__(self, player, store)
546                 self._node = node
547
548                 self._loadingBanner = banners.GenericBanner()
549
550                 modelTypes, columns = zip(*self._get_columns())
551
552                 self._model = gtk.ListStore(*modelTypes)
553
554                 self._treeView = gtk.TreeView()
555                 self._treeView.connect("row-activated", self._on_row_activated)
556                 self._treeView.set_headers_visible(False)
557                 self._treeView.set_model(self._model)
558                 for column in columns:
559                         if column is not None:
560                                 self._treeView.append_column(column)
561
562                 self._treeScroller = gtk.ScrolledWindow()
563                 self._treeScroller.add(self._treeView)
564                 self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
565
566                 self._separator = gtk.HSeparator()
567                 self._playcontrol = playcontrol.NavControl(self._player, self._store)
568                 self._playcontrol.connect("home", self._on_home)
569                 self._playcontrol.connect("jump-to", self._on_jump)
570
571                 self._contentLayout = gtk.VBox(False)
572                 self._contentLayout.pack_start(self._treeScroller, True, True)
573                 self._contentLayout.pack_start(self._separator, False, True)
574                 self._contentLayout.pack_start(self._playcontrol.toplevel, False, True)
575
576                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
577                 self._layout.pack_start(self._contentLayout, True, True)
578
579         def show(self):
580                 BasicWindow.show(self)
581
582                 self._errorBanner.toplevel.hide()
583                 self._loadingBanner.toplevel.hide()
584
585                 self._refresh()
586                 self._playcontrol.refresh()
587
588         @classmethod
589         def _get_columns(cls):
590                 raise NotImplementedError("")
591
592         def _get_current_row(self):
593                 if self._player.node is None:
594                         return -1
595                 ancestors, current, descendants = stream_index.common_paths(self._player.node, self._node)
596                 if not descendants:
597                         return -1
598                 activeChild = descendants[0]
599                 for i, row in enumerate(self._model):
600                         if activeChild is row[0]:
601                                 return i
602                 else:
603                         return -1
604
605         def jump_to(self, node):
606                 ancestors, current, descendants = stream_index.common_paths(node, self._node)
607                 if current is None:
608                         raise RuntimeError("Cannot jump to node %s" % node)
609                 if not descendants:
610                         _moduleLogger.info("Current node is the target")
611                         return
612                 child = descendants[0]
613                 window = self._window_from_node(child)
614                 window.jump_to(node)
615
616         def _window_from_node(self, node):
617                 raise NotImplementedError("")
618
619         @misc_utils.log_exception(_moduleLogger)
620         def _on_row_activated(self, view, path, column):
621                 raise NotImplementedError("")
622
623         @misc_utils.log_exception(_moduleLogger)
624         def _on_jump(self, source, node):
625                 ancestors, current, descendants = stream_index.common_paths(node, self._node)
626                 if current is None:
627                         _moduleLogger.info("%s is not the target, moving up" % self._node)
628                         self.emit("jump-to", node)
629                         self._window.destroy()
630                         return
631                 if not descendants:
632                         _moduleLogger.info("Current node is the target")
633                         return
634                 child = descendants[0]
635                 window = self._window_from_node(child)
636                 window.jump_to(node)
637
638         def _show_loading(self):
639                 animationPath = self._store.STORE_LOOKUP["loading"]
640                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
641                 self._loadingBanner.show(animation, "Loading...")
642
643         def _hide_loading(self):
644                 self._loadingBanner.hide()
645
646         def _refresh(self):
647                 self._show_loading()
648                 self._model.clear()
649
650         def _select_row(self):
651                 path = (self._get_current_row(), )
652                 self._treeView.scroll_to_cell(path)
653                 self._treeView.get_selection().select_path(path)
654
655
656 class ConferencesWindow(ListWindow):
657
658         def __init__(self, player, store, node):
659                 ListWindow.__init__(self, player, store, node)
660                 self._window.set_title(self._node.title)
661
662         @classmethod
663         def _get_columns(cls):
664                 yield gobject.TYPE_PYOBJECT, None
665
666                 textrenderer = gtk.CellRendererText()
667                 column = gtk.TreeViewColumn("Date")
668                 column.pack_start(textrenderer, expand=True)
669                 column.add_attribute(textrenderer, "text", 1)
670                 yield gobject.TYPE_STRING, column
671
672                 textrenderer = gtk.CellRendererText()
673                 column = gtk.TreeViewColumn("Conference")
674                 column.pack_start(textrenderer, expand=True)
675                 column.add_attribute(textrenderer, "text", 2)
676                 yield gobject.TYPE_STRING, column
677
678         def _refresh(self):
679                 ListWindow._refresh(self)
680                 self._node.get_children(
681                         self._on_conferences,
682                         self._on_error,
683                 )
684
685         @misc_utils.log_exception(_moduleLogger)
686         def _on_conferences(self, programs):
687                 if self._isDestroyed:
688                         _moduleLogger.info("Download complete but window destroyed")
689                         return
690
691                 self._hide_loading()
692                 for programNode in programs:
693                         program = programNode.get_properties()
694                         row = programNode, program["title"], program["full_title"]
695                         self._model.append(row)
696
697                 path = (self._get_current_row(), )
698                 self._treeView.scroll_to_cell(path)
699                 self._treeView.get_selection().select_path(path)
700
701         @misc_utils.log_exception(_moduleLogger)
702         def _on_error(self, exception):
703                 self._hide_loading()
704                 self._errorBanner.push_message(str(exception))
705
706         def _window_from_node(self, node):
707                 sessionsWindow = ConferenceSessionsWindow(self._player, self._store, node)
708                 sessionsWindow.window.set_modal(True)
709                 sessionsWindow.window.set_transient_for(self._window)
710                 sessionsWindow.window.set_default_size(*self._window.get_size())
711                 sessionsWindow.connect("quit", self._on_quit)
712                 sessionsWindow.connect("home", self._on_home)
713                 sessionsWindow.connect("jump-to", self._on_jump)
714                 sessionsWindow.show()
715                 return sessionsWindow
716
717         @misc_utils.log_exception(_moduleLogger)
718         def _on_row_activated(self, view, path, column):
719                 itr = self._model.get_iter(path)
720                 node = self._model.get_value(itr, 0)
721                 self._window_from_node(node)
722
723
724 gobject.type_register(ConferencesWindow)
725
726
727 class ConferenceSessionsWindow(ListWindow):
728
729         def __init__(self, player, store, node):
730                 ListWindow.__init__(self, player, store, node)
731                 self._window.set_title(self._node.title)
732
733         @classmethod
734         def _get_columns(cls):
735                 yield gobject.TYPE_PYOBJECT, None
736
737                 textrenderer = gtk.CellRendererText()
738                 column = gtk.TreeViewColumn("Session")
739                 column.pack_start(textrenderer, expand=True)
740                 column.add_attribute(textrenderer, "text", 1)
741                 yield gobject.TYPE_STRING, column
742
743         def _refresh(self):
744                 ListWindow._refresh(self)
745                 self._node.get_children(
746                         self._on_conference_sessions,
747                         self._on_error,
748                 )
749
750         @misc_utils.log_exception(_moduleLogger)
751         def _on_conference_sessions(self, programs):
752                 if self._isDestroyed:
753                         _moduleLogger.info("Download complete but window destroyed")
754                         return
755
756                 self._hide_loading()
757                 for programNode in programs:
758                         program = programNode.get_properties()
759                         row = programNode, program["title"]
760                         self._model.append(row)
761
762                 path = (self._get_current_row(), )
763                 self._treeView.scroll_to_cell(path)
764                 self._treeView.get_selection().select_path(path)
765
766         @misc_utils.log_exception(_moduleLogger)
767         def _on_error(self, exception):
768                 self._hide_loading()
769                 self._errorBanner.push_message(str(exception))
770
771         def _window_from_node(self, node):
772                 sessionsWindow = ConferenceTalksWindow(self._player, self._store, node)
773                 sessionsWindow.window.set_modal(True)
774                 sessionsWindow.window.set_transient_for(self._window)
775                 sessionsWindow.window.set_default_size(*self._window.get_size())
776                 sessionsWindow.connect("quit", self._on_quit)
777                 sessionsWindow.connect("home", self._on_home)
778                 sessionsWindow.connect("jump-to", self._on_jump)
779                 sessionsWindow.show()
780                 return sessionsWindow
781
782         @misc_utils.log_exception(_moduleLogger)
783         def _on_row_activated(self, view, path, column):
784                 itr = self._model.get_iter(path)
785                 node = self._model.get_value(itr, 0)
786                 self._window_from_node(node)
787
788
789 gobject.type_register(ConferenceSessionsWindow)
790
791
792 class ConferenceTalksWindow(ListWindow):
793
794         def __init__(self, player, store, node):
795                 ListWindow.__init__(self, player, store, node)
796                 self._window.set_title(self._node.title)
797
798         @classmethod
799         def _get_columns(cls):
800                 yield gobject.TYPE_PYOBJECT, None
801
802                 textrenderer = gtk.CellRendererText()
803                 column = gtk.TreeViewColumn("Talk")
804                 column.pack_start(textrenderer, expand=True)
805                 column.add_attribute(textrenderer, "text", 1)
806                 yield gobject.TYPE_STRING, column
807
808         def _refresh(self):
809                 ListWindow._refresh(self)
810                 self._node.get_children(
811                         self._on_conference_talks,
812                         self._on_error,
813                 )
814
815         @misc_utils.log_exception(_moduleLogger)
816         def _on_conference_talks(self, programs):
817                 if self._isDestroyed:
818                         _moduleLogger.info("Download complete but window destroyed")
819                         return
820
821                 self._hide_loading()
822                 for programNode in programs:
823                         program = programNode.get_properties()
824                         row = programNode, "%s\n%s" % (program["title"], program["speaker"])
825                         self._model.append(row)
826
827                 path = (self._get_current_row(), )
828                 self._treeView.scroll_to_cell(path)
829                 self._treeView.get_selection().select_path(path)
830
831         @misc_utils.log_exception(_moduleLogger)
832         def _on_error(self, exception):
833                 self._hide_loading()
834                 self._errorBanner.push_message(str(exception))
835
836         def _window_from_node(self, node):
837                 sessionsWindow = ConferenceTalkWindow(self._player, self._store, node)
838                 sessionsWindow.window.set_modal(True)
839                 sessionsWindow.window.set_transient_for(self._window)
840                 sessionsWindow.window.set_default_size(*self._window.get_size())
841                 sessionsWindow.connect("quit", self._on_quit)
842                 sessionsWindow.connect("home", self._on_home)
843                 sessionsWindow.connect("jump-to", self._on_jump)
844                 sessionsWindow.show()
845                 return sessionsWindow
846
847         @misc_utils.log_exception(_moduleLogger)
848         def _on_row_activated(self, view, path, column):
849                 itr = self._model.get_iter(path)
850                 node = self._model.get_value(itr, 0)
851                 self._window_from_node(node)
852
853
854 gobject.type_register(ConferenceTalksWindow)
855
856
857 class ConferenceTalkWindow(BasicWindow):
858
859         def __init__(self, player, store, node):
860                 BasicWindow.__init__(self, player, store)
861                 self._node = node
862                 self._playerNode = self._player.node
863
864                 self.connect_auto(self._player, "state-change", self._on_player_state_change)
865                 self.connect_auto(self._player, "title-change", self._on_player_title_change)
866                 self.connect_auto(self._player, "error", self._on_player_error)
867
868                 self._loadingBanner = banners.GenericBanner()
869
870                 self._presenter = presenter.StreamPresenter(self._store)
871                 self._presenter.set_context(
872                         self._store.STORE_LOOKUP["conference_background"],
873                         self._node.title,
874                         self._node.subtitle,
875                 )
876                 self._presenterNavigation = presenter.NavigationBox()
877                 self._presenterNavigation.toplevel.add(self._presenter.toplevel)
878                 self._presenterNavigation.connect("action", self._on_nav_action)
879                 self._presenterNavigation.connect("navigating", self._on_navigating)
880
881                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
882                 self._layout.pack_start(self._presenterNavigation.toplevel, True, True)
883
884                 self._window.set_title(self._node.title)
885
886         def show(self):
887                 BasicWindow.show(self)
888                 self._window.show_all()
889                 self._errorBanner.toplevel.hide()
890                 self._loadingBanner.toplevel.hide()
891                 self._set_context(self._player.state)
892
893         def jump_to(self, node):
894                 assert self._node is node
895
896         @property
897         def _active(self):
898                 return self._playerNode is self._node
899
900         def _show_loading(self):
901                 animationPath = self._store.STORE_LOOKUP["loading"]
902                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
903                 self._loadingBanner.show(animation, "Loading...")
904
905         def _hide_loading(self):
906                 self._loadingBanner.hide()
907
908         def _set_context(self, state):
909                 if state == self._player.STATE_PLAY:
910                         if self._active:
911                                 self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
912                         else:
913                                 self._presenter.set_state(self._store.STORE_LOOKUP["play"])
914                 elif state == self._player.STATE_PAUSE:
915                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
916                 elif state == self._player.STATE_STOP:
917                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
918                 else:
919                         _moduleLogger.info("Unhandled player state %s" % state)
920
921         @misc_utils.log_exception(_moduleLogger)
922         def _on_player_state_change(self, player, newState):
923                 if self._presenterNavigation.is_active():
924                         return
925
926                 self._set_context(newState)
927
928         @misc_utils.log_exception(_moduleLogger)
929         def _on_player_title_change(self, player, node):
930                 if not self._active or node in [None, self._node]:
931                         self._playerNode = player.node
932                         return
933                 self._playerNode = player.node
934                 self.emit("jump-to", node)
935                 self._window.destroy()
936
937         @misc_utils.log_exception(_moduleLogger)
938         def _on_player_error(self, player, err, debug):
939                 _moduleLogger.error("%r - %r" % (err, debug))
940
941         @misc_utils.log_exception(_moduleLogger)
942         def _on_navigating(self, widget, navState):
943                 if navState == "clicking":
944                         if self._player.state == self._player.STATE_PLAY:
945                                 if self._active:
946                                         imageName = "pause_pressed"
947                                 else:
948                                         imageName = "play_pressed"
949                         elif self._player.state == self._player.STATE_PAUSE:
950                                 imageName = "play_pressed"
951                         elif self._player.state == self._player.STATE_STOP:
952                                 imageName = "play_pressed"
953                         else:
954                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
955                 elif navState == "down":
956                         imageName = "home"
957                 elif navState == "up":
958                         if self._player.state == self._player.STATE_PLAY:
959                                 if self._active:
960                                         imageName = "pause"
961                                 else:
962                                         imageName = "play"
963                         elif self._player.state == self._player.STATE_PAUSE:
964                                 imageName = "play"
965                         elif self._player.state == self._player.STATE_STOP:
966                                 imageName = "play"
967                         else:
968                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
969                 elif navState == "left":
970                         imageName = "next"
971                 elif navState == "right":
972                         imageName = "prev"
973
974                 self._presenter.set_state(self._store.STORE_LOOKUP[imageName])
975
976         @misc_utils.log_exception(_moduleLogger)
977         def _on_nav_action(self, widget, navState):
978                 self._set_context(self._player.state)
979
980                 if navState == "clicking":
981                         if self._player.state == self._player.STATE_PLAY:
982                                 if self._active:
983                                         self._player.pause()
984                                 else:
985                                         self._player.set_piece_by_node(self._node)
986                                         self._player.play()
987                         elif self._player.state == self._player.STATE_PAUSE:
988                                 self._player.play()
989                         elif self._player.state == self._player.STATE_STOP:
990                                 self._player.set_piece_by_node(self._node)
991                                 self._player.play()
992                         else:
993                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
994                 elif navState == "down":
995                         self.emit("home")
996                         self._window.destroy()
997                 elif navState == "up":
998                         pass
999                 elif navState == "left":
1000                         if self._active:
1001                                 self._player.next()
1002                         else:
1003                                 pass # @todo Not Implemented
1004                 elif navState == "right":
1005                         if self._active:
1006                                 self._player.back()
1007                         else:
1008                                 pass # @todo Not Implemented
1009
1010
1011 gobject.type_register(ConferenceTalkWindow)