X-Git-Url: http://git.maemo.org/git/?p=watersofshiloah;a=blobdiff_plain;f=src%2Fwindows.py;h=38008a30f9150ee9a68929eb30461d89e018fcfb;hp=421d321b13fbc7a44e0ff51606257f188f6993a8;hb=3daeae4dded6b994e3c4862b63a1f2086622bc35;hpb=d7e1ea9ce15bd2047e90f5fa0137ed7bf9d0f5f3 diff --git a/src/windows.py b/src/windows.py index 421d321..38008a3 100644 --- a/src/windows.py +++ b/src/windows.py @@ -1,3 +1,5 @@ +# @todo Add icons to buttons/rows to indicate that the currently playing track is coming from that + import ConfigParser import datetime import logging @@ -8,7 +10,9 @@ import gtk import constants import hildonize import util.misc as misc_utils +import util.go_utils as go_utils +import stream_index import banners import playcontrol import presenter @@ -17,7 +21,7 @@ import presenter _moduleLogger = logging.getLogger(__name__) -class BasicWindow(gobject.GObject): +class BasicWindow(gobject.GObject, go_utils.AutoSignal): __gsignals__ = { 'quit' : ( @@ -25,20 +29,34 @@ class BasicWindow(gobject.GObject): gobject.TYPE_NONE, (), ), - 'fullscreen' : ( + 'home' : ( + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (), + ), + 'jump-to' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, ), ), + 'rotate' : ( + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_BOOLEAN, ), + ), + 'fullscreen' : ( + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_BOOLEAN, ), + ), } - def __init__(self, player, store, index): + def __init__(self, player, store): gobject.GObject.__init__(self) self._isDestroyed = False self._player = player self._store = store - self._index = index self._clipboard = gtk.clipboard_get() self._windowInFullscreen = False @@ -49,6 +67,7 @@ class BasicWindow(gobject.GObject): self._layout.pack_start(self._errorBanner.toplevel, False, True) self._window = gtk.Window() + go_utils.AutoSignal.__init__(self, self.window) self._window.add(self._layout) self._window = hildonize.hildonize_window(self, self._window) @@ -61,6 +80,9 @@ class BasicWindow(gobject.GObject): def window(self): return self._window + def show(self): + self._window.show_all() + def save_settings(self, config, sectionName): config.add_section(sectionName) config.set(sectionName, "fullscreen", str(self._windowInFullscreen)) @@ -81,6 +103,9 @@ class BasicWindow(gobject.GObject): else: self._window.unfullscreen() + def jump_to(self, node): + raise NotImplementedError("On %s" % self) + @misc_utils.log_exception(_moduleLogger) def _on_destroy(self, *args): self._isDestroyed = True @@ -124,49 +149,88 @@ class BasicWindow(gobject.GObject): self._clipboard.set_text(str(log)) return True + @misc_utils.log_exception(_moduleLogger) + def _on_home(self, *args): + self.emit("home") + self._window.destroy() + + @misc_utils.log_exception(_moduleLogger) + def _on_jump(self, source, node): + raise NotImplementedError("On %s" % self) + + @misc_utils.log_exception(_moduleLogger) + def _on_quit(self, *args): + self.emit("quit") + self._window.destroy() + class SourceSelector(BasicWindow): def __init__(self, player, store, index): - BasicWindow.__init__(self, player, store, index) + BasicWindow.__init__(self, player, store) + self._languages = [] + self._index = index + + self._loadingBanner = banners.GenericBanner() self._radioButton = self._create_button("radio", "Radio") - self._radioButton.connect("clicked", self._on_radio_selected) + self._radioButton.connect("clicked", self._on_source_selected, stream_index.SOURCE_RADIO) self._radioWrapper = gtk.VBox() self._radioWrapper.pack_start(self._radioButton, False, True) self._conferenceButton = self._create_button("conferences", "Conferences") - #self._conferenceButton.connect("clicked", self._on_conference_selected) + self._conferenceButton.connect("clicked", self._on_source_selected, stream_index.SOURCE_CONFERENCES) self._conferenceWrapper = gtk.VBox() self._conferenceWrapper.pack_start(self._conferenceButton, False, True) self._magazineButton = self._create_button("magazines", "Magazines") - #self._magazineButton.connect("clicked", self._on_magazine_selected) + self._magazineButton.connect("clicked", self._on_source_selected, stream_index.SOURCE_MAGAZINES) self._magazineWrapper = gtk.VBox() self._magazineWrapper.pack_start(self._magazineButton, False, True) self._scriptureButton = self._create_button("scriptures", "Scriptures") - #self._scriptureButton.connect("clicked", self._on_scripture_selected) + self._scriptureButton.connect("clicked", self._on_source_selected, stream_index.SOURCE_SCRIPTURES) self._scriptureWrapper = gtk.VBox() self._scriptureWrapper.pack_start(self._scriptureButton, False, True) - self._buttonLayout = gtk.VBox(True, 5) - self._buttonLayout.set_property("border-width", 5) + self._buttonLayout = gtk.VButtonBox() + self._buttonLayout.set_layout(gtk.BUTTONBOX_SPREAD) self._buttonLayout.pack_start(self._radioWrapper, True, True) self._buttonLayout.pack_start(self._conferenceWrapper, True, True) self._buttonLayout.pack_start(self._magazineWrapper, True, True) self._buttonLayout.pack_start(self._scriptureWrapper, True, True) - self._playcontrol = playcontrol.PlayControl(player, store) + self._playcontrol = playcontrol.NavControl(player, store) + self._playcontrol.connect("jump-to", self._on_jump) + self._layout.pack_start(self._loadingBanner.toplevel, False, False) self._layout.pack_start(self._buttonLayout, True, True) self._layout.pack_start(self._playcontrol.toplevel, False, True) self._window.set_title(constants.__pretty_app_name__) - self._window.show_all() + + def show(self): + BasicWindow.show(self) + self._errorBanner.toplevel.hide() self._playcontrol.toplevel.hide() + self._refresh() + + def _show_loading(self): + animationPath = self._store.STORE_LOOKUP["loading"] + animation = self._store.get_pixbuf_animation_from_store(animationPath) + self._loadingBanner.show(animation, "Loading...") + self._buttonLayout.set_sensitive(False) + + def _hide_loading(self): + self._loadingBanner.hide() + self._buttonLayout.set_sensitive(True) + + def _refresh(self): + self._show_loading() + self._index.get_languages(self._on_languages, self._on_error) + def _create_button(self, icon, message): image = self._store.get_image_from_store(self._store.STORE_LOOKUP[icon]) @@ -182,22 +246,67 @@ class SourceSelector(BasicWindow): return button @misc_utils.log_exception(_moduleLogger) - def _on_radio_selected(self, *args): - radioView = RadioView(self._player, self._store, self._index) - radioView.window.set_modal(True) - radioView.window.set_transient_for(self._window) - radioView.window.set_default_size(*self._window.get_size()) + def _on_languages(self, languages): + self._hide_loading() + self._languages = list(languages) + + @misc_utils.log_exception(_moduleLogger) + def _on_error(self, exception): + self._hide_loading() + self._errorBanner.push_message(str(exception)) + + def _window_from_node(self, node): + if node.id == stream_index.SOURCE_RADIO: + Source = RadioWindow + elif node.id == stream_index.SOURCE_CONFERENCES: + Source = ConferencesWindow + elif node.id == stream_index.SOURCE_MAGAZINES: + pass + elif node.id == stream_index.SOURCE_SCRIPTURES: + pass + sourceWindow = Source(self._player, self._store, node) + sourceWindow.window.set_modal(True) + sourceWindow.window.set_transient_for(self._window) + sourceWindow.window.set_default_size(*self._window.get_size()) + sourceWindow.connect("quit", self._on_quit) + sourceWindow.connect("jump-to", self._on_jump) + sourceWindow.show() + return sourceWindow + + @misc_utils.log_exception(_moduleLogger) + def _on_jump(self, source, node): + targetNodePath = list(reversed(list(stream_index.walk_ancestors(node)))) + ancestor = targetNodePath[0] + window = self._window_from_node(ancestor) + window.jump_to(node) + @misc_utils.log_exception(_moduleLogger) + def _on_source_selected(self, widget, nodeName): + node = self._index.get_source(nodeName, self._languages[0]["id"]) + self._window_from_node(node) -class RadioView(BasicWindow): - def __init__(self, player, store, index): - BasicWindow.__init__(self, player, store, index) +gobject.type_register(SourceSelector) + + +class RadioWindow(BasicWindow): + + def __init__(self, player, store, node): + BasicWindow.__init__(self, player, store) + self._node = node + self._childNode = None + + self.connect_auto(self._player, "state-change", self._on_player_state_change) + self.connect_auto(self._player, "title-change", self._on_player_title_change) self._loadingBanner = banners.GenericBanner() headerPath = self._store.STORE_LOOKUP["radio_header"] self._header = self._store.get_image_from_store(headerPath) + self._headerNavigation = presenter.NavigationBox() + self._headerNavigation.toplevel.add(self._header) + self._headerNavigation.connect("action", self._on_nav_action) + self._headerNavigation.connect("navigating", self._on_navigating) self._programmingModel = gtk.ListStore( gobject.TYPE_STRING, @@ -219,33 +328,58 @@ class RadioView(BasicWindow): self._treeView.set_model(self._programmingModel) self._treeView.append_column(timeColumn) self._treeView.append_column(titleColumn) + self._treeView.get_selection().connect("changed", self._on_row_changed) self._treeScroller = gtk.ScrolledWindow() self._treeScroller.add(self._treeView) self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) - self._presenter = presenter.StreamMiniPresenter(self._player, self._store) + self._presenter = presenter.StreamMiniPresenter(self._store) + self._presenterNavigation = presenter.NavigationBox() + self._presenterNavigation.toplevel.add(self._presenter.toplevel) + self._presenterNavigation.connect("action", self._on_nav_action) + self._presenterNavigation.connect("navigating", self._on_navigating) self._radioLayout = gtk.VBox(False) - self._radioLayout.pack_start(self._header, False, False) + self._radioLayout.pack_start(self._headerNavigation.toplevel, False, False) self._radioLayout.pack_start(self._treeScroller, True, True) - self._radioLayout.pack_start(self._presenter.toplevel, False, True) - - self._programNavigation = presenter.NavigationBox() - self._programNavigation.toplevel.add(self._radioLayout) - self._programNavigation.connect("action", self._on_nav_action) + self._radioLayout.pack_start(self._presenterNavigation.toplevel, False, True) self._layout.pack_start(self._loadingBanner.toplevel, False, False) - self._layout.pack_start(self._programNavigation.toplevel, True, True) + self._layout.pack_start(self._radioLayout, True, True) + + self._window.set_title(self._node.title) + self._dateShown = datetime.datetime.now() + + def show(self): + BasicWindow.show(self) - self._window.set_title("Radio") - self._window.show_all() self._errorBanner.toplevel.hide() self._loadingBanner.toplevel.hide() - self._dateShown = datetime.datetime.now() self._refresh() + def jump_to(self, node): + _moduleLogger.info("Only 1 channel, nothing to jump to") + + @property + def _active(self): + return self._player.node is self._childNode + + def _set_context(self, state): + if state == self._player.STATE_PLAY: + if self._active: + self._presenter.set_state(self._store.STORE_LOOKUP["pause"]) + else: + self._presenter.set_state(self._store.STORE_LOOKUP["play"]) + elif state == self._player.STATE_PAUSE: + self._presenter.set_state(self._store.STORE_LOOKUP["play"]) + elif state == self._player.STATE_STOP: + self._presenter.set_state(self._store.STORE_LOOKUP["play"]) + else: + _moduleLogger.info("Unhandled player state %s" % state) + self._presenter.set_state(self._store.STORE_LOOKUP["play"]) + def _show_loading(self): animationPath = self._store.STORE_LOOKUP["loading"] animation = self._store.get_pixbuf_animation_from_store(animationPath) @@ -255,15 +389,83 @@ class RadioView(BasicWindow): self._loadingBanner.hide() def _refresh(self): - self._programmingModel.clear() self._show_loading() - self._index.download_radio(self._on_channels, self._on_load_error) + self._programmingModel.clear() + self._node.get_children( + self._on_channels, + self._on_load_error, + ) + self._set_context(self._player.state) + + def _get_current_row(self): + nowTime = self._dateShown.strftime("%H:%M:%S") + i = 0 + for i, row in enumerate(self._programmingModel): + if nowTime < row[0]: + if i == 0: + return 0 + else: + return i - 1 + else: + return i + + @misc_utils.log_exception(_moduleLogger) + def _on_player_state_change(self, player, newState): + if self._headerNavigation.is_active() or self._presenterNavigation.is_active(): + return + + self._set_context(newState) + + @misc_utils.log_exception(_moduleLogger) + def _on_player_title_change(self, player, node): + if node is not self._childNode or node is None: + _moduleLogger.info("Player title magically changed to %s" % player.title) + return + + @misc_utils.log_exception(_moduleLogger) + def _on_navigating(self, widget, navState): + if navState == "clicking": + if self._player.state == self._player.STATE_PLAY: + if self._active: + imageName = "pause" + else: + imageName = "play" + elif self._player.state == self._player.STATE_PAUSE: + imageName = "play" + elif self._player.state == self._player.STATE_STOP: + imageName = "play" + else: + imageName = "play" + _moduleLogger.info("Unhandled player state %s" % self._player.state) + elif navState == "down": + imageName = "home" + elif navState == "up": + imageName = "play" + elif navState == "left": + imageName = "play" + elif navState == "right": + imageName = "play" + + self._presenter.set_state(self._store.STORE_LOOKUP[imageName]) @misc_utils.log_exception(_moduleLogger) def _on_nav_action(self, widget, navState): - _moduleLogger.info(navState) + self._set_context(self._player.state) + if navState == "clicking": - pass + if self._player.state == self._player.STATE_PLAY: + if self._active: + self._player.pause() + else: + self._player.set_piece_by_node(self._childNode) + self._player.play() + elif self._player.state == self._player.STATE_PAUSE: + self._player.play() + elif self._player.state == self._player.STATE_STOP: + self._player.set_piece_by_node(self._childNode) + self._player.play() + else: + _moduleLogger.info("Unhandled player state %s" % self._player.state) elif navState == "down": self.window.destroy() elif navState == "up": @@ -281,11 +483,15 @@ class RadioView(BasicWindow): _moduleLogger.info("Download complete but window destroyed") return - channels = list(channels) + channels = channels if 1 < len(channels): _moduleLogger.warning("More channels now available!") - channel = channels[0] - self._index.download_radio(self._on_channel, self._on_load_error, channel["id"]) + self._childNode = channels[0] + self._childNode.get_programming( + self._dateShown, + self._on_channel, + self._on_load_error, + ) @misc_utils.log_exception(_moduleLogger) def _on_channel(self, programs): @@ -298,19 +504,488 @@ class RadioView(BasicWindow): row = program["time"], program["title"] self._programmingModel.append(row) + currentDate = datetime.datetime.now() + if currentDate.date() != self._dateShown.date(): + self._treeView.get_selection().set_mode(gtk.SELECTION_NONE) + else: + self._treeView.get_selection().set_mode(gtk.SELECTION_SINGLE) + path = (self._get_current_row(), ) + self._treeView.scroll_to_cell(path) + self._treeView.get_selection().select_path(path) + + @misc_utils.log_exception(_moduleLogger) + def _on_load_error(self, exception): + self._hide_loading() + self._errorBanner.push_message(str(exception)) + + @misc_utils.log_exception(_moduleLogger) + def _on_row_changed(self, selection): + if len(self._programmingModel) == 0: + return + + rowIndex = self._get_current_row() + path = (rowIndex, ) + if not selection.path_is_selected(path): + # Undo the user's changing of the selection + selection.select_path(path) + + +gobject.type_register(RadioWindow) + + +class ListWindow(BasicWindow): + + def __init__(self, player, store, node): + BasicWindow.__init__(self, player, store) + self._node = node + + self._loadingBanner = banners.GenericBanner() + + modelTypes, columns = zip(*self._get_columns()) + + self._model = gtk.ListStore(*modelTypes) + + self._treeView = gtk.TreeView() + self._treeView.connect("row-activated", self._on_row_activated) + self._treeView.set_headers_visible(False) + self._treeView.set_model(self._model) + for column in columns: + if column is not None: + self._treeView.append_column(column) + + self._treeScroller = gtk.ScrolledWindow() + self._treeScroller.add(self._treeView) + self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + + self._separator = gtk.HSeparator() + self._playcontrol = playcontrol.NavControl(self._player, self._store) + self._playcontrol.connect("home", self._on_home) + self._playcontrol.connect("jump-to", self._on_jump) + + self._contentLayout = gtk.VBox(False) + self._contentLayout.pack_start(self._treeScroller, True, True) + self._contentLayout.pack_start(self._separator, False, True) + self._contentLayout.pack_start(self._playcontrol.toplevel, False, True) + + self._layout.pack_start(self._loadingBanner.toplevel, False, False) + self._layout.pack_start(self._contentLayout, True, True) + + def show(self): + BasicWindow.show(self) + + self._errorBanner.toplevel.hide() + self._loadingBanner.toplevel.hide() + + self._refresh() + self._playcontrol.refresh() + + @classmethod + def _get_columns(cls): + raise NotImplementedError("") + + def _get_current_row(self): + if self._player.node is None: + return -1 + ancestors, current, descendants = stream_index.common_paths(self._player.node, self._node) + if not descendants: + return -1 + activeChild = descendants[0] + for i, row in enumerate(self._model): + if activeChild is row[0]: + return i + else: + return -1 + + def jump_to(self, node): + ancestors, current, descendants = stream_index.common_paths(node, self._node) + if current is None: + raise RuntimeError("Cannot jump to node %s" % node) + if not descendants: + _moduleLogger.info("Current node is the target") + return + child = descendants[0] + window = self._window_from_node(child) + window.jump_to(node) + + def _window_from_node(self, node): + raise NotImplementedError("") + + @misc_utils.log_exception(_moduleLogger) + def _on_row_activated(self, view, path, column): + raise NotImplementedError("") + + @misc_utils.log_exception(_moduleLogger) + def _on_jump(self, source, node): + ancestors, current, descendants = stream_index.common_paths(node, self._node) + if current is None: + _moduleLogger.info("%s is not the target, moving up" % self._node) + self.emit("jump-to", node) + self._window.destroy() + return + if not descendants: + _moduleLogger.info("Current node is the target") + return + child = descendants[0] + window = self._window_from_node(child) + window.jump_to(node) + + def _show_loading(self): + animationPath = self._store.STORE_LOOKUP["loading"] + animation = self._store.get_pixbuf_animation_from_store(animationPath) + self._loadingBanner.show(animation, "Loading...") + + def _hide_loading(self): + self._loadingBanner.hide() + + def _refresh(self): + self._show_loading() + self._model.clear() + + def _select_row(self): path = (self._get_current_row(), ) self._treeView.scroll_to_cell(path) self._treeView.get_selection().select_path(path) - def _get_current_row(self): - nowTime = self._dateShown.strftime("%H:%M:%S") - for i, row in enumerate(self._programmingModel): - if nowTime < row[0]: - return i - 1 - else: - return i + +class ConferencesWindow(ListWindow): + + def __init__(self, player, store, node): + ListWindow.__init__(self, player, store, node) + self._window.set_title(self._node.title) + + @classmethod + def _get_columns(cls): + yield gobject.TYPE_PYOBJECT, None + + textrenderer = gtk.CellRendererText() + column = gtk.TreeViewColumn("Date") + column.pack_start(textrenderer, expand=True) + column.add_attribute(textrenderer, "text", 1) + yield gobject.TYPE_STRING, column + + textrenderer = gtk.CellRendererText() + column = gtk.TreeViewColumn("Conference") + column.pack_start(textrenderer, expand=True) + column.add_attribute(textrenderer, "text", 2) + yield gobject.TYPE_STRING, column + + def _refresh(self): + ListWindow._refresh(self) + self._node.get_children( + self._on_conferences, + self._on_error, + ) @misc_utils.log_exception(_moduleLogger) - def _on_load_error(self, exception): + def _on_conferences(self, programs): + if self._isDestroyed: + _moduleLogger.info("Download complete but window destroyed") + return + + self._hide_loading() + for programNode in programs: + program = programNode.get_properties() + row = programNode, program["title"], program["full_title"] + self._model.append(row) + + path = (self._get_current_row(), ) + self._treeView.scroll_to_cell(path) + self._treeView.get_selection().select_path(path) + + @misc_utils.log_exception(_moduleLogger) + def _on_error(self, exception): + self._hide_loading() + self._errorBanner.push_message(str(exception)) + + def _window_from_node(self, node): + sessionsWindow = ConferenceSessionsWindow(self._player, self._store, node) + sessionsWindow.window.set_modal(True) + sessionsWindow.window.set_transient_for(self._window) + sessionsWindow.window.set_default_size(*self._window.get_size()) + sessionsWindow.connect("quit", self._on_quit) + sessionsWindow.connect("home", self._on_home) + sessionsWindow.connect("jump-to", self._on_jump) + sessionsWindow.show() + return sessionsWindow + + @misc_utils.log_exception(_moduleLogger) + def _on_row_activated(self, view, path, column): + itr = self._model.get_iter(path) + node = self._model.get_value(itr, 0) + self._window_from_node(node) + + +gobject.type_register(ConferencesWindow) + + +class ConferenceSessionsWindow(ListWindow): + + def __init__(self, player, store, node): + ListWindow.__init__(self, player, store, node) + self._window.set_title(self._node.title) + + @classmethod + def _get_columns(cls): + yield gobject.TYPE_PYOBJECT, None + + textrenderer = gtk.CellRendererText() + column = gtk.TreeViewColumn("Session") + column.pack_start(textrenderer, expand=True) + column.add_attribute(textrenderer, "text", 1) + yield gobject.TYPE_STRING, column + + def _refresh(self): + ListWindow._refresh(self) + self._node.get_children( + self._on_conference_sessions, + self._on_error, + ) + + @misc_utils.log_exception(_moduleLogger) + def _on_conference_sessions(self, programs): + if self._isDestroyed: + _moduleLogger.info("Download complete but window destroyed") + return + + self._hide_loading() + for programNode in programs: + program = programNode.get_properties() + row = programNode, program["title"] + self._model.append(row) + + path = (self._get_current_row(), ) + self._treeView.scroll_to_cell(path) + self._treeView.get_selection().select_path(path) + + @misc_utils.log_exception(_moduleLogger) + def _on_error(self, exception): + self._hide_loading() + self._errorBanner.push_message(str(exception)) + + def _window_from_node(self, node): + sessionsWindow = ConferenceTalksWindow(self._player, self._store, node) + sessionsWindow.window.set_modal(True) + sessionsWindow.window.set_transient_for(self._window) + sessionsWindow.window.set_default_size(*self._window.get_size()) + sessionsWindow.connect("quit", self._on_quit) + sessionsWindow.connect("home", self._on_home) + sessionsWindow.connect("jump-to", self._on_jump) + sessionsWindow.show() + return sessionsWindow + + @misc_utils.log_exception(_moduleLogger) + def _on_row_activated(self, view, path, column): + itr = self._model.get_iter(path) + node = self._model.get_value(itr, 0) + self._window_from_node(node) + + +gobject.type_register(ConferenceSessionsWindow) + + +class ConferenceTalksWindow(ListWindow): + + def __init__(self, player, store, node): + ListWindow.__init__(self, player, store, node) + self._window.set_title(self._node.title) + + @classmethod + def _get_columns(cls): + yield gobject.TYPE_PYOBJECT, None + + textrenderer = gtk.CellRendererText() + column = gtk.TreeViewColumn("Talk") + column.pack_start(textrenderer, expand=True) + column.add_attribute(textrenderer, "text", 1) + yield gobject.TYPE_STRING, column + + def _refresh(self): + ListWindow._refresh(self) + self._node.get_children( + self._on_conference_talks, + self._on_error, + ) + + @misc_utils.log_exception(_moduleLogger) + def _on_conference_talks(self, programs): + if self._isDestroyed: + _moduleLogger.info("Download complete but window destroyed") + return + + self._hide_loading() + for programNode in programs: + program = programNode.get_properties() + row = programNode, "%s\n%s" % (program["title"], program["speaker"]) + self._model.append(row) + + path = (self._get_current_row(), ) + self._treeView.scroll_to_cell(path) + self._treeView.get_selection().select_path(path) + + @misc_utils.log_exception(_moduleLogger) + def _on_error(self, exception): self._hide_loading() - self._errorBanner.push_message(exception) + self._errorBanner.push_message(str(exception)) + + def _window_from_node(self, node): + sessionsWindow = ConferenceTalkWindow(self._player, self._store, node) + sessionsWindow.window.set_modal(True) + sessionsWindow.window.set_transient_for(self._window) + sessionsWindow.window.set_default_size(*self._window.get_size()) + sessionsWindow.connect("quit", self._on_quit) + sessionsWindow.connect("home", self._on_home) + sessionsWindow.connect("jump-to", self._on_jump) + sessionsWindow.show() + return sessionsWindow + + @misc_utils.log_exception(_moduleLogger) + def _on_row_activated(self, view, path, column): + itr = self._model.get_iter(path) + node = self._model.get_value(itr, 0) + self._window_from_node(node) + + +gobject.type_register(ConferenceTalksWindow) + + +class ConferenceTalkWindow(BasicWindow): + + def __init__(self, player, store, node): + BasicWindow.__init__(self, player, store) + self._node = node + + self.connect_auto(self._player, "state-change", self._on_player_state_change) + self.connect_auto(self._player, "title-change", self._on_player_title_change) + self.connect_auto(self._player, "error", self._on_player_error) + + self._loadingBanner = banners.GenericBanner() + + self._presenter = presenter.StreamPresenter(self._store) + self._presenterNavigation = presenter.NavigationBox() + self._presenterNavigation.toplevel.add(self._presenter.toplevel) + self._presenterNavigation.connect("action", self._on_nav_action) + self._presenterNavigation.connect("navigating", self._on_navigating) + + self._layout.pack_start(self._loadingBanner.toplevel, False, False) + self._layout.pack_start(self._presenterNavigation.toplevel, True, True) + + self._window.set_title(self._node.title) + + def show(self): + BasicWindow.show(self) + self._window.show_all() + self._errorBanner.toplevel.hide() + self._loadingBanner.toplevel.hide() + + self._presenter.set_context( + self._store.STORE_LOOKUP["conference_background"], + self._player.title, + self._player.subtitle, + ) + self._set_context(self._player.state) + + def jump_to(self, node): + assert self._node is node + + @property + def _active(self): + return self._player.node is self._node + + def _show_loading(self): + animationPath = self._store.STORE_LOOKUP["loading"] + animation = self._store.get_pixbuf_animation_from_store(animationPath) + self._loadingBanner.show(animation, "Loading...") + + def _hide_loading(self): + self._loadingBanner.hide() + + def _set_context(self, state): + if state == self._player.STATE_PLAY: + if self._active: + self._presenter.set_state(self._store.STORE_LOOKUP["pause"]) + else: + self._presenter.set_state(self._store.STORE_LOOKUP["play"]) + elif state == self._player.STATE_PAUSE: + self._presenter.set_state(self._store.STORE_LOOKUP["play"]) + elif state == self._player.STATE_STOP: + self._presenter.set_state(self._store.STORE_LOOKUP["play"]) + else: + _moduleLogger.info("Unhandled player state %s" % state) + + @misc_utils.log_exception(_moduleLogger) + def _on_player_state_change(self, player, newState): + if self._presenterNavigation.is_active(): + return + + self._set_context(newState) + + @misc_utils.log_exception(_moduleLogger) + def _on_player_title_change(self, player, node): + if node is not self._node or node is None: + _moduleLogger.info("Player title magically changed to %s" % player.title) + return + self._presenter.set_context( + self._store.STORE_LOOKUP["conference_background"], + self._player.title, + self._player.subtitle, + ) + + @misc_utils.log_exception(_moduleLogger) + def _on_player_error(self, player, err, debug): + _moduleLogger.error("%r - %r" % (err, debug)) + + @misc_utils.log_exception(_moduleLogger) + def _on_navigating(self, widget, navState): + if navState == "clicking": + if self._player.state == self._player.STATE_PLAY: + if self._active: + imageName = "pause" + else: + imageName = "play" + elif self._player.state == self._player.STATE_PAUSE: + imageName = "play" + elif self._player.state == self._player.STATE_STOP: + imageName = "play" + else: + _moduleLogger.info("Unhandled player state %s" % self._player.state) + elif navState == "down": + imageName = "home" + elif navState == "up": + imageName = "play" + elif navState == "left": + imageName = "next" + elif navState == "right": + imageName = "prev" + + self._presenter.set_state(self._store.STORE_LOOKUP[imageName]) + + @misc_utils.log_exception(_moduleLogger) + def _on_nav_action(self, widget, navState): + self._set_context(self._player.state) + + if navState == "clicking": + if self._player.state == self._player.STATE_PLAY: + if self._active: + self._player.pause() + else: + self._player.set_piece_by_node(self._node) + self._player.play() + elif self._player.state == self._player.STATE_PAUSE: + self._player.play() + elif self._player.state == self._player.STATE_STOP: + self._player.set_piece_by_node(self._node) + self._player.play() + else: + _moduleLogger.info("Unhandled player state %s" % self._player.state) + elif navState == "down": + self.emit("home") + self._window.destroy() + elif navState == "up": + pass + elif navState == "left": + self._player.next() + elif navState == "right": + self._player.back() + + +gobject.type_register(ConferenceTalkWindow)