class ScrollArea(Base, clutter.Group): """Wrapper of a clutter Group that allows for scrolling. ScrollArea modifies the width of the content and it assumes that the content uses percent modification (read: not default clutter objects).""" __gsignals__ = { 'activated': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'moving': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } MODE_SELECTION = 0 MODE_MOTION = 1 MODE_STOP = 2 STEP_SIZE_PERCENT = 0.04 def __init__(self, x, y, width, height, content): Base.__init__(self) clutter.Group.__init__(self) self._motion_buffer = MotionBuffer() self._offset = 0 # Drives the content motion. self._offset_max = 0 # Maximum value of offset (equal on bottom). self._old_offset = 0 # Stores the old value of offset on motions. self._motion_handler = 0 self._active = None self.step_size = self.get_abs_y(self.STEP_SIZE_PERCENT) # Allowed area for the widget's scrolling content. self.area_width = self.get_abs_x(width) self.area_height = self.get_abs_y(height) # Create content position indicator self.indicator = ListIndicator(3 * width / 4, height, 0.2, 0.045, ListIndicator.VERTICAL) self.indicator.hide_position() self.indicator.set_maximum(2) self.add(self.indicator) # A clipped Group to receive the content. self._fixed_group = clutter.Group() self._fixed_group.set_clip(0, 0, self.area_width, self.area_height) self.add(self._fixed_group) self.content = None self._motion_timeline = clutter.Timeline(500) self._motion_timeline.connect('completed', self._motion_timeline_callback, None) self._motion_alpha = clutter.Alpha(self._motion_timeline, clutter.EASE_OUT_SINE) self._motion_behaviour = LoopedPathBehaviour(self._motion_alpha) self.set_content(content) self.active = None # Preparation to pointer events handling. self.set_reactive(True) self.connect('button-press-event', self._on_button_press_event) self.connect('button-release-event', self._on_button_release_event) self.connect('scroll-event', self._on_scroll_event) self.set_position(self.get_abs_x(x), self.get_abs_y(y)) @property def on_top(self): """True if we're on top.""" return self._offset == 0 @property def on_bottom(self): """True if we're on bottom.""" return self._offset == self._offset_max def _get_active(self): """Active property getter.""" return self._active def _set_active(self, boolean): """Active property setter.""" if self._active == boolean: return self._active = boolean if boolean: # Show indicator if there is need for scrolling. if self._offset_max >= 0: self.indicator.show() self.set_opacity(255) self.emit('activated') else: self.indicator.hide() self.set_opacity(128) active = property(_get_active, _set_active) def _get_offset(self): """Get current offset value.""" return self._offset def _set_offset(self, integer): """Set current offset value.""" if self._offset == integer: return self._offset = integer if self._offset < 0: self._offset = 0 elif self._offset > self._offset_max: self._offset = self._offset_max self.content.set_position(0, -self._offset) # Indicator updates. if self.on_top: self.indicator.set_current(1) elif self.on_bottom: self.indicator.set_current(2) offset = property(_get_offset, _set_offset) def set_content(self, content): """Set content into scroll area.""" if self.content is not None: self._fixed_group.remove(self.content) self._motion_behaviour.remove(self.content) self.content = content self._fixed_group.add(content) self._offset_max = self.content.get_height() - self.area_height self._motion_behaviour.apply(self.content) def stop_animation(self): """Stops the timeline driving animation.""" self._motion_timeline.stop() def scroll_to_top(self): """Scroll content back to top.""" self.offset = 0 def scroll_to_bottom(self): """Scroll content as much as possible.""" self.offset = self._offset_max def scroll_up(self): """Scroll up by one step size.""" self.offset -= self.step_size def scroll_down(self): """Scroll down by one step size.""" self.offset += self.step_size def scroll_page_up(self): """Scroll up by one page. Page is a scroll area height.""" self.offset -= self.area_height def scroll_page_down(self): self.offset += self.area_height def _update_motion_behaviour(self, target): """Preparation of looped behaviour applied to the content.""" self._motion_behaviour.start_knot = (0.0, -self.offset) self._motion_behaviour.end_knot = (0.0, -target) self._motion_behaviour.start_index = 0.0 # Need to set the end index to 0.9999. Indeed the LoopedPathBehaviour # uses an index in [0, 1[. So index = 1 is equivalent to index = 0, the # Actor will the be placed on the start_knot. self._motion_behaviour.end_index = 0.9999 def _on_button_press_event(self, actor, event): """button-press-event handler.""" clutter.grab_pointer(self) if not self.handler_is_connected(self._motion_handler): self._motion_handler = self.connect('motion-event', self._on_motion_event) if self._motion_timeline.is_playing(): # A click with an animation pending should stop the animation. self._motion_timeline.stop() # Go to MODE_STOP to handle correctly next button-release event. self._event_mode = self.MODE_STOP self.offset = -self.content.get_y() else: # No animation pending so we're going to do nothing or to move # all the content. self._old_offset = self.offset self._motion_buffer.start(event) self._event_mode = self.MODE_SELECTION return False def _on_button_release_event(self, actor, event): """button-release-event handler.""" clutter.ungrab_pointer() if self.handler_is_connected(self._motion_handler): self.disconnect_by_func(self._on_motion_event) self._motion_buffer.compute_from_last_motion_event(event) if not self.active: self.active = True return if self._event_mode == self.MODE_MOTION: speed = self._motion_buffer.speed_y_from_last_motion_event # Calculation of the new target according to vertical speed. target = self.offset - speed * 200 if target < 0: target = 0 elif target > self._offset_max: target = self._offset_max self._update_motion_behaviour(target) self._motion_timeline.start() return False def _on_motion_event(self, actor, event): """motion-event handler.""" # Minimum distance we to move before we consider a motion has started. motion_threshold = 10 self._motion_buffer.compute_from_start(event) if self._motion_buffer.distance_from_start > motion_threshold: self._motion_buffer.take_new_motion_event(event) self._event_mode = self.MODE_MOTION self.offset = self._old_offset - self._motion_buffer.dy_from_start return False def _on_scroll_event(self, actor, event): """scroll-event handler (mouse's wheel).""" if not self.active: self.active = True return # Do not scroll if there is no need. if self._offset_max < 0: return False if event.direction == clutter.SCROLL_DOWN: self.scroll_down() else: self.scroll_up() self.emit('moving') return False def _motion_timeline_callback(self, timeline, screen): """Code executed when the animation is finished.""" self.offset = -self.content.get_y()
class VideoClipsTab(Tab): """ Tab can be used as part of the TabGroup Tab is a very simple container that contains all the widgets and logic of the tab page. """ def __init__(self, media_player, video_library, move_to_new_screen_callback, name="clips", tab_title=_("Video clips")): Tab.__init__(self, name, tab_title, move_to_new_screen_callback) self.media_player = media_player self.video_library = video_library self.theme = self.config.theme self.list_indicator = None self.clip_info = None self.menu = None self.clip_title = None if self.video_library.get_number_of_video_clips() == 0: self._create_empty_library_notice() else: # Start the loading animation while the menu is loading self.throbber = LoadingAnimation(0.7, 0.1) self.throbber.show() self.add(self.throbber) self.menu = self._create_menu() self.add(self.menu) self.menu.connect("moved", self._update_clip_info) self.menu.connect("selected", self._handle_select) self.menu.connect("activated", self._on_activated) self.menu.connect("filled", self._on_menu_filled) self.connect('activated', self._on_activated) self.connect('deactivated', self._on_deactivated) def can_activate(self): """ Allow if we have some movies indexed. """ if self.video_library.get_number_of_video_clips() == 0: return False else: return True def _create_empty_library_notice(self): """ Create an information box that is displayed if there are no indexed movies. """ message = _( "There are no indexed Video Clips in the Entertainer media " "library. Please add some folders containing video clips " "to the Library using the configuration tool.") Tab.show_empty_tab_notice(self, _("No video clips available!"), message) def _create_menu(self): """ Create a view that is displayed when there are indexed clips in the video library. """ menu = ImageMenu(0.04, 0.16, 0.23, self.y_for_x(0.23) * 0.7) menu.items_per_col = 2 menu.visible_rows = 2 menu.visible_cols = 4 clips = self.video_library.get_video_clips() clips_list = [[clip.thumbnail_url, clip] for clip in clips] menu.async_add_clips(clips_list) # Create list indicator self.list_indicator = ListIndicator(0.7, 0.8, 0.2, 0.045, ListIndicator.HORIZONTAL) self.list_indicator.set_maximum(len(clips)) self.list_indicator.show() self.add(self.list_indicator) # Create information labels self.clip_title = Label(0.042, "title", 0.15, 0.77, "", font_weight="bold") self.clip_title.set_ellipsize(pango.ELLIPSIZE_END) self.clip_title.set_line_wrap(False) self.clip_title.width = 0.5 self.add(self.clip_title) self.clip_info = Label(0.034, "subtitle", 0.15, 0.85, "") self.clip_info.set_ellipsize(pango.ELLIPSIZE_END) self.clip_info.set_line_wrap(False) self.clip_info.width = 0.5 self.add(self.clip_info) return menu def _update_clip_info(self, event=None): '''Update the VideoClip information labels.''' if self.active: clip = self.menu.selected_userdata (folder, filename) = os.path.split(clip.filename) self.clip_title.set_text(filename) self.clip_info.set_text(folder) self.list_indicator.show() self.list_indicator.set_current(self.menu.selected_index + 1) else: self.clip_title.set_text("") self.clip_info.set_text("") self.list_indicator.hide() def _handle_up(self): '''Handle the up user event.''' if self.menu.on_top: return True # Move control back to tab bar else: self.menu.up() return False def _handle_down(self): '''Handle the down user event.''' self.menu.down() return False def _handle_left(self): '''Handle the left user event.''' self.menu.left() return False def _handle_right(self): '''Handle the right user event.''' self.menu.right() return False def _handle_select(self, event=None): '''Handle the select user event.''' clip = self.menu.selected_userdata self.media_player.set_media(clip) self.media_player.play() self.callback("video_osd") return False def _on_activated(self, event=None): '''Tab activated.''' if self.tab_group is not None: self.tab_group.active = False self.menu.active = True self.active = True self._update_clip_info() return False def _on_deactivated(self, event=None): '''Tab deactivated.''' self.active = False self.menu.active = False self._update_clip_info() return False def _on_menu_filled(self, event=None): '''Handles filled event.''' self.throbber.hide()
class MoviesTab(Tab): """ Tab can be used as part of the TabGroup Tab is a very simple container that contains all the widgets and logic of the tab page. """ def __init__(self, video_library, move_to_new_screen_callback, name="movies", tab_title=_("Movies")): Tab.__init__(self, name, tab_title, move_to_new_screen_callback) self.video_library = video_library self.theme = self.config.theme self.list_indicator = None self.movie_info = None self.menu = None self.movie_plot = None self.movie_title = None if self.video_library.get_number_of_movies() == 0: self._create_empty_library_notice() else: # Start the loading animation while the menu is loading self.throbber = LoadingAnimation(0.1, 0.1) self.throbber.show() self.add(self.throbber) self.menu = self._create_menu() self.add(self.menu) self.menu.connect("moved", self._update_movie_info) self.menu.connect("selected", self._handle_select) self.menu.connect("activated", self._on_activated) self.menu.connect("filled", self._on_menu_filled) self.connect('activated', self._on_activated) self.connect('deactivated', self._on_deactivated) def can_activate(self): """ Allow if we have some movies indexed. """ if self.video_library.get_number_of_movies() == 0: return False else: return True def _create_empty_library_notice(self): """ Create an information box that is displayed if there are no indexed movies. """ message = _( "There are no indexed movies in Entertainer media library. To " "add movies, click on 'content' button on toolbar and open " "'videos' tab. Now click on 'add' button and select some folders " "which contains movie files.") Tab.show_empty_tab_notice(self, _("No movies available!"), message) def _create_menu(self): """ Create a view that is displayed when there is indexed movies in the video library. """ #Create movie menu menu = ImageMenu(0.06, 0.18, 0.12, 0.25) menu.items_per_col = 2 menu.visible_rows = 2 menu.visible_cols = 7 movies = self.video_library.get_movies() movies_list = [[movie.cover_art_url, movie] for movie in movies] menu.async_add_videos(movies_list) # Create list indicator self.list_indicator = ListIndicator(0.75, 0.76, 0.2, 0.045, ListIndicator.HORIZONTAL) self.list_indicator.set_maximum(len(movies)) self.show() self.add(self.list_indicator) # Create information labels self.movie_title = Label(0.042, "title", 0.2, 0.75, "", font_weight="bold") self.movie_title.set_ellipsize(pango.ELLIPSIZE_END) self.movie_title.set_line_wrap(False) self.movie_title.width = 0.5 self.add(self.movie_title) self.movie_info = Label(0.034, "subtitle", 0.2, 0.8, "") self.movie_info.set_ellipsize(pango.ELLIPSIZE_END) self.movie_info.set_line_wrap(False) self.movie_info.width = 0.5 self.add(self.movie_info) self.movie_plot = Label(0.025, "subtitle", 0.2, 0.85, "") self.movie_plot.width = 0.7 self.add(self.movie_plot) return menu def _update_movie_info(self, event=None): '''Update the movie information labels.''' if self.active: movie = self.menu.selected_userdata genres = movie.genres if len(genres) > 1: genre = genres[0] + "/" + genres[1] else: genre = genres[0] self.movie_title.set_text(_("%(title)s (%(year)s)") % \ {'title': movie.title, 'year': movie.year}) self.movie_info.set_text(_("%(min)d min, (%(genre)s)") % \ {'min': movie.runtime, 'genre': genre}) self.movie_plot.set_text(movie.short_plot) self.list_indicator.show() self.list_indicator.set_current(self.menu.selected_index + 1) else: self.movie_title.set_text("") self.movie_info.set_text("") self.movie_plot.set_text("") self.list_indicator.hide() def _handle_up(self): '''Handle the up user event.''' if self.menu.on_top: return True # Move control back to tab bar else: self.menu.up() return False def _handle_down(self): '''Handle the down user event.''' self.menu.down() return False def _handle_left(self): '''Handle the left user event.''' self.menu.left() return False def _handle_right(self): '''Handle the right user event.''' self.menu.right() return False def _handle_select(self, event=None): '''Handle the select user event.''' movie = self.menu.selected_userdata kwargs = { 'movie' : movie } self.callback("movie", kwargs) return False def _on_activated(self, event=None): '''Tab activated.''' if self.tab_group is not None: self.tab_group.active = False self.menu.active = True self.active = True self._update_movie_info() return False def _on_deactivated(self, event=None): '''Tab deactivated.''' self.active = False self.menu.active = False self._update_movie_info() return False def _on_menu_filled(self, event=None): '''Handles filled event.''' self.throbber.hide()
class AlbumsTab(Tab): '''Tab to show album listings''' def __init__(self, albums, move_to_new_screen_callback, name="albums", tab_title=_("Albums")): Tab.__init__(self, name, tab_title, move_to_new_screen_callback) # Start the loading animation while the menu is loading self.throbber = LoadingAnimation(0.6, 0.1) self.throbber.show() self.add(self.throbber) if len(albums) < 4: x_percent = 0.2928 visible_rows = 1 visible_cols = 3 elif len(albums) < 13: x_percent = 0.1464 visible_rows = 2 visible_cols = 6 else: x_percent = 0.1098 visible_rows = 3 visible_cols = 8 # Create albums menu self.menu = ImageMenu(0.07, 0.16, x_percent, self.y_for_x(x_percent)) self.menu.visible_rows = visible_rows self.menu.visible_cols = visible_cols self.menu.items_per_col = self.menu.visible_rows self.add(self.menu) albums_list = [[album.album_art_url, album] for album in albums] self.menu.async_add_albums(albums_list) self.li = ListIndicator(0.77, 0.8, 0.18, 0.045, ListIndicator.HORIZONTAL) self.li.set_maximum(len(albums)) self.li.show() self.add(self.li) # Create album information (displays current menuitem information) self.album_title = Label(0.045, "title", 0.22, 0.79, "") self.album_title.set_ellipsize(pango.ELLIPSIZE_END) self.album_title.set_line_wrap(False) self.album_title.width = 0.366 self.add(self.album_title) self.album_artist = Label(0.037, "subtitle", 0.22, 0.86, "") self.album_artist.set_ellipsize(pango.ELLIPSIZE_END) self.album_artist.set_line_wrap(False) self.album_artist.width = 0.366 self.add(self.album_artist) self.album_tracks = Label(0.037, "subtitle", 0.22, 0.91, "") self.add(self.album_tracks) self.connect('activated', self._on_activated) self.connect('deactivated', self._on_deactivated) self.menu.connect("moved", self._update_album_info) self.menu.connect("selected", self._handle_select) self.menu.connect("activated", self._on_activated) self.menu.connect("filled", self._on_menu_filled) def can_activate(self): '''Albums tab will always be created from an existing artist with at least one album.''' return True def _update_album_info(self, event=None): '''Update the album information labels.''' if self.active: album = self.menu.selected_userdata self.album_title.set_text(album.title) self.album_artist.set_text(album.artist) self.album_tracks.set_text(_("%(total)s tracks") % \ {'total': len(album.tracks)}) self.li.show() self.li.set_current(self.menu.selected_index + 1) else: self.album_title.set_text("") self.album_artist.set_text("") self.album_tracks.set_text("") self.li.hide() def _handle_up(self): '''Handle the up user event.''' if self.menu.on_top: return True # Move control back to tab bar else: self.menu.up() return False def _handle_down(self): '''Handle the down user event.''' self.menu.down() return False def _handle_left(self): '''Handle the left user event.''' self.menu.left() return False def _handle_right(self): '''Handle the right user event.''' self.menu.right() return False def _handle_select(self, event=None): '''Handle the select user event.''' album = self.menu.selected_userdata kwargs = { 'album' : album } self.callback("album", kwargs) return False def _on_activated(self, event=None): '''Tab activated.''' if self.tab_group is not None: self.tab_group.active = False self.menu.active = True self.active = True self._update_album_info() return False def _on_deactivated(self, event=None): '''Tab deactivated.''' self.active = False self.menu.active = False self._update_album_info() return False def _on_menu_filled(self, event=None): '''Handles filled event.''' self.throbber.hide()
class AlbumsTab(Tab): '''Tab to show album listings''' def __init__(self, albums, move_to_new_screen_callback, name="albums", tab_title=_("Albums")): Tab.__init__(self, name, tab_title, move_to_new_screen_callback) # Start the loading animation while the menu is loading self.throbber = LoadingAnimation(0.6, 0.1) self.throbber.show() self.add(self.throbber) if len(albums) < 4: x_percent = 0.2928 visible_rows = 1 visible_cols = 3 elif len(albums) < 13: x_percent = 0.1464 visible_rows = 2 visible_cols = 6 else: x_percent = 0.1098 visible_rows = 3 visible_cols = 8 # Create albums menu self.menu = ImageMenu(0.07, 0.16, x_percent, self.y_for_x(x_percent)) self.menu.visible_rows = visible_rows self.menu.visible_cols = visible_cols self.menu.items_per_col = self.menu.visible_rows self.add(self.menu) albums_list = [[album.album_art_url, album] for album in albums] self.menu.async_add_albums(albums_list) self.li = ListIndicator(0.77, 0.8, 0.18, 0.045, ListIndicator.HORIZONTAL) self.li.set_maximum(len(albums)) self.li.show() self.add(self.li) # Create album information (displays current menuitem information) self.album_title = Label(0.045, "title", 0.22, 0.79, "") self.album_title.set_ellipsize(pango.ELLIPSIZE_END) self.album_title.set_line_wrap(False) self.album_title.width = 0.366 self.add(self.album_title) self.album_artist = Label(0.037, "subtitle", 0.22, 0.86, "") self.album_artist.set_ellipsize(pango.ELLIPSIZE_END) self.album_artist.set_line_wrap(False) self.album_artist.width = 0.366 self.add(self.album_artist) self.album_tracks = Label(0.037, "subtitle", 0.22, 0.91, "") self.add(self.album_tracks) self.connect('activated', self._on_activated) self.connect('deactivated', self._on_deactivated) self.menu.connect("moved", self._update_album_info) self.menu.connect("selected", self._handle_select) self.menu.connect("activated", self._on_activated) self.menu.connect("filled", self._on_menu_filled) def can_activate(self): '''Albums tab will always be created from an existing artist with at least one album.''' return True def _update_album_info(self, event=None): '''Update the album information labels.''' if self.active: album = self.menu.selected_userdata self.album_title.set_text(album.title) self.album_artist.set_text(album.artist) self.album_tracks.set_text(_("%(total)s tracks") % \ {'total': len(album.tracks)}) self.li.show() self.li.set_current(self.menu.selected_index + 1) else: self.album_title.set_text("") self.album_artist.set_text("") self.album_tracks.set_text("") self.li.hide() def _handle_up(self): '''Handle the up user event.''' if self.menu.on_top: return True # Move control back to tab bar else: self.menu.up() return False def _handle_down(self): '''Handle the down user event.''' self.menu.down() return False def _handle_left(self): '''Handle the left user event.''' self.menu.left() return False def _handle_right(self): '''Handle the right user event.''' self.menu.right() return False def _handle_select(self, event=None): '''Handle the select user event.''' album = self.menu.selected_userdata kwargs = {'album': album} self.callback("album", kwargs) return False def _on_activated(self, event=None): '''Tab activated.''' if self.tab_group is not None: self.tab_group.active = False self.menu.active = True self.active = True self._update_album_info() return False def _on_deactivated(self, event=None): '''Tab deactivated.''' self.active = False self.menu.active = False self._update_album_info() return False def _on_menu_filled(self, event=None): '''Handles filled event.''' self.throbber.hide()
class ArtistsTab(Tab): '''Tab for the music screen to show artist listings''' def __init__(self, music_library, artists, move_to_new_screen_callback, name="artists", tab_title=_("Artists")): Tab.__init__(self, name, tab_title, move_to_new_screen_callback) self.library = music_library # Start the loading animation while the menu is loading self.throbber = LoadingAnimation(0.1, 0.1) self.throbber.show() self.add(self.throbber) self.menu = TextMenu(0.057, 0.208, 0.293, 0.078) self.menu.items_per_row = 3 self.menu.visible_rows = 7 self.menu.visible_cols = 3 self.menu.active = False self.menu.cursor = None self.add(self.menu) artists_list = [[artist, None, artist] for artist in artists] self.menu.async_add_artists(artists_list) # Create artist label self.artist_title = Label(0.0416, "title", 0.22, 0.794, "") self.artist_title.set_ellipsize(pango.ELLIPSIZE_END) self.artist_title.set_line_wrap(False) self.artist_title.width = 0.366 self.add(self.artist_title) self.artist_albums = Label(0.0365, "subtitle", 0.22, 0.86, "") self.add(self.artist_albums) self.artist_tracks = Label(0.0365, "subtitle", 0.22, 0.911, "") self.add(self.artist_tracks) # Create artist menu list indicator self.li = ListIndicator(0.77, 0.8, 0.18, 0.045, ListIndicator.VERTICAL) self.li.set_maximum(len(artists)) self.add(self.li) self.connect('activated', self._on_activated) self.connect('deactivated', self._on_deactivated) self.menu.connect("moved", self._update_artist_info) self.menu.connect("selected", self._handle_select) self.menu.connect("activated", self._on_activated) self.menu.connect("filled", self._on_menu_filled) def can_activate(self): '''Albums tab will always be created from an existing artist with at least one album.''' return True def _update_artist_info(self, event=None): '''Update the artist information labels''' if self.active: artist = self.menu.selected_userdata self.artist_title.set_text(artist) self.artist_albums.set_text(_("%(albums)d albums") % {'albums': self.library.number_of_albums_by_artist(artist)}) self.artist_tracks.set_text(_("%(tracks)d tracks") % {'tracks': self.library.number_of_tracks_by_artist(artist)}) self.li.show() self.li.set_current(self.menu.selected_index + 1) else: self.artist_title.set_text("") self.artist_albums.set_text("") self.artist_tracks.set_text("") self.li.hide() def _handle_up(self): '''Handle the up user event.''' if self.menu.on_top: return True # Move control back to tab bar else: self.menu.up() return False def _handle_down(self): '''Handle the down user event.''' self.menu.down() return False def _handle_left(self): '''Handle the left user event.''' self.menu.left() return False def _handle_right(self): '''Handle the right user event.''' self.menu.right() return False def _handle_select(self, event=None): '''Handle the select user event.''' artist = self.menu.selected_userdata kwargs = { 'artist' : artist } self.callback("artist", kwargs) return False def _on_activated(self, event=None): '''Tab activated.''' if self.tab_group is not None: self.tab_group.active = False self.menu.active = True self.active = True self._update_artist_info() return False def _on_deactivated(self, event=None): '''Tab deactivated.''' self.active = False self.menu.active = False self._update_artist_info() def _on_menu_filled(self, event=None): '''Handles filled event.''' self.throbber.hide()
class TracksTab(Tab): '''Tab for the artist screen to show track listings''' def __init__(self, tracks, move_to_new_screen_callback, name="tracks", tab_title=_("Tracks")): Tab.__init__(self, name, tab_title, move_to_new_screen_callback) # Start the loading animation while the menu is loading self.throbber = LoadingAnimation(0.1, 0.1) self.throbber.show() self.add(self.throbber) self.menu = TextMenu(0.0586, 0.2083, 0.2928, 0.0781) self.menu.items_per_row = 3 self.menu.visible_rows = 7 self.menu.visible_cols = 3 self.menu.active = False self.menu.cursor = None self.add(self.menu) tracks_list = [[track.title, None, track] for track in tracks] self.menu.async_add_artists(tracks_list) self.track_title = Label(0.045, "title", 0.22, 0.79, "") self.track_title.set_ellipsize(pango.ELLIPSIZE_END) self.track_title.set_line_wrap(False) self.track_title.width = 0.366 self.add(self.track_title) self.track_number = Label(0.037, "subtitle", 0.22, 0.86, "") self.track_number.set_ellipsize(pango.ELLIPSIZE_END) self.track_number.set_line_wrap(False) self.track_number.width = 0.366 self.add(self.track_number) self.track_length = Label(0.037, "subtitle", 0.22, 0.91, "") self.add(self.track_length) self.li = ListIndicator(0.77, 0.8, 0.18, 0.045, ListIndicator.VERTICAL) self.li.set_maximum(len(tracks)) self.li.show() self.add(self.li) self.connect('activated', self._on_activated) self.connect('deactivated', self._on_deactivated) self.menu.connect("moved", self._update_track_info) self.menu.connect("selected", self._handle_select) self.menu.connect("activated", self._on_activated) self.menu.connect("filled", self._on_menu_filled) def can_activate(self): '''Tracks tab will always be created from an existing artist with at least one track.''' return True def _update_track_info(self, event=None): '''Update the track information labels''' if self.active: track = self.menu.selected_userdata self.track_title.set_text(track.title) self.track_length.set_text(track.length_string) self.track_number.set_text(_("Track %(track)d from %(album)s") % \ {'track': track.number, 'album': track.album.title}) self.li.show() self.li.set_current(self.menu.selected_index + 1) else: self.track_title.set_text("") self.track_length.set_text("") self.track_number.set_text("") self.li.hide() def _handle_up(self): '''Handle the up user event.''' if self.menu.on_top: return True # Move control back to tab bar else: self.menu.up() return False def _handle_down(self): '''Handle the down user event.''' self.menu.down() return False def _handle_left(self): '''Handle the left user event.''' self.menu.left() return False def _handle_right(self): '''Handle the right user event.''' self.menu.right() return False def _handle_select(self, event=None): '''Handle the select user event.''' track = self.menu.selected_userdata kwargs = { 'track' : track } self.callback("audio_play", kwargs) return False def _on_activated(self, event=None): '''Tab activated.''' if self.tab_group is not None: self.tab_group.active = False self.menu.active = True self.active = True self._update_track_info() return False def _on_deactivated(self, event=None): '''Tab deactivated.''' self.active = False self.menu.active = False self._update_track_info() return False def _on_menu_filled(self, event=None): '''Handles filled event.''' self.throbber.hide()
class ScrollArea(Base, clutter.Group): """Wrapper of a clutter Group that allows for scrolling. ScrollArea modifies the width of the content and it assumes that the content uses percent modification (read: not default clutter objects).""" __gsignals__ = { 'activated' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ), 'moving' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ), } MODE_SELECTION = 0 MODE_MOTION = 1 MODE_STOP = 2 STEP_SIZE_PERCENT = 0.04 def __init__(self, x, y, width, height, content): Base.__init__(self) clutter.Group.__init__(self) self._motion_buffer = MotionBuffer() self._offset = 0 # Drives the content motion. self._offset_max = 0 # Maximum value of offset (equal on bottom). self._old_offset = 0 # Stores the old value of offset on motions. self._motion_handler = 0 self._active = None self.step_size = self.get_abs_y(self.STEP_SIZE_PERCENT) # Allowed area for the widget's scrolling content. self.area_width = self.get_abs_x(width) self.area_height = self.get_abs_y(height) # Create content position indicator self.indicator = ListIndicator(3 * width / 4, height, 0.2, 0.045, ListIndicator.VERTICAL) self.indicator.hide_position() self.indicator.set_maximum(2) self.add(self.indicator) # A clipped Group to receive the content. self._fixed_group = clutter.Group() self._fixed_group.set_clip(0, 0, self.area_width, self.area_height) self.add(self._fixed_group) self.content = None self._motion_timeline = clutter.Timeline(500) self._motion_timeline.connect('completed', self._motion_timeline_callback, None) self._motion_alpha = clutter.Alpha(self._motion_timeline, clutter.EASE_OUT_SINE) self._motion_behaviour = LoopedPathBehaviour(self._motion_alpha) self.set_content(content) self.active = None # Preparation to pointer events handling. self.set_reactive(True) self.connect('button-press-event', self._on_button_press_event) self.connect('button-release-event', self._on_button_release_event) self.connect('scroll-event', self._on_scroll_event) self.set_position(self.get_abs_x(x), self.get_abs_y(y)) @property def on_top(self): """True if we're on top.""" return self._offset == 0 @property def on_bottom(self): """True if we're on bottom.""" return self._offset == self._offset_max def _get_active(self): """Active property getter.""" return self._active def _set_active(self, boolean): """Active property setter.""" if self._active == boolean: return self._active = boolean if boolean: # Show indicator if there is need for scrolling. if self._offset_max >= 0: self.indicator.show() self.set_opacity(255) self.emit('activated') else: self.indicator.hide() self.set_opacity(128) active = property(_get_active, _set_active) def _get_offset(self): """Get current offset value.""" return self._offset def _set_offset(self, integer): """Set current offset value.""" if self._offset == integer: return self._offset = integer if self._offset < 0: self._offset = 0 elif self._offset > self._offset_max: self._offset = self._offset_max self.content.set_position(0, - self._offset) # Indicator updates. if self.on_top: self.indicator.set_current(1) elif self.on_bottom: self.indicator.set_current(2) offset = property(_get_offset, _set_offset) def set_content(self, content): """Set content into scroll area.""" if self.content is not None: self._fixed_group.remove(self.content) self._motion_behaviour.remove(self.content) self.content = content self._fixed_group.add(content) self._offset_max = self.content.get_height() - self.area_height self._motion_behaviour.apply(self.content) def stop_animation(self): """Stops the timeline driving animation.""" self._motion_timeline.stop() def scroll_to_top(self): """Scroll content back to top.""" self.offset = 0 def scroll_to_bottom(self): """Scroll content as much as possible.""" self.offset = self._offset_max def scroll_up(self): """Scroll up by one step size.""" self.offset -= self.step_size def scroll_down(self): """Scroll down by one step size.""" self.offset += self.step_size def scroll_page_up(self): """Scroll up by one page. Page is a scroll area height.""" self.offset -= self.area_height def scroll_page_down(self): self.offset += self.area_height def _update_motion_behaviour(self, target): """Preparation of looped behaviour applied to the content.""" self._motion_behaviour.start_knot = (0.0, -self.offset) self._motion_behaviour.end_knot = (0.0, -target) self._motion_behaviour.start_index = 0.0 # Need to set the end index to 0.9999. Indeed the LoopedPathBehaviour # uses an index in [0, 1[. So index = 1 is equivalent to index = 0, the # Actor will the be placed on the start_knot. self._motion_behaviour.end_index = 0.9999 def _on_button_press_event(self, actor, event): """button-press-event handler.""" clutter.grab_pointer(self) if not self.handler_is_connected(self._motion_handler): self._motion_handler = self.connect('motion-event', self._on_motion_event) if self._motion_timeline.is_playing(): # A click with an animation pending should stop the animation. self._motion_timeline.stop() # Go to MODE_STOP to handle correctly next button-release event. self._event_mode = self.MODE_STOP self.offset = -self.content.get_y() else: # No animation pending so we're going to do nothing or to move # all the content. self._old_offset = self.offset self._motion_buffer.start(event) self._event_mode = self.MODE_SELECTION return False def _on_button_release_event(self, actor, event): """button-release-event handler.""" clutter.ungrab_pointer() if self.handler_is_connected(self._motion_handler): self.disconnect_by_func(self._on_motion_event) self._motion_buffer.compute_from_last_motion_event(event) if not self.active: self.active = True return if self._event_mode == self.MODE_MOTION: speed = self._motion_buffer.speed_y_from_last_motion_event # Calculation of the new target according to vertical speed. target = self.offset - speed * 200 if target < 0: target = 0 elif target > self._offset_max: target = self._offset_max self._update_motion_behaviour(target) self._motion_timeline.start() return False def _on_motion_event(self, actor, event): """motion-event handler.""" # Minimum distance we to move before we consider a motion has started. motion_threshold = 10 self._motion_buffer.compute_from_start(event) if self._motion_buffer.distance_from_start > motion_threshold: self._motion_buffer.take_new_motion_event(event) self._event_mode = self.MODE_MOTION self.offset = self._old_offset - self._motion_buffer.dy_from_start return False def _on_scroll_event(self, actor, event): """scroll-event handler (mouse's wheel).""" if not self.active: self.active = True return # Do not scroll if there is no need. if self._offset_max < 0: return False if event.direction == clutter.SCROLL_DOWN: self.scroll_down() else: self.scroll_up() self.emit('moving') return False def _motion_timeline_callback(self, timeline, screen): """Code executed when the animation is finished.""" self.offset = -self.content.get_y()
class MoviesTab(Tab): """ Tab can be used as part of the TabGroup Tab is a very simple container that contains all the widgets and logic of the tab page. """ def __init__(self, video_library, move_to_new_screen_callback, name="movies", tab_title=_("Movies")): Tab.__init__(self, name, tab_title, move_to_new_screen_callback) self.video_library = video_library self.theme = self.config.theme self.list_indicator = None self.movie_info = None self.menu = None self.movie_plot = None self.movie_title = None if self.video_library.get_number_of_movies() == 0: self._create_empty_library_notice() else: # Start the loading animation while the menu is loading self.throbber = LoadingAnimation(0.1, 0.1) self.throbber.show() self.add(self.throbber) self.menu = self._create_menu() self.add(self.menu) self.menu.connect("moved", self._update_movie_info) self.menu.connect("selected", self._handle_select) self.menu.connect("activated", self._on_activated) self.menu.connect("filled", self._on_menu_filled) self.connect('activated', self._on_activated) self.connect('deactivated', self._on_deactivated) def can_activate(self): """ Allow if we have some movies indexed. """ if self.video_library.get_number_of_movies() == 0: return False else: return True def _create_empty_library_notice(self): """ Create an information box that is displayed if there are no indexed movies. """ message = _( "There are no indexed movies in Entertainer media library. To " "add movies, click on 'content' button on toolbar and open " "'videos' tab. Now click on 'add' button and select some folders " "which contains movie files.") Tab.show_empty_tab_notice(self, _("No movies available!"), message) def _create_menu(self): """ Create a view that is displayed when there is indexed movies in the video library. """ #Create movie menu menu = ImageMenu(0.06, 0.18, 0.12, 0.25) menu.items_per_col = 2 menu.visible_rows = 2 menu.visible_cols = 7 movies = self.video_library.get_movies() movies_list = [[movie.cover_art_url, movie] for movie in movies] menu.async_add_videos(movies_list) # Create list indicator self.list_indicator = ListIndicator(0.75, 0.76, 0.2, 0.045, ListIndicator.HORIZONTAL) self.list_indicator.set_maximum(len(movies)) self.show() self.add(self.list_indicator) # Create information labels self.movie_title = Label(0.042, "title", 0.2, 0.75, "", font_weight="bold") self.movie_title.set_ellipsize(pango.ELLIPSIZE_END) self.movie_title.set_line_wrap(False) self.movie_title.width = 0.5 self.add(self.movie_title) self.movie_info = Label(0.034, "subtitle", 0.2, 0.8, "") self.movie_info.set_ellipsize(pango.ELLIPSIZE_END) self.movie_info.set_line_wrap(False) self.movie_info.width = 0.5 self.add(self.movie_info) self.movie_plot = Label(0.025, "subtitle", 0.2, 0.85, "") self.movie_plot.width = 0.7 self.add(self.movie_plot) return menu def _update_movie_info(self, event=None): '''Update the movie information labels.''' if self.active: movie = self.menu.selected_userdata genres = movie.genres if len(genres) > 1: genre = genres[0] + "/" + genres[1] else: genre = genres[0] self.movie_title.set_text(_("%(title)s (%(year)s)") % \ {'title': movie.title, 'year': movie.year}) self.movie_info.set_text(_("%(min)d min, (%(genre)s)") % \ {'min': movie.runtime, 'genre': genre}) self.movie_plot.set_text(movie.short_plot) self.list_indicator.show() self.list_indicator.set_current(self.menu.selected_index + 1) else: self.movie_title.set_text("") self.movie_info.set_text("") self.movie_plot.set_text("") self.list_indicator.hide() def _handle_up(self): '''Handle the up user event.''' if self.menu.on_top: return True # Move control back to tab bar else: self.menu.up() return False def _handle_down(self): '''Handle the down user event.''' self.menu.down() return False def _handle_left(self): '''Handle the left user event.''' self.menu.left() return False def _handle_right(self): '''Handle the right user event.''' self.menu.right() return False def _handle_select(self, event=None): '''Handle the select user event.''' movie = self.menu.selected_userdata kwargs = {'movie': movie} self.callback("movie", kwargs) return False def _on_activated(self, event=None): '''Tab activated.''' if self.tab_group is not None: self.tab_group.active = False self.menu.active = True self.active = True self._update_movie_info() return False def _on_deactivated(self, event=None): '''Tab deactivated.''' self.active = False self.menu.active = False self._update_movie_info() return False def _on_menu_filled(self, event=None): '''Handles filled event.''' self.throbber.hide()