X-Git-Url: http://git.maemo.org/git/?p=watersofshiloah;a=blobdiff_plain;f=src%2Fwindows.py;h=0820900446eac4dfa3c895add19ea61e0b844a0c;hp=fd7ece3971636d9414284412535683c503c6c79b;hb=48391603fc793625843346917737373b891212a9;hpb=ae0d1937085329138f391a9c9d70e4be85d59251 diff --git a/src/windows.py b/src/windows.py index fd7ece3..0820900 100644 --- a/src/windows.py +++ b/src/windows.py @@ -10,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 @@ -19,7 +21,7 @@ import presenter _moduleLogger = logging.getLogger(__name__) -class BasicWindow(gobject.GObject): +class BasicWindow(gobject.GObject, go_utils.AutoSignal): __gsignals__ = { 'quit' : ( @@ -65,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) @@ -100,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 @@ -150,9 +156,7 @@ class BasicWindow(gobject.GObject): @misc_utils.log_exception(_moduleLogger) def _on_jump(self, source, node): - _moduleLogger.error("Jump is not implemented") - self.emit("jump-to", node) - self._window.destroy() + raise NotImplementedError("On %s" % self) @misc_utils.log_exception(_moduleLogger) def _on_quit(self, *args): @@ -170,22 +174,22 @@ class SourceSelector(BasicWindow): self._loadingBanner = banners.GenericBanner() self._radioButton = self._create_button("radio", "Radio") - self._radioButton.connect("clicked", self._on_source_selected, RadioWindow, "radio") + 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_source_selected, ConferencesWindow, "conferences") + 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_source_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_source_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) @@ -196,10 +200,13 @@ class SourceSelector(BasicWindow): self._buttonLayout.pack_start(self._magazineWrapper, True, True) self._buttonLayout.pack_start(self._scriptureWrapper, True, True) + self._separator = gtk.HSeparator() 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._separator, False, True) self._layout.pack_start(self._playcontrol.toplevel, False, True) self._window.set_title(constants.__pretty_app_name__) @@ -250,15 +257,35 @@ class SourceSelector(BasicWindow): self._hide_loading() self._errorBanner.push_message(str(exception)) - @misc_utils.log_exception(_moduleLogger) - def _on_source_selected(self, widget, Source, nodeName): - node = self._index.get_source(nodeName, self._languages[0]["id"]) + 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) gobject.type_register(SourceSelector) @@ -271,8 +298,8 @@ class RadioWindow(BasicWindow): self._node = node self._childNode = None - self._player.connect("state-change", self._on_player_state_change) - self._player.connect("title-change", self._on_player_title_change) + 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() @@ -323,8 +350,8 @@ class RadioWindow(BasicWindow): self._layout.pack_start(self._loadingBanner.toplevel, False, False) self._layout.pack_start(self._radioLayout, True, True) - self._window.set_title(self._node.title) self._dateShown = datetime.datetime.now() + self._update_title() def show(self): BasicWindow.show(self) @@ -334,6 +361,12 @@ class RadioWindow(BasicWindow): self._refresh() + def jump_to(self, node): + _moduleLogger.info("Only 1 channel, nothing to jump to") + + def _update_title(self): + self._window.set_title("%s - %s" % (self._node.title, self._dateShown.strftime("%m/%d"))) + @property def _active(self): return self._player.node is self._childNode @@ -343,7 +376,7 @@ class RadioWindow(BasicWindow): if self._active: self._presenter.set_state(self._store.STORE_LOOKUP["pause"]) else: - self._presenter.set_state(self._store.STORE_LOOKUP["stop"]) + 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: @@ -399,24 +432,23 @@ class RadioWindow(BasicWindow): if navState == "clicking": if self._player.state == self._player.STATE_PLAY: if self._active: - imageName = "pause" + imageName = "pause_pressed" else: - imageName = "stop" + imageName = "play_pressed" elif self._player.state == self._player.STATE_PAUSE: - imageName = "play" + imageName = "play_pressed" elif self._player.state == self._player.STATE_STOP: - imageName = "play" + imageName = "play_pressed" else: - imageName = "play" + imageName = "play_pressed" _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" + else: + if self._player.state == self._player.STATE_PLAY: + imageName = "pause" + else: + imageName = "play" self._presenter.set_state(self._store.STORE_LOOKUP[imageName]) @@ -429,7 +461,8 @@ class RadioWindow(BasicWindow): if self._active: self._player.pause() else: - self._player.stop() + 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: @@ -443,9 +476,11 @@ class RadioWindow(BasicWindow): pass elif navState == "left": self._dateShown += datetime.timedelta(days=1) + self._update_title() self._refresh() elif navState == "right": self._dateShown -= datetime.timedelta(days=1) + self._update_title() self._refresh() @misc_utils.log_exception(_moduleLogger) @@ -475,9 +510,14 @@ class RadioWindow(BasicWindow): row = program["time"], program["title"] self._programmingModel.append(row) - path = (self._get_current_row(), ) - self._treeView.scroll_to_cell(path) - self._treeView.get_selection().select_path(path) + 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): @@ -505,6 +545,8 @@ class ListWindow(BasicWindow): BasicWindow.__init__(self, player, store) self._node = node + self.connect_auto(self._player, "title-change", self._on_player_title_change) + self._loadingBanner = banners.GenericBanner() modelTypes, columns = zip(*self._get_columns()) @@ -550,12 +592,55 @@ class ListWindow(BasicWindow): 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_player_title_change(self, player, node): + self._select_row() + + @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) @@ -569,7 +654,10 @@ class ListWindow(BasicWindow): self._model.clear() def _select_row(self): - path = (self._get_current_row(), ) + rowIndex = self._get_current_row() + if rowIndex < 0: + return + path = (rowIndex, ) self._treeView.scroll_to_cell(path) self._treeView.get_selection().select_path(path) @@ -596,10 +684,6 @@ class ConferencesWindow(ListWindow): column.add_attribute(textrenderer, "text", 2) yield gobject.TYPE_STRING, column - def _get_current_row(self): - # @todo Not implemented yet - return 0 - def _refresh(self): ListWindow._refresh(self) self._node.get_children( @@ -619,27 +703,29 @@ class ConferencesWindow(ListWindow): 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) + self._select_row() @misc_utils.log_exception(_moduleLogger) def _on_error(self, exception): self._hide_loading() self._errorBanner.push_message(str(exception)) - @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) - + 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) @@ -661,10 +747,6 @@ class ConferenceSessionsWindow(ListWindow): column.add_attribute(textrenderer, "text", 1) yield gobject.TYPE_STRING, column - def _get_current_row(self): - # @todo Not implemented yet - return 0 - def _refresh(self): ListWindow._refresh(self) self._node.get_children( @@ -684,27 +766,29 @@ class ConferenceSessionsWindow(ListWindow): 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) + self._select_row() @misc_utils.log_exception(_moduleLogger) def _on_error(self, exception): self._hide_loading() self._errorBanner.push_message(str(exception)) - @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) - + 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) @@ -726,10 +810,6 @@ class ConferenceTalksWindow(ListWindow): column.add_attribute(textrenderer, "text", 1) yield gobject.TYPE_STRING, column - def _get_current_row(self): - # @todo Not implemented yet - return 0 - def _refresh(self): ListWindow._refresh(self) self._node.get_children( @@ -749,27 +829,29 @@ class ConferenceTalksWindow(ListWindow): 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) + self._select_row() @misc_utils.log_exception(_moduleLogger) def _on_error(self, exception): self._hide_loading() self._errorBanner.push_message(str(exception)) - @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) - + 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) @@ -780,21 +862,33 @@ class ConferenceTalkWindow(BasicWindow): def __init__(self, player, store, node): BasicWindow.__init__(self, player, store) self._node = node + self._playerNode = self._player.node + self._nextSearch = None + self._updateSeek = None - self._player.connect("state-change", self._on_player_state_change) - self._player.connect("title-change", self._on_player_title_change) - self._player.connect("error", self._on_player_error) + 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._presenter.set_context( + self._store.STORE_LOOKUP["conference_background"], + self._node.title, + self._node.subtitle, + ) 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._seekbar = hildonize.create_seekbar() + self._seekbar.connect("change-value", self._on_user_seek) + self._layout.pack_start(self._loadingBanner.toplevel, False, False) self._layout.pack_start(self._presenterNavigation.toplevel, True, True) + self._layout.pack_start(self._seekbar, False, False) self._window.set_title(self._node.title) @@ -803,17 +897,15 @@ class ConferenceTalkWindow(BasicWindow): 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) + self._seekbar.hide() + + def jump_to(self, node): + assert self._node is node @property def _active(self): - return self._player.node is self._node + return self._playerNode is self._node def _show_loading(self): animationPath = self._store.STORE_LOOKUP["loading"] @@ -828,7 +920,7 @@ class ConferenceTalkWindow(BasicWindow): if self._active: self._presenter.set_state(self._store.STORE_LOOKUP["pause"]) else: - self._presenter.set_state(self._store.STORE_LOOKUP["stop"]) + 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: @@ -837,22 +929,37 @@ class ConferenceTalkWindow(BasicWindow): _moduleLogger.info("Unhandled player state %s" % state) @misc_utils.log_exception(_moduleLogger) + def _on_user_seek(self, widget, scroll, value): + self._player.seek(value / 100.0) + + @misc_utils.log_exception(_moduleLogger) + def _on_player_update_seek(self): + self._seekbar.set_value(self._player.percent_elapsed * 100) + return True if not self._isDestroyed else False + + @misc_utils.log_exception(_moduleLogger) def _on_player_state_change(self, player, newState): - if self._presenterNavigation.is_active(): - return + if self._active and self._player.state == self._player.STATE_PLAY: + self._seekbar.show() + assert self._updateSeek is None + self._updateSeek = go_utils.Timeout(self._updateSeek, once=False) + self._updateSeek.start(seconds=30) + else: + self._seekbar.hide() + self._updateSeek.cancel() + self._updateSeek = None - self._set_context(newState) + if not self._presenterNavigation.is_active(): + 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) + if not self._active or node in [None, self._node]: + self._playerNode = player.node return - self._presenter.set_context( - self._store.STORE_LOOKUP["conference_background"], - self._player.title, - self._player.subtitle, - ) + self._playerNode = player.node + self.emit("jump-to", node) + self._window.destroy() @misc_utils.log_exception(_moduleLogger) def _on_player_error(self, player, err, debug): @@ -863,19 +970,29 @@ class ConferenceTalkWindow(BasicWindow): if navState == "clicking": if self._player.state == self._player.STATE_PLAY: if self._active: - imageName = "pause" + imageName = "pause_pressed" else: - imageName = "stop" + imageName = "play_pressed" elif self._player.state == self._player.STATE_PAUSE: - imageName = "play" + imageName = "play_pressed" elif self._player.state == self._player.STATE_STOP: - imageName = "play" + imageName = "play_pressed" else: _moduleLogger.info("Unhandled player state %s" % self._player.state) elif navState == "down": imageName = "home" elif navState == "up": - imageName = "play" + 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 == "left": imageName = "next" elif navState == "right": @@ -892,7 +1009,8 @@ class ConferenceTalkWindow(BasicWindow): if self._active: self._player.pause() else: - self._player.stop() + 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: @@ -906,9 +1024,30 @@ class ConferenceTalkWindow(BasicWindow): elif navState == "up": pass elif navState == "left": - self._player.next() + if self._active: + self._player.next() + else: + assert self._nextSearch is None + self._nextSearch = stream_index.AsyncWalker(stream_index.get_next) + self._nextSearch.start(self._node, self._on_next_node, self._on_node_search_error) elif navState == "right": - self._player.back() + if self._active: + self._player.back() + else: + assert self._nextSearch is None + self._nextSearch = stream_index.AsyncWalker(stream_index.get_previous) + self._nextSearch.start(self._node, self._on_next_node, self._on_node_search_error) + + @misc_utils.log_exception(_moduleLogger) + def _on_next_node(self, node): + self._nextSearch = None + self.emit("jump-to", node) + self._window.destroy() + + @misc_utils.log_exception(_moduleLogger) + def _on_node_search_error(self, e): + self._nextSearch = None + self._errorBanner.push_message(str(e)) gobject.type_register(ConferenceTalkWindow)