Adding a separator to better distinguish the play controls
[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                 if self._player.state == "play":
314                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
315                 else:
316                         self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
317                 self._presenterNavigation = presenter.NavigationBox()
318                 self._presenterNavigation.toplevel.add(self._presenter.toplevel)
319                 self._presenterNavigation.connect("action", self._on_nav_action)
320                 self._presenterNavigation.connect("navigating", self._on_navigating)
321
322                 self._radioLayout = gtk.VBox(False)
323                 self._radioLayout.pack_start(self._headerNavigation.toplevel, False, False)
324                 self._radioLayout.pack_start(self._treeScroller, True, True)
325                 self._radioLayout.pack_start(self._presenterNavigation.toplevel, False, True)
326
327                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
328                 self._layout.pack_start(self._radioLayout, True, True)
329
330                 self._window.set_title("Radio")
331                 self._dateShown = datetime.datetime.now()
332
333         def show(self):
334                 BasicWindow.show(self)
335
336                 self._errorBanner.toplevel.hide()
337                 self._loadingBanner.toplevel.hide()
338
339                 self._refresh()
340
341         def _show_loading(self):
342                 animationPath = self._store.STORE_LOOKUP["loading"]
343                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
344                 self._loadingBanner.show(animation, "Loading...")
345
346         def _hide_loading(self):
347                 self._loadingBanner.hide()
348
349         def _refresh(self):
350                 self._show_loading()
351                 self._programmingModel.clear()
352                 self._node.get_children(
353                         self._on_channels,
354                         self._on_load_error,
355                 )
356
357         def _get_current_row(self):
358                 nowTime = self._dateShown.strftime("%H:%M:%S")
359                 i = 0
360                 for i, row in enumerate(self._programmingModel):
361                         if nowTime < row[0]:
362                                 if i == 0:
363                                         return 0
364                                 else:
365                                         return i - 1
366                 else:
367                         return i
368
369         @misc_utils.log_exception(_moduleLogger)
370         def _on_player_state_change(self, player, newState):
371                 if self._headerNavigation.is_active() or self._presenterNavigation.is_active():
372                         return
373
374                 if newState == "play":
375                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
376                 elif newState == "pause":
377                         self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
378                 else:
379                         _moduleLogger.info("Unhandled player state %s" % newState)
380                         self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
381
382         @misc_utils.log_exception(_moduleLogger)
383         def _on_player_title_change(self, player, newState):
384                 _moduleLogger.info("Player title magically changed to %s" % player.title)
385                 self._destroy()
386
387         @misc_utils.log_exception(_moduleLogger)
388         def _on_navigating(self, widget, navState):
389                 if navState == "clicking":
390                         if self._player.state == "play":
391                                 imageName = "pause"
392                         else:
393                                 imageName = "play"
394                 elif navState == "down":
395                         imageName = "home"
396                 elif navState == "up":
397                         imageName = "play"
398                 elif navState == "left":
399                         imageName = "play"
400                 elif navState == "right":
401                         imageName = "play"
402
403                 self._presenter.set_state(self._store.STORE_LOOKUP[imageName])
404
405         @misc_utils.log_exception(_moduleLogger)
406         def _on_nav_action(self, widget, navState):
407                 if self._player.state == "play":
408                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
409                 else:
410                         self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
411
412                 if navState == "clicking":
413                         if self._player.state == "play":
414                                 self._player.pause()
415                         else:
416                                 self._player.play()
417                 elif navState == "down":
418                         self.window.destroy()
419                 elif navState == "up":
420                         pass
421                 elif navState == "left":
422                         self._dateShown += datetime.timedelta(days=1)
423                         self._refresh()
424                 elif navState == "right":
425                         self._dateShown -= datetime.timedelta(days=1)
426                         self._refresh()
427
428         @misc_utils.log_exception(_moduleLogger)
429         def _on_channels(self, channels):
430                 if self._isDestroyed:
431                         _moduleLogger.info("Download complete but window destroyed")
432                         return
433
434                 channels = channels
435                 if 1 < len(channels):
436                         _moduleLogger.warning("More channels now available!")
437                 self._childNode = channels[0]
438                 self._childNode.get_programming(
439                         self._dateShown,
440                         self._on_channel,
441                         self._on_load_error,
442                 )
443
444         @misc_utils.log_exception(_moduleLogger)
445         def _on_channel(self, programs):
446                 if self._isDestroyed:
447                         _moduleLogger.info("Download complete but window destroyed")
448                         return
449
450                 self._hide_loading()
451                 for program in programs:
452                         row = program["time"], program["title"]
453                         self._programmingModel.append(row)
454
455                 path = (self._get_current_row(), )
456                 self._treeView.scroll_to_cell(path)
457                 self._treeView.get_selection().select_path(path)
458
459         @misc_utils.log_exception(_moduleLogger)
460         def _on_load_error(self, exception):
461                 self._hide_loading()
462                 self._errorBanner.push_message(str(exception))
463
464         @misc_utils.log_exception(_moduleLogger)
465         def _on_row_changed(self, selection):
466                 if len(self._programmingModel) == 0:
467                         return
468
469                 rowIndex = self._get_current_row()
470                 path = (rowIndex, )
471                 if not selection.path_is_selected(path):
472                         # Undo the user's changing of the selection
473                         selection.select_path(path)
474
475
476 gobject.type_register(RadioWindow)
477
478
479 class ListWindow(BasicWindow):
480
481         def __init__(self, player, store, node):
482                 BasicWindow.__init__(self, player, store)
483                 self._node = node
484
485                 self._loadingBanner = banners.GenericBanner()
486
487                 modelTypes, columns = zip(*self._get_columns())
488
489                 self._model = gtk.ListStore(*modelTypes)
490
491                 self._treeView = gtk.TreeView()
492                 self._treeView.connect("row-activated", self._on_row_activated)
493                 self._treeView.set_headers_visible(False)
494                 self._treeView.set_model(self._model)
495                 for column in columns:
496                         if column is not None:
497                                 self._treeView.append_column(column)
498
499                 self._treeScroller = gtk.ScrolledWindow()
500                 self._treeScroller.add(self._treeView)
501                 self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
502
503                 self._separator = gtk.HSeparator()
504                 self._playcontrol = playcontrol.NavControl(self._player, self._store)
505                 self._playcontrol.connect("home", self._on_home)
506                 self._playcontrol.connect("jump-to", self._on_jump)
507
508                 self._contentLayout = gtk.VBox(False)
509                 self._contentLayout.pack_start(self._treeScroller, True, True)
510                 self._contentLayout.pack_start(self._separator, False, True)
511                 self._contentLayout.pack_start(self._playcontrol.toplevel, False, True)
512
513                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
514                 self._layout.pack_start(self._contentLayout, True, True)
515
516         def show(self):
517                 BasicWindow.show(self)
518
519                 self._errorBanner.toplevel.hide()
520                 self._loadingBanner.toplevel.hide()
521
522                 self._refresh()
523                 self._playcontrol.refresh()
524
525         @classmethod
526         def _get_columns(cls):
527                 raise NotImplementedError("")
528
529         def _get_current_row(self):
530                 raise NotImplementedError("")
531
532         @misc_utils.log_exception(_moduleLogger)
533         def _on_row_activated(self, view, path, column):
534                 raise NotImplementedError("")
535
536         def _show_loading(self):
537                 animationPath = self._store.STORE_LOOKUP["loading"]
538                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
539                 self._loadingBanner.show(animation, "Loading...")
540
541         def _hide_loading(self):
542                 self._loadingBanner.hide()
543
544         def _refresh(self):
545                 self._show_loading()
546                 self._model.clear()
547
548         def _select_row(self):
549                 path = (self._get_current_row(), )
550                 self._treeView.scroll_to_cell(path)
551                 self._treeView.get_selection().select_path(path)
552
553
554 class ConferencesWindow(ListWindow):
555
556         def __init__(self, player, store, node):
557                 ListWindow.__init__(self, player, store, node)
558                 self._window.set_title("Conferences")
559
560         @classmethod
561         def _get_columns(cls):
562                 yield gobject.TYPE_PYOBJECT, None
563
564                 textrenderer = gtk.CellRendererText()
565                 column = gtk.TreeViewColumn("Date")
566                 column.pack_start(textrenderer, expand=True)
567                 column.add_attribute(textrenderer, "text", 1)
568                 yield gobject.TYPE_STRING, column
569
570                 textrenderer = gtk.CellRendererText()
571                 column = gtk.TreeViewColumn("Conference")
572                 column.pack_start(textrenderer, expand=True)
573                 column.add_attribute(textrenderer, "text", 2)
574                 yield gobject.TYPE_STRING, column
575
576         def _get_current_row(self):
577                 # @todo Not implemented yet
578                 return 0
579
580         def _refresh(self):
581                 ListWindow._refresh(self)
582                 self._node.get_children(
583                         self._on_conferences,
584                         self._on_error,
585                 )
586
587         @misc_utils.log_exception(_moduleLogger)
588         def _on_conferences(self, programs):
589                 if self._isDestroyed:
590                         _moduleLogger.info("Download complete but window destroyed")
591                         return
592
593                 self._hide_loading()
594                 for programNode in programs:
595                         program = programNode.get_properties()
596                         row = programNode, program["title"], program["full_title"]
597                         self._model.append(row)
598
599                 path = (self._get_current_row(), )
600                 self._treeView.scroll_to_cell(path)
601                 self._treeView.get_selection().select_path(path)
602
603         @misc_utils.log_exception(_moduleLogger)
604         def _on_error(self, exception):
605                 self._hide_loading()
606                 self._errorBanner.push_message(str(exception))
607
608         @misc_utils.log_exception(_moduleLogger)
609         def _on_row_activated(self, view, path, column):
610                 itr = self._model.get_iter(path)
611                 node = self._model.get_value(itr, 0)
612
613                 sessionsWindow = ConferenceSessionsWindow(self._player, self._store, node)
614                 sessionsWindow.window.set_modal(True)
615                 sessionsWindow.window.set_transient_for(self._window)
616                 sessionsWindow.window.set_default_size(*self._window.get_size())
617                 sessionsWindow.connect("quit", self._on_quit)
618                 sessionsWindow.connect("home", self._on_home)
619                 sessionsWindow.show()
620
621
622 gobject.type_register(ConferencesWindow)
623
624
625 class ConferenceSessionsWindow(ListWindow):
626
627         def __init__(self, player, store, node):
628                 ListWindow.__init__(self, player, store, node)
629                 self._window.set_title("Sessions")
630
631         @classmethod
632         def _get_columns(cls):
633                 yield gobject.TYPE_PYOBJECT, None
634
635                 textrenderer = gtk.CellRendererText()
636                 column = gtk.TreeViewColumn("Session")
637                 column.pack_start(textrenderer, expand=True)
638                 column.add_attribute(textrenderer, "text", 1)
639                 yield gobject.TYPE_STRING, column
640
641         def _get_current_row(self):
642                 # @todo Not implemented yet
643                 return 0
644
645         def _refresh(self):
646                 ListWindow._refresh(self)
647                 self._node.get_children(
648                         self._on_conference_sessions,
649                         self._on_error,
650                 )
651
652         @misc_utils.log_exception(_moduleLogger)
653         def _on_conference_sessions(self, programs):
654                 if self._isDestroyed:
655                         _moduleLogger.info("Download complete but window destroyed")
656                         return
657
658                 self._hide_loading()
659                 for programNode in programs:
660                         program = programNode.get_properties()
661                         row = programNode, program["title"]
662                         self._model.append(row)
663
664                 path = (self._get_current_row(), )
665                 self._treeView.scroll_to_cell(path)
666                 self._treeView.get_selection().select_path(path)
667
668         @misc_utils.log_exception(_moduleLogger)
669         def _on_error(self, exception):
670                 self._hide_loading()
671                 self._errorBanner.push_message(str(exception))
672
673         @misc_utils.log_exception(_moduleLogger)
674         def _on_row_activated(self, view, path, column):
675                 itr = self._model.get_iter(path)
676                 node = self._model.get_value(itr, 0)
677
678                 sessionsWindow = ConferenceTalksWindow(self._player, self._store, node)
679                 sessionsWindow.window.set_modal(True)
680                 sessionsWindow.window.set_transient_for(self._window)
681                 sessionsWindow.window.set_default_size(*self._window.get_size())
682                 sessionsWindow.connect("quit", self._on_quit)
683                 sessionsWindow.connect("home", self._on_home)
684                 sessionsWindow.show()
685
686
687 gobject.type_register(ConferenceSessionsWindow)
688
689
690 class ConferenceTalksWindow(ListWindow):
691
692         def __init__(self, player, store, node):
693                 ListWindow.__init__(self, player, store, node)
694                 self._window.set_title("Talks")
695
696         @classmethod
697         def _get_columns(cls):
698                 yield gobject.TYPE_PYOBJECT, None
699
700                 textrenderer = gtk.CellRendererText()
701                 column = gtk.TreeViewColumn("Talk")
702                 column.pack_start(textrenderer, expand=True)
703                 column.add_attribute(textrenderer, "text", 1)
704                 yield gobject.TYPE_STRING, column
705
706         def _get_current_row(self):
707                 # @todo Not implemented yet
708                 return 0
709
710         def _refresh(self):
711                 ListWindow._refresh(self)
712                 self._node.get_children(
713                         self._on_conference_talks,
714                         self._on_error,
715                 )
716
717         @misc_utils.log_exception(_moduleLogger)
718         def _on_conference_talks(self, programs):
719                 if self._isDestroyed:
720                         _moduleLogger.info("Download complete but window destroyed")
721                         return
722
723                 self._hide_loading()
724                 for programNode in programs:
725                         program = programNode.get_properties()
726                         row = programNode, "%s\n%s" % (program["title"], program["speaker"])
727                         self._model.append(row)
728
729                 path = (self._get_current_row(), )
730                 self._treeView.scroll_to_cell(path)
731                 self._treeView.get_selection().select_path(path)
732
733         @misc_utils.log_exception(_moduleLogger)
734         def _on_error(self, exception):
735                 self._hide_loading()
736                 self._errorBanner.push_message(str(exception))
737
738         @misc_utils.log_exception(_moduleLogger)
739         def _on_row_activated(self, view, path, column):
740                 itr = self._model.get_iter(path)
741                 node = self._model.get_value(itr, 0)
742
743                 sessionsWindow = ConferenceTalkWindow(self._player, self._store, node)
744                 sessionsWindow.window.set_modal(True)
745                 sessionsWindow.window.set_transient_for(self._window)
746                 sessionsWindow.window.set_default_size(*self._window.get_size())
747                 sessionsWindow.connect("quit", self._on_quit)
748                 sessionsWindow.connect("home", self._on_home)
749                 sessionsWindow.show()
750
751
752 gobject.type_register(ConferenceTalksWindow)
753
754
755 class ConferenceTalkWindow(BasicWindow):
756
757         def __init__(self, player, store, node):
758                 BasicWindow.__init__(self, player, store)
759
760                 self._player.connect("state-change", self._on_player_state_change)
761                 self._player.connect("title-change", self._on_player_title_change)
762
763                 self._loadingBanner = banners.GenericBanner()
764
765                 self._presenter = presenter.StreamPresenter(self._store)
766                 self._presenterNavigation = presenter.NavigationBox()
767                 self._presenterNavigation.toplevel.add(self._presenter.toplevel)
768                 self._presenterNavigation.connect("action", self._on_nav_action)
769                 self._presenterNavigation.connect("navigating", self._on_navigating)
770
771                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
772                 self._layout.pack_start(self._presenterNavigation.toplevel, True, True)
773
774                 self._window.set_title("Talk")
775
776         def show(self):
777                 BasicWindow.show(self)
778                 self._window.show_all()
779                 self._errorBanner.toplevel.hide()
780                 self._loadingBanner.toplevel.hide()
781
782                 self._presenter.set_context(
783                         self._store.STORE_LOOKUP["conference_background"],
784                         self._player.title,
785                         self._player.subtitle,
786                 )
787                 if self._player.state == "play":
788                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
789                 else:
790                         self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
791
792         def _show_loading(self):
793                 animationPath = self._store.STORE_LOOKUP["loading"]
794                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
795                 self._loadingBanner.show(animation, "Loading...")
796
797         def _hide_loading(self):
798                 self._loadingBanner.hide()
799
800         @misc_utils.log_exception(_moduleLogger)
801         def _on_player_state_change(self, player, newState):
802                 if self._presenterNavigation.is_active():
803                         return
804
805                 if newState == "play":
806                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
807                 elif newState == "pause":
808                         self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
809                 else:
810                         _moduleLogger.info("Unhandled player state %s" % newState)
811                         self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
812
813         @misc_utils.log_exception(_moduleLogger)
814         def _on_player_title_change(self, player, newState):
815                 self._presenter.set_context(
816                         self._store.STORE_LOOKUP["conference_background"],
817                         self._player.title,
818                         self._player.subtitle,
819                 )
820
821         @misc_utils.log_exception(_moduleLogger)
822         def _on_navigating(self, widget, navState):
823                 if navState == "clicking":
824                         if self._player.state == "play":
825                                 imageName = "pause"
826                         else:
827                                 imageName = "play"
828                 elif navState == "down":
829                         imageName = "home"
830                 elif navState == "up":
831                         imageName = "play"
832                 elif navState == "left":
833                         imageName = "next"
834                 elif navState == "right":
835                         imageName = "prev"
836
837                 self._presenter.set_state(self._store.STORE_LOOKUP[imageName])
838
839         @misc_utils.log_exception(_moduleLogger)
840         def _on_nav_action(self, widget, navState):
841                 if self._player.state == "play":
842                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
843                 else:
844                         self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
845
846                 if navState == "clicking":
847                         if self._player.state == "play":
848                                 self._player.pause()
849                         else:
850                                 self._player.play()
851                 elif navState == "down":
852                         self.emit("home")
853                         self._window.destroy()
854                 elif navState == "up":
855                         pass
856                 elif navState == "left":
857                         self._player.next()
858                 elif navState == "right":
859                         self._player.back()
860
861
862 gobject.type_register(ConferenceTalkWindow)