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