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