a7ec704c48b8718d7a06b9a7e50d097cf284ac89
[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._dateShown = datetime.datetime.now()
354                 self._update_title()
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         def _update_title(self):
368                 self._window.set_title("%s - %s" % (self._node.title, self._dateShown.strftime("%m/%d")))
369
370         @property
371         def _active(self):
372                 return self._player.node is self._childNode
373
374         def _set_context(self, state):
375                 if state == self._player.STATE_PLAY:
376                         if self._active:
377                                 self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
378                         else:
379                                 self._presenter.set_state(self._store.STORE_LOOKUP["play"])
380                 elif state == self._player.STATE_PAUSE:
381                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
382                 elif state == self._player.STATE_STOP:
383                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
384                 else:
385                         _moduleLogger.info("Unhandled player state %s" % state)
386                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
387
388         def _show_loading(self):
389                 animationPath = self._store.STORE_LOOKUP["loading"]
390                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
391                 self._loadingBanner.show(animation, "Loading...")
392
393         def _hide_loading(self):
394                 self._loadingBanner.hide()
395
396         def _refresh(self):
397                 self._show_loading()
398                 self._programmingModel.clear()
399                 self._node.get_children(
400                         self._on_channels,
401                         self._on_load_error,
402                 )
403                 self._set_context(self._player.state)
404
405         def _get_current_row(self):
406                 nowTime = self._dateShown.strftime("%H:%M:%S")
407                 i = 0
408                 for i, row in enumerate(self._programmingModel):
409                         if nowTime < row[0]:
410                                 if i == 0:
411                                         return 0
412                                 else:
413                                         return i - 1
414                 else:
415                         return i
416
417         @misc_utils.log_exception(_moduleLogger)
418         def _on_player_state_change(self, player, newState):
419                 if self._headerNavigation.is_active() or self._presenterNavigation.is_active():
420                         return
421
422                 self._set_context(newState)
423
424         @misc_utils.log_exception(_moduleLogger)
425         def _on_player_title_change(self, player, node):
426                 if node is not self._childNode or node is None:
427                         _moduleLogger.info("Player title magically changed to %s" % player.title)
428                         return
429
430         @misc_utils.log_exception(_moduleLogger)
431         def _on_navigating(self, widget, navState):
432                 if navState == "clicking":
433                         if self._player.state == self._player.STATE_PLAY:
434                                 if self._active:
435                                         imageName = "pause_pressed"
436                                 else:
437                                         imageName = "play_pressed"
438                         elif self._player.state == self._player.STATE_PAUSE:
439                                 imageName = "play_pressed"
440                         elif self._player.state == self._player.STATE_STOP:
441                                 imageName = "play_pressed"
442                         else:
443                                 imageName = "play_pressed"
444                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
445                 elif navState == "down":
446                         imageName = "home"
447                 elif navState == "up":
448                         imageName = "play"
449                 elif navState == "left":
450                         imageName = "play"
451                 elif navState == "right":
452                         imageName = "play"
453
454                 self._presenter.set_state(self._store.STORE_LOOKUP[imageName])
455
456         @misc_utils.log_exception(_moduleLogger)
457         def _on_nav_action(self, widget, navState):
458                 self._set_context(self._player.state)
459
460                 if navState == "clicking":
461                         if self._player.state == self._player.STATE_PLAY:
462                                 if self._active:
463                                         self._player.pause()
464                                 else:
465                                         self._player.set_piece_by_node(self._childNode)
466                                         self._player.play()
467                         elif self._player.state == self._player.STATE_PAUSE:
468                                 self._player.play()
469                         elif self._player.state == self._player.STATE_STOP:
470                                 self._player.set_piece_by_node(self._childNode)
471                                 self._player.play()
472                         else:
473                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
474                 elif navState == "down":
475                         self.window.destroy()
476                 elif navState == "up":
477                         pass
478                 elif navState == "left":
479                         self._dateShown += datetime.timedelta(days=1)
480                         self._update_title()
481                         self._refresh()
482                 elif navState == "right":
483                         self._dateShown -= datetime.timedelta(days=1)
484                         self._update_title()
485                         self._refresh()
486
487         @misc_utils.log_exception(_moduleLogger)
488         def _on_channels(self, channels):
489                 if self._isDestroyed:
490                         _moduleLogger.info("Download complete but window destroyed")
491                         return
492
493                 channels = channels
494                 if 1 < len(channels):
495                         _moduleLogger.warning("More channels now available!")
496                 self._childNode = channels[0]
497                 self._childNode.get_programming(
498                         self._dateShown,
499                         self._on_channel,
500                         self._on_load_error,
501                 )
502
503         @misc_utils.log_exception(_moduleLogger)
504         def _on_channel(self, programs):
505                 if self._isDestroyed:
506                         _moduleLogger.info("Download complete but window destroyed")
507                         return
508
509                 self._hide_loading()
510                 for program in programs:
511                         row = program["time"], program["title"]
512                         self._programmingModel.append(row)
513
514                 currentDate = datetime.datetime.now()
515                 if currentDate.date() != self._dateShown.date():
516                         self._treeView.get_selection().set_mode(gtk.SELECTION_NONE)
517                 else:
518                         self._treeView.get_selection().set_mode(gtk.SELECTION_SINGLE)
519                         path = (self._get_current_row(), )
520                         self._treeView.scroll_to_cell(path)
521                         self._treeView.get_selection().select_path(path)
522
523         @misc_utils.log_exception(_moduleLogger)
524         def _on_load_error(self, exception):
525                 self._hide_loading()
526                 self._errorBanner.push_message(str(exception))
527
528         @misc_utils.log_exception(_moduleLogger)
529         def _on_row_changed(self, selection):
530                 if len(self._programmingModel) == 0:
531                         return
532
533                 rowIndex = self._get_current_row()
534                 path = (rowIndex, )
535                 if not selection.path_is_selected(path):
536                         # Undo the user's changing of the selection
537                         selection.select_path(path)
538
539
540 gobject.type_register(RadioWindow)
541
542
543 class ListWindow(BasicWindow):
544
545         def __init__(self, player, store, node):
546                 BasicWindow.__init__(self, player, store)
547                 self._node = node
548
549                 self._loadingBanner = banners.GenericBanner()
550
551                 modelTypes, columns = zip(*self._get_columns())
552
553                 self._model = gtk.ListStore(*modelTypes)
554
555                 self._treeView = gtk.TreeView()
556                 self._treeView.connect("row-activated", self._on_row_activated)
557                 self._treeView.set_headers_visible(False)
558                 self._treeView.set_model(self._model)
559                 for column in columns:
560                         if column is not None:
561                                 self._treeView.append_column(column)
562
563                 self._treeScroller = gtk.ScrolledWindow()
564                 self._treeScroller.add(self._treeView)
565                 self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
566
567                 self._separator = gtk.HSeparator()
568                 self._playcontrol = playcontrol.NavControl(self._player, self._store)
569                 self._playcontrol.connect("home", self._on_home)
570                 self._playcontrol.connect("jump-to", self._on_jump)
571
572                 self._contentLayout = gtk.VBox(False)
573                 self._contentLayout.pack_start(self._treeScroller, True, True)
574                 self._contentLayout.pack_start(self._separator, False, True)
575                 self._contentLayout.pack_start(self._playcontrol.toplevel, False, True)
576
577                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
578                 self._layout.pack_start(self._contentLayout, True, True)
579
580         def show(self):
581                 BasicWindow.show(self)
582
583                 self._errorBanner.toplevel.hide()
584                 self._loadingBanner.toplevel.hide()
585
586                 self._refresh()
587                 self._playcontrol.refresh()
588
589         @classmethod
590         def _get_columns(cls):
591                 raise NotImplementedError("")
592
593         def _get_current_row(self):
594                 if self._player.node is None:
595                         return -1
596                 ancestors, current, descendants = stream_index.common_paths(self._player.node, self._node)
597                 if not descendants:
598                         return -1
599                 activeChild = descendants[0]
600                 for i, row in enumerate(self._model):
601                         if activeChild is row[0]:
602                                 return i
603                 else:
604                         return -1
605
606         def jump_to(self, node):
607                 ancestors, current, descendants = stream_index.common_paths(node, self._node)
608                 if current is None:
609                         raise RuntimeError("Cannot jump to node %s" % node)
610                 if not descendants:
611                         _moduleLogger.info("Current node is the target")
612                         return
613                 child = descendants[0]
614                 window = self._window_from_node(child)
615                 window.jump_to(node)
616
617         def _window_from_node(self, node):
618                 raise NotImplementedError("")
619
620         @misc_utils.log_exception(_moduleLogger)
621         def _on_row_activated(self, view, path, column):
622                 raise NotImplementedError("")
623
624         @misc_utils.log_exception(_moduleLogger)
625         def _on_jump(self, source, node):
626                 ancestors, current, descendants = stream_index.common_paths(node, self._node)
627                 if current is None:
628                         _moduleLogger.info("%s is not the target, moving up" % self._node)
629                         self.emit("jump-to", node)
630                         self._window.destroy()
631                         return
632                 if not descendants:
633                         _moduleLogger.info("Current node is the target")
634                         return
635                 child = descendants[0]
636                 window = self._window_from_node(child)
637                 window.jump_to(node)
638
639         def _show_loading(self):
640                 animationPath = self._store.STORE_LOOKUP["loading"]
641                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
642                 self._loadingBanner.show(animation, "Loading...")
643
644         def _hide_loading(self):
645                 self._loadingBanner.hide()
646
647         def _refresh(self):
648                 self._show_loading()
649                 self._model.clear()
650
651         def _select_row(self):
652                 path = (self._get_current_row(), )
653                 self._treeView.scroll_to_cell(path)
654                 self._treeView.get_selection().select_path(path)
655
656
657 class ConferencesWindow(ListWindow):
658
659         def __init__(self, player, store, node):
660                 ListWindow.__init__(self, player, store, node)
661                 self._window.set_title(self._node.title)
662
663         @classmethod
664         def _get_columns(cls):
665                 yield gobject.TYPE_PYOBJECT, None
666
667                 textrenderer = gtk.CellRendererText()
668                 column = gtk.TreeViewColumn("Date")
669                 column.pack_start(textrenderer, expand=True)
670                 column.add_attribute(textrenderer, "text", 1)
671                 yield gobject.TYPE_STRING, column
672
673                 textrenderer = gtk.CellRendererText()
674                 column = gtk.TreeViewColumn("Conference")
675                 column.pack_start(textrenderer, expand=True)
676                 column.add_attribute(textrenderer, "text", 2)
677                 yield gobject.TYPE_STRING, column
678
679         def _refresh(self):
680                 ListWindow._refresh(self)
681                 self._node.get_children(
682                         self._on_conferences,
683                         self._on_error,
684                 )
685
686         @misc_utils.log_exception(_moduleLogger)
687         def _on_conferences(self, programs):
688                 if self._isDestroyed:
689                         _moduleLogger.info("Download complete but window destroyed")
690                         return
691
692                 self._hide_loading()
693                 for programNode in programs:
694                         program = programNode.get_properties()
695                         row = programNode, program["title"], program["full_title"]
696                         self._model.append(row)
697
698                 path = (self._get_current_row(), )
699                 self._treeView.scroll_to_cell(path)
700                 self._treeView.get_selection().select_path(path)
701
702         @misc_utils.log_exception(_moduleLogger)
703         def _on_error(self, exception):
704                 self._hide_loading()
705                 self._errorBanner.push_message(str(exception))
706
707         def _window_from_node(self, node):
708                 sessionsWindow = ConferenceSessionsWindow(self._player, self._store, node)
709                 sessionsWindow.window.set_modal(True)
710                 sessionsWindow.window.set_transient_for(self._window)
711                 sessionsWindow.window.set_default_size(*self._window.get_size())
712                 sessionsWindow.connect("quit", self._on_quit)
713                 sessionsWindow.connect("home", self._on_home)
714                 sessionsWindow.connect("jump-to", self._on_jump)
715                 sessionsWindow.show()
716                 return sessionsWindow
717
718         @misc_utils.log_exception(_moduleLogger)
719         def _on_row_activated(self, view, path, column):
720                 itr = self._model.get_iter(path)
721                 node = self._model.get_value(itr, 0)
722                 self._window_from_node(node)
723
724
725 gobject.type_register(ConferencesWindow)
726
727
728 class ConferenceSessionsWindow(ListWindow):
729
730         def __init__(self, player, store, node):
731                 ListWindow.__init__(self, player, store, node)
732                 self._window.set_title(self._node.title)
733
734         @classmethod
735         def _get_columns(cls):
736                 yield gobject.TYPE_PYOBJECT, None
737
738                 textrenderer = gtk.CellRendererText()
739                 column = gtk.TreeViewColumn("Session")
740                 column.pack_start(textrenderer, expand=True)
741                 column.add_attribute(textrenderer, "text", 1)
742                 yield gobject.TYPE_STRING, column
743
744         def _refresh(self):
745                 ListWindow._refresh(self)
746                 self._node.get_children(
747                         self._on_conference_sessions,
748                         self._on_error,
749                 )
750
751         @misc_utils.log_exception(_moduleLogger)
752         def _on_conference_sessions(self, programs):
753                 if self._isDestroyed:
754                         _moduleLogger.info("Download complete but window destroyed")
755                         return
756
757                 self._hide_loading()
758                 for programNode in programs:
759                         program = programNode.get_properties()
760                         row = programNode, program["title"]
761                         self._model.append(row)
762
763                 path = (self._get_current_row(), )
764                 self._treeView.scroll_to_cell(path)
765                 self._treeView.get_selection().select_path(path)
766
767         @misc_utils.log_exception(_moduleLogger)
768         def _on_error(self, exception):
769                 self._hide_loading()
770                 self._errorBanner.push_message(str(exception))
771
772         def _window_from_node(self, node):
773                 sessionsWindow = ConferenceTalksWindow(self._player, self._store, node)
774                 sessionsWindow.window.set_modal(True)
775                 sessionsWindow.window.set_transient_for(self._window)
776                 sessionsWindow.window.set_default_size(*self._window.get_size())
777                 sessionsWindow.connect("quit", self._on_quit)
778                 sessionsWindow.connect("home", self._on_home)
779                 sessionsWindow.connect("jump-to", self._on_jump)
780                 sessionsWindow.show()
781                 return sessionsWindow
782
783         @misc_utils.log_exception(_moduleLogger)
784         def _on_row_activated(self, view, path, column):
785                 itr = self._model.get_iter(path)
786                 node = self._model.get_value(itr, 0)
787                 self._window_from_node(node)
788
789
790 gobject.type_register(ConferenceSessionsWindow)
791
792
793 class ConferenceTalksWindow(ListWindow):
794
795         def __init__(self, player, store, node):
796                 ListWindow.__init__(self, player, store, node)
797                 self._window.set_title(self._node.title)
798
799         @classmethod
800         def _get_columns(cls):
801                 yield gobject.TYPE_PYOBJECT, None
802
803                 textrenderer = gtk.CellRendererText()
804                 column = gtk.TreeViewColumn("Talk")
805                 column.pack_start(textrenderer, expand=True)
806                 column.add_attribute(textrenderer, "text", 1)
807                 yield gobject.TYPE_STRING, column
808
809         def _refresh(self):
810                 ListWindow._refresh(self)
811                 self._node.get_children(
812                         self._on_conference_talks,
813                         self._on_error,
814                 )
815
816         @misc_utils.log_exception(_moduleLogger)
817         def _on_conference_talks(self, programs):
818                 if self._isDestroyed:
819                         _moduleLogger.info("Download complete but window destroyed")
820                         return
821
822                 self._hide_loading()
823                 for programNode in programs:
824                         program = programNode.get_properties()
825                         row = programNode, "%s\n%s" % (program["title"], program["speaker"])
826                         self._model.append(row)
827
828                 path = (self._get_current_row(), )
829                 self._treeView.scroll_to_cell(path)
830                 self._treeView.get_selection().select_path(path)
831
832         @misc_utils.log_exception(_moduleLogger)
833         def _on_error(self, exception):
834                 self._hide_loading()
835                 self._errorBanner.push_message(str(exception))
836
837         def _window_from_node(self, node):
838                 sessionsWindow = ConferenceTalkWindow(self._player, self._store, node)
839                 sessionsWindow.window.set_modal(True)
840                 sessionsWindow.window.set_transient_for(self._window)
841                 sessionsWindow.window.set_default_size(*self._window.get_size())
842                 sessionsWindow.connect("quit", self._on_quit)
843                 sessionsWindow.connect("home", self._on_home)
844                 sessionsWindow.connect("jump-to", self._on_jump)
845                 sessionsWindow.show()
846                 return sessionsWindow
847
848         @misc_utils.log_exception(_moduleLogger)
849         def _on_row_activated(self, view, path, column):
850                 itr = self._model.get_iter(path)
851                 node = self._model.get_value(itr, 0)
852                 self._window_from_node(node)
853
854
855 gobject.type_register(ConferenceTalksWindow)
856
857
858 class ConferenceTalkWindow(BasicWindow):
859
860         def __init__(self, player, store, node):
861                 BasicWindow.__init__(self, player, store)
862                 self._node = node
863                 self._playerNode = self._player.node
864
865                 self.connect_auto(self._player, "state-change", self._on_player_state_change)
866                 self.connect_auto(self._player, "title-change", self._on_player_title_change)
867                 self.connect_auto(self._player, "error", self._on_player_error)
868
869                 self._loadingBanner = banners.GenericBanner()
870
871                 self._presenter = presenter.StreamPresenter(self._store)
872                 self._presenter.set_context(
873                         self._store.STORE_LOOKUP["conference_background"],
874                         self._node.title,
875                         self._node.subtitle,
876                 )
877                 self._presenterNavigation = presenter.NavigationBox()
878                 self._presenterNavigation.toplevel.add(self._presenter.toplevel)
879                 self._presenterNavigation.connect("action", self._on_nav_action)
880                 self._presenterNavigation.connect("navigating", self._on_navigating)
881
882                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
883                 self._layout.pack_start(self._presenterNavigation.toplevel, True, True)
884
885                 self._window.set_title(self._node.title)
886
887         def show(self):
888                 BasicWindow.show(self)
889                 self._window.show_all()
890                 self._errorBanner.toplevel.hide()
891                 self._loadingBanner.toplevel.hide()
892                 self._set_context(self._player.state)
893
894         def jump_to(self, node):
895                 assert self._node is node
896
897         @property
898         def _active(self):
899                 return self._playerNode is self._node
900
901         def _show_loading(self):
902                 animationPath = self._store.STORE_LOOKUP["loading"]
903                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
904                 self._loadingBanner.show(animation, "Loading...")
905
906         def _hide_loading(self):
907                 self._loadingBanner.hide()
908
909         def _set_context(self, state):
910                 if state == self._player.STATE_PLAY:
911                         if self._active:
912                                 self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
913                         else:
914                                 self._presenter.set_state(self._store.STORE_LOOKUP["play"])
915                 elif state == self._player.STATE_PAUSE:
916                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
917                 elif state == self._player.STATE_STOP:
918                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
919                 else:
920                         _moduleLogger.info("Unhandled player state %s" % state)
921
922         @misc_utils.log_exception(_moduleLogger)
923         def _on_player_state_change(self, player, newState):
924                 if self._presenterNavigation.is_active():
925                         return
926
927                 self._set_context(newState)
928
929         @misc_utils.log_exception(_moduleLogger)
930         def _on_player_title_change(self, player, node):
931                 if not self._active or node in [None, self._node]:
932                         return
933                 self.emit("jump-to", node)
934
935         @misc_utils.log_exception(_moduleLogger)
936         def _on_player_error(self, player, err, debug):
937                 _moduleLogger.error("%r - %r" % (err, debug))
938
939         @misc_utils.log_exception(_moduleLogger)
940         def _on_navigating(self, widget, navState):
941                 if navState == "clicking":
942                         if self._player.state == self._player.STATE_PLAY:
943                                 if self._active:
944                                         imageName = "pause_pressed"
945                                 else:
946                                         imageName = "play_pressed"
947                         elif self._player.state == self._player.STATE_PAUSE:
948                                 imageName = "play_pressed"
949                         elif self._player.state == self._player.STATE_STOP:
950                                 imageName = "play_pressed"
951                         else:
952                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
953                 elif navState == "down":
954                         imageName = "home"
955                 elif navState == "up":
956                         imageName = "play"
957                 elif navState == "left":
958                         imageName = "next"
959                 elif navState == "right":
960                         imageName = "prev"
961
962                 self._presenter.set_state(self._store.STORE_LOOKUP[imageName])
963
964         @misc_utils.log_exception(_moduleLogger)
965         def _on_nav_action(self, widget, navState):
966                 self._set_context(self._player.state)
967
968                 if navState == "clicking":
969                         if self._player.state == self._player.STATE_PLAY:
970                                 if self._active:
971                                         self._player.pause()
972                                 else:
973                                         self._player.set_piece_by_node(self._node)
974                                         self._player.play()
975                         elif self._player.state == self._player.STATE_PAUSE:
976                                 self._player.play()
977                         elif self._player.state == self._player.STATE_STOP:
978                                 self._player.set_piece_by_node(self._node)
979                                 self._player.play()
980                         else:
981                                 _moduleLogger.info("Unhandled player state %s" % self._player.state)
982                 elif navState == "down":
983                         self.emit("home")
984                         self._window.destroy()
985                 elif navState == "up":
986                         pass
987                 elif navState == "left":
988                         if self._active:
989                                 self._player.next()
990                         else:
991                                 pass # @todo Not Implemented
992                 elif navState == "right":
993                         if self._active:
994                                 self._player.back()
995                         else:
996                                 pass # @todo Not Implemented
997
998
999 gobject.type_register(ConferenceTalkWindow)