Integrating and testing of playback
[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["stop"])
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 = "stop"
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.stop()
433                         elif self._player.state == self._player.STATE_PAUSE:
434                                 self._player.play()
435                         elif self._player.state == self._player.STATE_STOP:
436                                 self._player.set_piece_by_node(self._childNode)
437                                 self._player.play()
438                         else:
439                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
440                 elif navState == "down":
441                         self.window.destroy()
442                 elif navState == "up":
443                         pass
444                 elif navState == "left":
445                         self._dateShown += datetime.timedelta(days=1)
446                         self._refresh()
447                 elif navState == "right":
448                         self._dateShown -= datetime.timedelta(days=1)
449                         self._refresh()
450
451         @misc_utils.log_exception(_moduleLogger)
452         def _on_channels(self, channels):
453                 if self._isDestroyed:
454                         _moduleLogger.info("Download complete but window destroyed")
455                         return
456
457                 channels = channels
458                 if 1 < len(channels):
459                         _moduleLogger.warning("More channels now available!")
460                 self._childNode = channels[0]
461                 self._childNode.get_programming(
462                         self._dateShown,
463                         self._on_channel,
464                         self._on_load_error,
465                 )
466
467         @misc_utils.log_exception(_moduleLogger)
468         def _on_channel(self, programs):
469                 if self._isDestroyed:
470                         _moduleLogger.info("Download complete but window destroyed")
471                         return
472
473                 self._hide_loading()
474                 for program in programs:
475                         row = program["time"], program["title"]
476                         self._programmingModel.append(row)
477
478                 path = (self._get_current_row(), )
479                 self._treeView.scroll_to_cell(path)
480                 self._treeView.get_selection().select_path(path)
481
482         @misc_utils.log_exception(_moduleLogger)
483         def _on_load_error(self, exception):
484                 self._hide_loading()
485                 self._errorBanner.push_message(str(exception))
486
487         @misc_utils.log_exception(_moduleLogger)
488         def _on_row_changed(self, selection):
489                 if len(self._programmingModel) == 0:
490                         return
491
492                 rowIndex = self._get_current_row()
493                 path = (rowIndex, )
494                 if not selection.path_is_selected(path):
495                         # Undo the user's changing of the selection
496                         selection.select_path(path)
497
498
499 gobject.type_register(RadioWindow)
500
501
502 class ListWindow(BasicWindow):
503
504         def __init__(self, player, store, node):
505                 BasicWindow.__init__(self, player, store)
506                 self._node = node
507
508                 self._loadingBanner = banners.GenericBanner()
509
510                 modelTypes, columns = zip(*self._get_columns())
511
512                 self._model = gtk.ListStore(*modelTypes)
513
514                 self._treeView = gtk.TreeView()
515                 self._treeView.connect("row-activated", self._on_row_activated)
516                 self._treeView.set_headers_visible(False)
517                 self._treeView.set_model(self._model)
518                 for column in columns:
519                         if column is not None:
520                                 self._treeView.append_column(column)
521
522                 self._treeScroller = gtk.ScrolledWindow()
523                 self._treeScroller.add(self._treeView)
524                 self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
525
526                 self._separator = gtk.HSeparator()
527                 self._playcontrol = playcontrol.NavControl(self._player, self._store)
528                 self._playcontrol.connect("home", self._on_home)
529                 self._playcontrol.connect("jump-to", self._on_jump)
530
531                 self._contentLayout = gtk.VBox(False)
532                 self._contentLayout.pack_start(self._treeScroller, True, True)
533                 self._contentLayout.pack_start(self._separator, False, True)
534                 self._contentLayout.pack_start(self._playcontrol.toplevel, False, True)
535
536                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
537                 self._layout.pack_start(self._contentLayout, True, True)
538
539         def show(self):
540                 BasicWindow.show(self)
541
542                 self._errorBanner.toplevel.hide()
543                 self._loadingBanner.toplevel.hide()
544
545                 self._refresh()
546                 self._playcontrol.refresh()
547
548         @classmethod
549         def _get_columns(cls):
550                 raise NotImplementedError("")
551
552         def _get_current_row(self):
553                 raise NotImplementedError("")
554
555         @misc_utils.log_exception(_moduleLogger)
556         def _on_row_activated(self, view, path, column):
557                 raise NotImplementedError("")
558
559         def _show_loading(self):
560                 animationPath = self._store.STORE_LOOKUP["loading"]
561                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
562                 self._loadingBanner.show(animation, "Loading...")
563
564         def _hide_loading(self):
565                 self._loadingBanner.hide()
566
567         def _refresh(self):
568                 self._show_loading()
569                 self._model.clear()
570
571         def _select_row(self):
572                 path = (self._get_current_row(), )
573                 self._treeView.scroll_to_cell(path)
574                 self._treeView.get_selection().select_path(path)
575
576
577 class ConferencesWindow(ListWindow):
578
579         def __init__(self, player, store, node):
580                 ListWindow.__init__(self, player, store, node)
581                 self._window.set_title(self._node.title)
582
583         @classmethod
584         def _get_columns(cls):
585                 yield gobject.TYPE_PYOBJECT, None
586
587                 textrenderer = gtk.CellRendererText()
588                 column = gtk.TreeViewColumn("Date")
589                 column.pack_start(textrenderer, expand=True)
590                 column.add_attribute(textrenderer, "text", 1)
591                 yield gobject.TYPE_STRING, column
592
593                 textrenderer = gtk.CellRendererText()
594                 column = gtk.TreeViewColumn("Conference")
595                 column.pack_start(textrenderer, expand=True)
596                 column.add_attribute(textrenderer, "text", 2)
597                 yield gobject.TYPE_STRING, column
598
599         def _get_current_row(self):
600                 # @todo Not implemented yet
601                 return 0
602
603         def _refresh(self):
604                 ListWindow._refresh(self)
605                 self._node.get_children(
606                         self._on_conferences,
607                         self._on_error,
608                 )
609
610         @misc_utils.log_exception(_moduleLogger)
611         def _on_conferences(self, programs):
612                 if self._isDestroyed:
613                         _moduleLogger.info("Download complete but window destroyed")
614                         return
615
616                 self._hide_loading()
617                 for programNode in programs:
618                         program = programNode.get_properties()
619                         row = programNode, program["title"], program["full_title"]
620                         self._model.append(row)
621
622                 path = (self._get_current_row(), )
623                 self._treeView.scroll_to_cell(path)
624                 self._treeView.get_selection().select_path(path)
625
626         @misc_utils.log_exception(_moduleLogger)
627         def _on_error(self, exception):
628                 self._hide_loading()
629                 self._errorBanner.push_message(str(exception))
630
631         @misc_utils.log_exception(_moduleLogger)
632         def _on_row_activated(self, view, path, column):
633                 itr = self._model.get_iter(path)
634                 node = self._model.get_value(itr, 0)
635
636                 sessionsWindow = ConferenceSessionsWindow(self._player, self._store, node)
637                 sessionsWindow.window.set_modal(True)
638                 sessionsWindow.window.set_transient_for(self._window)
639                 sessionsWindow.window.set_default_size(*self._window.get_size())
640                 sessionsWindow.connect("quit", self._on_quit)
641                 sessionsWindow.connect("home", self._on_home)
642                 sessionsWindow.show()
643
644
645 gobject.type_register(ConferencesWindow)
646
647
648 class ConferenceSessionsWindow(ListWindow):
649
650         def __init__(self, player, store, node):
651                 ListWindow.__init__(self, player, store, node)
652                 self._window.set_title(self._node.title)
653
654         @classmethod
655         def _get_columns(cls):
656                 yield gobject.TYPE_PYOBJECT, None
657
658                 textrenderer = gtk.CellRendererText()
659                 column = gtk.TreeViewColumn("Session")
660                 column.pack_start(textrenderer, expand=True)
661                 column.add_attribute(textrenderer, "text", 1)
662                 yield gobject.TYPE_STRING, column
663
664         def _get_current_row(self):
665                 # @todo Not implemented yet
666                 return 0
667
668         def _refresh(self):
669                 ListWindow._refresh(self)
670                 self._node.get_children(
671                         self._on_conference_sessions,
672                         self._on_error,
673                 )
674
675         @misc_utils.log_exception(_moduleLogger)
676         def _on_conference_sessions(self, programs):
677                 if self._isDestroyed:
678                         _moduleLogger.info("Download complete but window destroyed")
679                         return
680
681                 self._hide_loading()
682                 for programNode in programs:
683                         program = programNode.get_properties()
684                         row = programNode, program["title"]
685                         self._model.append(row)
686
687                 path = (self._get_current_row(), )
688                 self._treeView.scroll_to_cell(path)
689                 self._treeView.get_selection().select_path(path)
690
691         @misc_utils.log_exception(_moduleLogger)
692         def _on_error(self, exception):
693                 self._hide_loading()
694                 self._errorBanner.push_message(str(exception))
695
696         @misc_utils.log_exception(_moduleLogger)
697         def _on_row_activated(self, view, path, column):
698                 itr = self._model.get_iter(path)
699                 node = self._model.get_value(itr, 0)
700
701                 sessionsWindow = ConferenceTalksWindow(self._player, self._store, node)
702                 sessionsWindow.window.set_modal(True)
703                 sessionsWindow.window.set_transient_for(self._window)
704                 sessionsWindow.window.set_default_size(*self._window.get_size())
705                 sessionsWindow.connect("quit", self._on_quit)
706                 sessionsWindow.connect("home", self._on_home)
707                 sessionsWindow.show()
708
709
710 gobject.type_register(ConferenceSessionsWindow)
711
712
713 class ConferenceTalksWindow(ListWindow):
714
715         def __init__(self, player, store, node):
716                 ListWindow.__init__(self, player, store, node)
717                 self._window.set_title(self._node.title)
718
719         @classmethod
720         def _get_columns(cls):
721                 yield gobject.TYPE_PYOBJECT, None
722
723                 textrenderer = gtk.CellRendererText()
724                 column = gtk.TreeViewColumn("Talk")
725                 column.pack_start(textrenderer, expand=True)
726                 column.add_attribute(textrenderer, "text", 1)
727                 yield gobject.TYPE_STRING, column
728
729         def _get_current_row(self):
730                 # @todo Not implemented yet
731                 return 0
732
733         def _refresh(self):
734                 ListWindow._refresh(self)
735                 self._node.get_children(
736                         self._on_conference_talks,
737                         self._on_error,
738                 )
739
740         @misc_utils.log_exception(_moduleLogger)
741         def _on_conference_talks(self, programs):
742                 if self._isDestroyed:
743                         _moduleLogger.info("Download complete but window destroyed")
744                         return
745
746                 self._hide_loading()
747                 for programNode in programs:
748                         program = programNode.get_properties()
749                         row = programNode, "%s\n%s" % (program["title"], program["speaker"])
750                         self._model.append(row)
751
752                 path = (self._get_current_row(), )
753                 self._treeView.scroll_to_cell(path)
754                 self._treeView.get_selection().select_path(path)
755
756         @misc_utils.log_exception(_moduleLogger)
757         def _on_error(self, exception):
758                 self._hide_loading()
759                 self._errorBanner.push_message(str(exception))
760
761         @misc_utils.log_exception(_moduleLogger)
762         def _on_row_activated(self, view, path, column):
763                 itr = self._model.get_iter(path)
764                 node = self._model.get_value(itr, 0)
765
766                 sessionsWindow = ConferenceTalkWindow(self._player, self._store, node)
767                 sessionsWindow.window.set_modal(True)
768                 sessionsWindow.window.set_transient_for(self._window)
769                 sessionsWindow.window.set_default_size(*self._window.get_size())
770                 sessionsWindow.connect("quit", self._on_quit)
771                 sessionsWindow.connect("home", self._on_home)
772                 sessionsWindow.show()
773
774
775 gobject.type_register(ConferenceTalksWindow)
776
777
778 class ConferenceTalkWindow(BasicWindow):
779
780         def __init__(self, player, store, node):
781                 BasicWindow.__init__(self, player, store)
782                 self._node = node
783
784                 self._player.connect("state-change", self._on_player_state_change)
785                 self._player.connect("title-change", self._on_player_title_change)
786                 self._player.connect("error", self._on_player_error)
787
788                 self._loadingBanner = banners.GenericBanner()
789
790                 self._presenter = presenter.StreamPresenter(self._store)
791                 self._presenterNavigation = presenter.NavigationBox()
792                 self._presenterNavigation.toplevel.add(self._presenter.toplevel)
793                 self._presenterNavigation.connect("action", self._on_nav_action)
794                 self._presenterNavigation.connect("navigating", self._on_navigating)
795
796                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
797                 self._layout.pack_start(self._presenterNavigation.toplevel, True, True)
798
799                 self._window.set_title(self._node.title)
800
801         def show(self):
802                 BasicWindow.show(self)
803                 self._window.show_all()
804                 self._errorBanner.toplevel.hide()
805                 self._loadingBanner.toplevel.hide()
806
807                 self._presenter.set_context(
808                         self._store.STORE_LOOKUP["conference_background"],
809                         self._player.title,
810                         self._player.subtitle,
811                 )
812                 self._set_context(self._player.state)
813
814         @property
815         def _active(self):
816                 return self._player.node is self._node
817
818         def _show_loading(self):
819                 animationPath = self._store.STORE_LOOKUP["loading"]
820                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
821                 self._loadingBanner.show(animation, "Loading...")
822
823         def _hide_loading(self):
824                 self._loadingBanner.hide()
825
826         def _set_context(self, state):
827                 if state == self._player.STATE_PLAY:
828                         if self._active:
829                                 self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
830                         else:
831                                 self._presenter.set_state(self._store.STORE_LOOKUP["stop"])
832                 elif state == self._player.STATE_PAUSE:
833                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
834                 elif state == self._player.STATE_STOP:
835                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
836                 else:
837                         _moduleLogger.info("Unhandled player state %s" % state)
838
839         @misc_utils.log_exception(_moduleLogger)
840         def _on_player_state_change(self, player, newState):
841                 if self._presenterNavigation.is_active():
842                         return
843
844                 self._set_context(newState)
845
846         @misc_utils.log_exception(_moduleLogger)
847         def _on_player_title_change(self, player, node):
848                 if node is not self._node or node is None:
849                         _moduleLogger.info("Player title magically changed to %s" % player.title)
850                         return
851                 self._presenter.set_context(
852                         self._store.STORE_LOOKUP["conference_background"],
853                         self._player.title,
854                         self._player.subtitle,
855                 )
856
857         @misc_utils.log_exception(_moduleLogger)
858         def _on_player_error(self, player, err, debug):
859                 _moduleLogger.error("%r - %r" % (err, debug))
860
861         @misc_utils.log_exception(_moduleLogger)
862         def _on_navigating(self, widget, navState):
863                 if navState == "clicking":
864                         if self._player.state == self._player.STATE_PLAY:
865                                 if self._active:
866                                         imageName = "pause"
867                                 else:
868                                         imageName = "stop"
869                         elif self._player.state == self._player.STATE_PAUSE:
870                                 imageName = "play"
871                         elif self._player.state == self._player.STATE_STOP:
872                                 imageName = "play"
873                         else:
874                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
875                 elif navState == "down":
876                         imageName = "home"
877                 elif navState == "up":
878                         imageName = "play"
879                 elif navState == "left":
880                         imageName = "next"
881                 elif navState == "right":
882                         imageName = "prev"
883
884                 self._presenter.set_state(self._store.STORE_LOOKUP[imageName])
885
886         @misc_utils.log_exception(_moduleLogger)
887         def _on_nav_action(self, widget, navState):
888                 self._set_context(self._player.state)
889
890                 if navState == "clicking":
891                         if self._player.state == self._player.STATE_PLAY:
892                                 if self._active:
893                                         self._player.pause()
894                                 else:
895                                         self._player.stop()
896                         elif self._player.state == self._player.STATE_PAUSE:
897                                 self._player.play()
898                         elif self._player.state == self._player.STATE_STOP:
899                                 self._player.set_piece_by_node(self._node)
900                                 self._player.play()
901                         else:
902                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
903                 elif navState == "down":
904                         self.emit("home")
905                         self._window.destroy()
906                 elif navState == "up":
907                         pass
908                 elif navState == "left":
909                         self._player.next()
910                 elif navState == "right":
911                         self._player.back()
912
913
914 gobject.type_register(ConferenceTalkWindow)