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