Hi Matt, Thanks a lot for your review. I've fixed the things you've already noticed. > 1) The scroll menu no longer remembers its last item when you return to a > screen. Every time I hit back, it started on Music again. This is fixed. I've also replace "..has_media()" by "..is_playing" because I thought it was closer to what we want in the preview. This will allow us in a close future to connect "play" "stop" events of the media_player to the refresh method so we'll have a dynamic update of it. (those events are created in *reactive_progress_bar*) > 2) The major problem that I have so far is that the keyboard interface is > mostly broken if you return to the main screen after being on a different > screen. For some reason, the menu doesn't think that it is active so it won't > react until the user presses the right arrow key, then it becomes active. Yep didn't notice that. It's fixed now. > 3) I'm not a fan of variable abbreviations most of the time. I noticed that > you have the menu property "visible_nb" and I understand what it is supposed > to mean, but since ScrollMenu uses the term "item," I think that > "visible_items" would be a much clearer property name. > > 4) You created some new methods in Main and UserInterface (which is fine, of > course), but I don't think that they match the conventions that we use for > "private" methods. Since the methods you made aren't meant to be used outside > the class, I would recommend that you prepend an underscore character to make > that clear (e.g., preview_activation becomes _preview_activation). This isn't > directly stated in PEP8, but there is some discussion about having private > portions start with _. Yes, that's right. I've renamed those private methods. Thanks again Matt Samuel- new diff : === modified file 'entertainerlib/frontend/gui/screens/main.py' --- entertainerlib/frontend/gui/screens/main.py 2009-03-02 03:11:24 +0000 +++ entertainerlib/frontend/gui/screens/main.py 2009-04-14 19:43:51 +0000 @@ -44,65 +44,89 @@ self.preview = clutter.Group() # Group that contains preview actors self.preview.set_position(self.get_abs_x(0.07), self.get_abs_y(0.1)) self.preview.show() + self.preview.set_opacity(0x00) self.add(self.preview) - self.active_menu = "main" - self.rss_preview_menu = None # Pointer to the current preview menu - - self.menu = None - self.playing_item = None - self.create_main_menu() - - clock = ClockLabel(0.13, "screentitle", 0, 0.87) - self.add(clock) - - def create_main_menu(self): + self.rss_preview_menu = self._create_rss_preview_menu() + self.preview.add(self.rss_preview_menu) + + self._create_main_menu() + self.menu.active = True + + self.add(ClockLabel(0.13, "screentitle", 0, 0.87)) + + def get_type(self): + """Return screen type.""" + return Screen.NORMAL + + def get_name(self): + """Return screen name (human readble)""" + return "Main" + + def _create_main_menu(self): """Create main menu of the home screen""" - self.menu = ScrollMenu(10, 60, self.config.show_effects()) + self.menu = ScrollMenu(10, 60, 0.045, "menuitem_active") self.menu.set_name("mainmenu") - # Common values for all ScrollMenu items - size = 0.045 - color = "menuitem_active" - - item0 = Label(size, color, 0, 0, _("Play CD"), "disc") - self.menu.add(item0) - - item2 = Label(size, color, 0, 0, _("Videos"), "videos") - self.menu.add(item2) - - item3 = Label(size, color, 0, 0, _("Music"), "music") - self.menu.add(item3) - - item4 = Label(size, color, 0, 0, _("Photographs"), "photo") - self.menu.add(item4) - - item5 = Label(size, color, 0, 0, _("Headlines"), "rss") - self.menu.add(item5) + self.menu.connect('selected', self._on_menu_selected) + self.menu.connect('moved', self._on_menu_moved) + self.menu.connect('activated', self._main_menu_activation) + + self.menu.add_item(_("Play CD"), "disc") + self.menu.add_item(_("Videos"), "videos") + self.menu.add_item(_("Music"), "music") + self.menu.add_item(_("Photographs"), "photo") + self.menu.add_item(_("Headlines"), "rss") if self.config.display_weather_in_frontend(): - item6 = Label(size, color, 0, 0, _("Weather"), "weather") - self.menu.add(item6) + self.menu.add_item(_("Weather"), "weather") if self.config.display_cd_eject_in_frontend(): - item7 = Label(size, color, 0, 0, _("Eject CD"), "eject_cd") - self.menu.add(item7) + self.menu.add_item(_("Eject CD"), "eject_cd") if self.media_player.has_media(): - playing = Label(size, color, 0, 0, _("Playing now..."), "playing") - self.menu.add(playing) - - self.menu.set_number_of_visible_items(5) - menu_clip = self.menu.get_number_of_visible_items() * 70 - + self.menu.add_item(_("Playing now..."), "playing") + + self.menu.visible_items = 5 + self.menu.selected_index = 2 + + menu_clip = self.menu.visible_items * 70 # Menu position menu_y = int((self.config.get_stage_height() - menu_clip + 10) / 2) self.menu.set_position(self.get_abs_x(0.75), menu_y) self.add(self.menu) - def show_playing_preview(self): - """Create a group that displays information on current media.""" + def _create_rss_preview_menu(self): + '''Create the RSS preview menu that will show feed highlights. An + uninitialized menu will be returned to prevent move errors if there + is nothing in the feed library.''' + menu = TextMenu(self.theme, self.config.show_effects()) + + if self.feed_library.is_empty() is False: + menu.set_row_count(1) + menu.set_position(self.get_abs_x(0.035), self.get_abs_y(0.12)) + menu.set_item_size(self.get_abs_x(0.549), self.get_abs_y(0.078)) + + # List of latest entries. pack = (Feed object, Entry object) + entries = self.feed_library.get_latest_entries(5) + for pack in entries: + text = pack[0].get_title() + " - " + pack[1].get_title() + item = TextMenuItem(0.549, 0.078, + FeedEntryParser().strip_tags(text), + pack[1].get_date()) + kwargs = { 'feed' : pack[0], 'entry' : pack[1] } + item.set_userdata(kwargs) + menu.add_actor(item) + + menu.set_active(False) + + return menu + + def _create_playing_preview(self): + '''Create the Now Playing preview sidebar.''' + preview = clutter.Group() + # Video preview of current media video_texture = self.media_player.get_texture() if video_texture == None: @@ -141,106 +165,91 @@ rect.set_size(int(new_width + 6), int(new_height + 6)) rect.set_position(rect_x, rect_y) rect.set_color((128, 128, 128, 192)) - self.preview.add(rect) + preview.add(rect) - self.preview.add(video_texture) + preview.add(video_texture) title_text = _("Now Playing: %(title)s") % \ {'title': self.media_player.get_media_title()} title = Label(0.03, "text", 0.03, 0.74, title_text) - self.preview.add(title) - - def show_rss_preview(self): - """Rss preview""" + preview.add(title) + + return preview + + def _create_rss_preview(self): + '''Create the RSS preview sidebar.''' + preview = clutter.Group() + + # RSS Icon + icon = Texture(self.theme.getImage("rss_icon"), 0, 0.04) + icon.set_scale(0.6, 0.6) + preview.add(icon) + + # RSS Feed Title + title = Label(0.075, "title", 0.045, 0.03, _("Recent headlines")) + preview.add(title) if self.feed_library.is_empty() is False: - # RSS Icon - icon = Texture(self.theme.getImage("rss_icon"), 0, 0.04) - icon.set_scale(0.6, 0.6) - - # RSS Feed Title - title = Label(0.075, "title", 0.045, 0.03, _("Recent headlines")) - - menu = TextMenu(self.theme, self.config.show_effects()) - menu.set_row_count(1) - menu.set_position(self.get_abs_x(0.035), self.get_abs_y(0.12)) - menu.set_item_size(self.get_abs_x(0.549), self.get_abs_y(0.078)) - - # List of latest entries. pack = (Feed object, Entry object) - entries = self.feed_library.get_latest_entries(5) - for pack in entries: - text = pack[0].get_title() + " - " + pack[1].get_title() - item = TextMenuItem(0.549, 0.078, - FeedEntryParser().strip_tags(text), - pack[1].get_date()) - kwargs = { 'feed' : pack[0], 'entry' : pack[1] } - item.set_userdata(kwargs) - menu.add_actor(item) - - menu.set_active(False) - - # Fade in timeline + menu = self._create_rss_preview_menu() + preview.add(menu) + self.rss_preview_menu = menu + else: + # No headlines available in the library + info = Label(0.05, "title", 0.1, 0.35, _("No headlines available")) + preview.add(info) + + return preview + + def _update_preview_area(self): + '''Update the preview area to display the current menu item.''' + self.preview.remove_all() + item = self.menu.get_selected() + + self.preview.set_opacity(0x00) + + update = True + + if item.get_name() == "playing": + self.preview.add(self._create_playing_preview()) + elif item.get_name() == "rss": + self.preview.add(self._create_rss_preview()) + # when we update the rss preview menu, we must set it active if + # main menu is not + if not self.menu.active: + self.rss_preview_menu.set_active(True) + else: + update = False + + # If the preview was updated fade it in + if update: fade_in = clutter.Timeline(20, 60) alpha_in = clutter.Alpha(fade_in, clutter.smoothstep_inc_func) - self.in_behaviour = clutter.BehaviourOpacity(0x00, 0xff, alpha_in) - self.in_behaviour.apply(menu) - self.in_behaviour.apply(title) - self.in_behaviour.apply(icon) - - self.preview.add(icon) - self.preview.add(title) - self.rss_preview_menu = menu - self.preview.add(menu) - - menu.set_opacity(0x00) - icon.set_opacity(0x00) - title.set_opacity(0x00) - + self.behaviour = clutter.BehaviourOpacity(0x00, 0xff, alpha_in) + self.behaviour.apply(self.preview) fade_in.start() + def _can_move_horizontally(self): + '''Return a boolean indicating if horizontal movement is allowed.''' + item = self.menu.get_selected() + if self.feed_library.is_empty() or item.get_name() != 'rss': + return False else: - # No headlines available in the library - info = Label(0.07, "title", 0.1, 0.35, _("No headlines available")) - self.preview.add(info) - - def get_type(self): - """Return screen type.""" - return Screen.NORMAL - - def get_name(self): - """Return screen name (human readble)""" - return "Main" + return True def update(self): """ Update screen widgets. This is called always when screen is poped from the screen history. Updates main menu widget. """ - selected_name = self.menu.get_selected().get_name() - - self.remove(self.menu) - self.create_main_menu() - - index = self.menu.get_index(selected_name) - if not index == -1: - self.menu.select(index) - - self.menu.update() - - self.update_preview_area(self.menu.get_selected()) - - def update_preview_area(self, item): - """ - Update preview area. This area displayes information of currently - selected menuitem. - @param item: Menuitem (clutter.Actor) - """ - self.preview.remove_all() - if item.get_name() == "playing": - self.show_playing_preview() - #only show the rss menu if it has entries - elif item.get_name() == "rss": - self.show_rss_preview() + if self.media_player.is_playing() and \ + (self.menu.get_index("playing") == -1): + self.menu.add_item(_("Playing now..."), "playing") + elif not self.media_player.is_playing() and \ + (self.menu.get_index("playing") != -1): + self.menu.remove_item("playing") + + self.menu.refresh() + self._update_preview_area() def _move_menu(self, menu_direction): '''Move the menu in the given direction.''' @@ -252,43 +261,38 @@ scroll_direction = self.menu.scroll_up preview_direction = TextMenu.DOWN - if self.active_menu == "main": + if self.menu.active: scroll_direction() selected = self.menu.get_selected() if selected.get_name() == None: scroll_direction() selected = self.menu.get_selected() - self.update_preview_area(selected) + self._update_preview_area(selected) else: self.rss_preview_menu.move(preview_direction) def _handle_up(self): '''Handle UserEvent.NAVIGATE_UP.''' - self._move_menu(self.UP) + if self.menu.active: + self.menu.scroll_up() + else: + self.rss_preview_menu.move(TextMenu.UP) def _handle_down(self): '''Handle UserEvent.NAVIGATE_DOWN.''' - self._move_menu(self.DOWN) + if self.menu.active: + self.menu.scroll_down() + else: + self.rss_preview_menu.move(TextMenu.DOWN) def _handle_left(self): '''Handle UserEvent.NAVIGATE_LEFT.''' - if self.feed_library.is_empty(): - return - - if self.active_menu == "main": - self.active_menu = "preview" - self.menu.set_active(False) - self.rss_preview_menu.set_active(True) + if self._can_move_horizontally(): + self._preview_activation() def _handle_right(self): '''Handle UserEvent.NAVIGATE_RIGHT.''' - if self.feed_library.is_empty(): - return - - if self.active_menu == "preview": - self.active_menu = "main" - self.menu.set_active(True) - self.rss_preview_menu.set_active(False) + self.menu.active = True def _handle_select(self): '''Handle UserEvent.NAVIGATE_SELECT.''' @@ -309,10 +313,35 @@ elif item.get_name() == "photo": self.callback("photo_albums") elif item.get_name() == "rss": - if self.active_menu == "main": + if self.menu.active: self.callback("rss") else: menu_item = self.rss_preview_menu.get_current_menuitem() kwargs = menu_item.get_userdata() self.callback("entry", kwargs) + def _on_menu_moved(self, actor): + ''' + We update the preview area when the selected item changed on the + Main menu. + ''' + self._update_preview_area() + + def _on_menu_selected(self, actor): + ''' + We handle a *select command* if an item was selected. + ''' + self._handle_select() + + def _main_menu_activation(self, actor=None): + '''Handle the main menu activation.''' + self.menu.active = True + if self.rss_preview_menu: + self.rss_preview_menu.set_active(False) + + def _preview_activation(self, actor=None): + '''Handle the preview activation.''' + self.menu.active = False + if self.rss_preview_menu: + self.rss_preview_menu.set_active(True) + === modified file 'entertainerlib/frontend/gui/user_interface.py' --- entertainerlib/frontend/gui/user_interface.py 2009-03-22 21:00:43 +0000 +++ entertainerlib/frontend/gui/user_interface.py 2009-04-05 20:33:36 +0000 @@ -76,7 +76,10 @@ #problem clutter.set_use_mipmapped_text(False) + self.hide_cursor_timeout_key = None + self.stage.connect('key-press-event', self.handle_keyboard_event) + self.stage.connect('motion-event', self.handle_motion_event) self.stage.set_color(self.config.theme.get_color("background")) self.stage.set_size( self.config.get_stage_width(), self.config.get_stage_height()) @@ -177,7 +180,6 @@ def _fullscreen(self): '''Set the window, stage, and config to fullscreen dimensions.''' - self.stage.hide_cursor() self.window.fullscreen() self.stage.fullscreen() width, height = self.stage.get_size() @@ -204,6 +206,7 @@ def start_up(self): '''Start the user interface and make it visible.''' self.show() + self.stage.hide_cursor() self.current = self.create_screen("main") self.transition.forward_effect(None, self.current) self.enable_menu_overlay() @@ -217,7 +220,6 @@ if self.is_fullscreen: self.stage.unfullscreen() self.window.unfullscreen() - self.stage.show_cursor() self.config.set_stage_width(self.old_width) self.config.set_stage_height(self.old_height) self.is_fullscreen = False @@ -302,6 +304,22 @@ elif direction == Transition.BACKWARD: self.transition.backward_effect(from_screen, screen) + def hide_cursor_timeout_callback(self): + '''Hide the cursor''' + self.stage.hide_cursor() + return True + + def handle_motion_event(self, stage, clutter_event): + ''' + Show the cursor if it has been moved and start a timeout to + hide it after a few seconds. + ''' + self.stage.show_cursor() + if self.hide_cursor_timeout_key is not None: + gobject.source_remove(self.hide_cursor_timeout_key) + self.hide_cursor_timeout_key = gobject.timeout_add(3000, + self.hide_cursor_timeout_callback) + def handle_keyboard_event(self, stage, clutter_event, event_handler=None): '''Translate all received keyboard events to UserEvents.''' if event_handler is None: === added file 'entertainerlib/frontend/gui/widgets/motion_buffer.py' --- entertainerlib/frontend/gui/widgets/motion_buffer.py 1970-01-01 00:00:00 +0000 +++ entertainerlib/frontend/gui/widgets/motion_buffer.py 2009-04-14 19:43:51 +0000 @@ -0,0 +1,140 @@ +"""Tools for pointer motion calculations""" + +__licence__ = "GPLv2" +__copyright__ = "2009, Samuel Buffet" +__author__ = "Samuel Buffet