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