class ListIndicatorTest(EntertainerTest): '''Test for entertainerlib.gui.widgets.list_indicator.''' def setUp(self): '''Set up the test.''' EntertainerTest.setUp(self) self.indicator = ListIndicator(0.7, 0.8, 0.2, 0.045, ListIndicator.HORIZONTAL) def test_create(self): '''Test correct ListIndicator initialization.''' self.assertTrue(isinstance(self.indicator, ListIndicator)) def test_currentmax(self): '''Test methods to handle the current and maximum attributes.''' self.indicator.set_current(10) self.assertEqual(self.indicator.get_current(), 1) self.indicator.set_current(-99) self.assertEqual(self.indicator.get_current(), 1) self.indicator.set_maximum(10) self.indicator.set_current(5) self.assertEqual(self.indicator.get_current(), 5) def test_setdelimiter(self): '''Test the set_delimiter method.''' self.indicator.set_maximum(100) self.indicator.set_current(50) self.indicator.set_delimiter(" * ") self.assertEqual(self.indicator.text.get_text(), "50 * 100")
class Album(Screen): '''Screen that allows user to browse and play tracks of the music album.''' def __init__(self, media_player, music_library, move_to_new_screen_callback, album): Screen.__init__(self, 'Album', move_to_new_screen_callback) self.media_player = media_player self.theme = self.config.theme self.library = music_library self.album = album self.art = None self.track_menu = None # Create and initialize screen items self.track_menu = self._create_track_menu() self.add(self.track_menu) self._create_album_cover_texture() self._create_album_info() self.screen_title = Label(0.13, "screentitle", 0, 0.87, "") self.screen_title.set_ellipsize(pango.ELLIPSIZE_END) self.screen_title.width = 0.8 self.add(self.screen_title) #List indicator self.li = ListIndicator(0.74, 0.85, 0.2, 0.045, ListIndicator.VERTICAL) self.li.set_maximum(len(self.album.tracks)) self.add(self.li) self.track_menu.active = True self.track_menu.connect('selected', self._on_menu_selected) self.track_menu.connect('moved', self._display_selected_track) def _create_album_cover_texture(self): """ Create a texture that is displayed next to track list. This texture displays album cover art. """ if(self.album.has_album_art()): pixbuf = gtk.gdk.pixbuf_new_from_file(self.album.album_art_url) else: pixbuf = gtk.gdk.pixbuf_new_from_file( self.theme.getImage("default_album_art")) self.art = EyeCandyTexture(0.1, 0.13, 0.3148, 0.5599, pixbuf) self.art.set_rotation(clutter.Y_AXIS, 25, 0, 0, 0) self.add(self.art) def _create_album_info(self): """ Create album info labels. """ if self.album.year != 0: album_text = self.album.title + ", " + str(self.album.year) else: album_text = self.album.title album = Label(0.0416, "text", 0.5, 0.13, album_text, font_weight="bold") album.set_ellipsize(pango.ELLIPSIZE_END) album.set_line_wrap(False) album.width = 0.45 self.add(album) length = str(self.album.length / 60) num_of_tracks_text = _("%(total)s tracks, %(time)s minutes") % \ {'total': len(self.album.tracks), 'time': length} num_of_tracks = Label(0.028, "subtitle", 0.5, 0.18, num_of_tracks_text, font_weight="bold") self.add(num_of_tracks) def _create_track_menu(self): """ Create track menu. This menu contains list of all tracks on album. """ menu = TextMenu(0.4978, 0.2344, 0.4393, 0.0781) tracks = self.album.tracks tracks_list = [[track.title, track.length_string, track] \ for track in tracks] menu.async_add(tracks_list) return menu def is_interested_in_play_action(self): """ Override function from Screen class. See Screen class for better documentation. """ return True def execute_play_action(self): """ Override function from Screen class. See Screen class for better documentation. """ track = self.track_menu.selected_userdata self.media_player.set_media(track) self.media_player.play() def _handle_up(self): '''Handle UserEvent.NAVIGATE_UP.''' self.track_menu.up() def _handle_down(self): '''Handle UserEvent.NAVIGATE_DOWN.''' self.track_menu.down() def _handle_select(self, event=None): '''Handle UserEvent.NAVIGATE_SELECT.''' track = self.track_menu.selected_userdata kwargs = { 'track' : track } self.callback("audio_play", kwargs) def _on_menu_selected(self, actor=None): '''Handle a *select command* if an item was selected.''' self._handle_select() def _display_selected_track(self, actor=None): '''Update of the list indicator and the screen's title''' self.li.set_current(self.track_menu.selected_index + 1) track = self.track_menu.selected_userdata self.screen_title.set_text(track.artist) self.screen_title.show()
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 PhotoAlbums(Screen): '''Screen contains a list of photo albums and album previews.''' def __init__(self, image_library, move_to_new_screen_callback): Screen.__init__(self, 'PhotoAlbums', move_to_new_screen_callback) self.theme = self.config.theme self.image_library = image_library self.timeout = None # Timeout key (this is used when scrolling menu) # Screen Title (Displayed at the bottom left corner) screen_title = Label(0.13, "screentitle", 0, 0.87, _("Photographs")) self.add(screen_title) if self.image_library.get_number_of_albums() == 0: self._create_no_photos_information() else: # Album preview group self.preview = clutter.Group() self.preview.set_position(self.get_abs_x(0.07), self.get_abs_y(0.1953)) self.preview.show() self.add(self.preview) self.preview_fade = None self.menu = None self.in_behaviour = None self.out_behaviour = None self.in_opacity = None self.out_opacity = None self.preview_textures = None self.menu = self._create_album_menu() self.add(self.menu) self.li = None self._create_list_indicator() self._update_album_preview(self.menu.selected_userdata) self.menu.connect('selected', self._handle_select) self.menu.connect('moved', self._display_selected_album) def _create_no_photos_information(self): """ Create textures and labels for information screen. This is displayed instead of album list if there are no photos available and it helps users to add new photographs to the system. """ # Create warning icon warning_icon = Texture(self.theme.getImage("warning_icon"), 0.28, 0.27) self.add(warning_icon) # Create warning title info_title = Label(0.0625, "title", 0.3367, 0.2709, _("No photographs available!")) self.add(info_title) # Create warning help text message = _( "There are no indexed photographs in the Entertainer media " "library. To add photographs, start the Content management tool " "and open the 'Images' tab. Now click on the 'Add' button and " "select some folders which contain image files.") info = Label(0.0417, "menuitem_inactive", 0.2804, 0.45, message) info.set_size(0.5, 0.5859) self.add(info) def _create_album_menu(self): """ Create ImageAlbum-menu. This menu contains list of albums. It also displays number of photographs per album. """ menu = TextMenu(0.5271, 0.3385, 0.4393, 0.0781) menu.visible_rows = 7 albums = self.image_library.get_albums() albums_list = [[album.get_title(), str(album.get_number_of_images()), album] for album in albums if album.get_number_of_images() != 0] menu.async_add(albums_list) menu.active = True return menu def _create_album_preview(self, album): """ Create a clutter.Group that contains album preview actors. """ group = clutter.Group() group.set_position(self.get_abs_x(0.07), self.get_abs_y(0.1953)) # Preview images images = album.get_preview_images(3) self.preview_textures = [] max_w = 0.4026 max_h = 0.5599 abs_max_w = self.get_abs_x(max_w) abs_max_h = self.get_abs_y(max_h) for image in images: pix_buffer = gtk.gdk.pixbuf_new_from_file(image.get_thumbnail_url()) ratio = float(pix_buffer.get_width()) ratio /= float(pix_buffer.get_height()) # Resize and center preview texture if ratio > 1: texture = EyeCandyTexture(0.0, 0.0, max_w, max_h / ratio, pix_buffer) new_y = int((abs_max_h - abs_max_h / ratio) / 2.0) texture.set_position(0, new_y) else: texture = EyeCandyTexture(0.0, 0.0, max_w * ratio, max_h, pix_buffer) new_x = int((abs_max_w - abs_max_w * ratio) / 2.0) texture.set_position(new_x, 0) texture.set_rotation(clutter.Y_AXIS, 25, 0, 0, 0) texture.set_opacity(0) self.preview_textures.append(texture) group.add(texture) self.preview_textures[0].set_opacity(255) title = Label(0.03646, "title", 0.4649, 0, album.get_title(), font_weight="bold") title.width = 0.4758 title.set_ellipsize(pango.ELLIPSIZE_END) group.add(title) desc = Label(0.026, "subtitle", 0.4649, 0.0521, album.get_description()) desc.width = 0.4758 group.add(desc) return group def _update_album_preview(self, album): """ Update album preview. Display preview images from the current album. @param album: Currently selected album in menu """ if self.preview_fade is not None: gobject.source_remove(self.preview_fade) new = self._create_album_preview(album) if self.config.show_effects: old = self.preview new.set_opacity(0) self.preview = new self.add(self.preview) #Fade out timeline timeline1 = clutter.Timeline(500) alpha1 = clutter.Alpha(timeline1, clutter.EASE_IN_OUT_SINE) self.out_opacity = clutter.BehaviourOpacity(255, 0, alpha1) self.out_opacity.apply(old) timeline1.connect('completed', self._change_preview_timeline_ended, old) # Fade in timeline timeline2 = clutter.Timeline(500) alpha2 = clutter.Alpha(timeline2, clutter.EASE_IN_OUT_SINE) self.in_opacity = clutter.BehaviourOpacity(0, 255, alpha2) self.in_opacity.apply(new) # Start animation timeline1.start() timeline2.start() else: # Do it without animation if self.preview is not None: self.remove(self.preview) self.preview = new self.add(self.preview) if len(self.preview_textures) > 1: self.preview_fade = gobject.timeout_add(6000, self._change_preview_image) return False # see gobject.timeout_add() doc def _change_preview_timeline_ended(self, timeline, group): """ This is a callback function for preview updates. This is called when transition effect is finished. This method removes old preview group from the stage. """ self.remove(group) def _change_preview_image(self): """ Run a timeline that crossfades preview images. This method is a callback that is called every 4 seconds. """ if len(self.preview_textures)<=1: self.preview_textures[0].set_opacity(255) elif self.config.show_effects: #Fade out timeline fade_out = clutter.Timeline(500) alpha_out = clutter.Alpha(fade_out, clutter.EASE_IN_OUT_SINE) self.out_behaviour = clutter.BehaviourOpacity(255, 0, alpha_out) self.out_behaviour.apply(self.preview_textures[0]) # Fade in timeline fade_in = clutter.Timeline(500) alpha_in = clutter.Alpha(fade_in, clutter.EASE_IN_OUT_SINE) self.in_behaviour = clutter.BehaviourOpacity(0, 255, alpha_in) self.in_behaviour.apply(self.preview_textures[1]) # Start animation fade_out.start() fade_in.start() else: self.preview_textures[0].set_opacity(0) self.preview_textures[1].set_opacity(255) # Scroll images self.preview_textures = self.preview_textures[1:] + \ self.preview_textures[:1] return True def _create_list_indicator(self): '''Create list indicator for albums list.''' self.li = ListIndicator(0.77, 0.9, 0.2, 0.045, ListIndicator.VERTICAL) albums = self.image_library.get_albums() albums_list = [[album.get_title(), str(album.get_number_of_images()), album] for album in albums if album.get_number_of_images() != 0] self.li.set_maximum(len(albums_list)) self.add(self.li) def _handle_up(self): '''Handle UserEvent.NAVIGATE_UP.''' self.menu.up() def _handle_down(self): '''Handle UserEvent.NAVIGATE_DOWN.''' self.menu.down() def _handle_select(self, event=None): '''Handle UserEvent.NAVIGATE_SELECT.''' album = self.menu.selected_userdata kwargs = { 'title' : album.get_title(), 'images' : album.get_images() } self.callback("photographs", kwargs) def handle_user_event(self, event): '''Handle screen specific user events unless the library is empty.''' if self.image_library.get_number_of_albums() == 0: return else: Screen.handle_user_event(self, event) def _display_selected_album(self, event=None): '''Update of the list indicator''' self.li.set_current(self.menu.selected_index + 1) # 500ms timeout before preview is updated (fast scrolling) if self.timeout is not None: gobject.source_remove(self.timeout) self.timeout = gobject.timeout_add(500, self._update_album_preview, self.menu.selected_userdata)
class TvEpisodes(Screen): '''Screen contains list of all episodes of one specific season.''' def __init__(self, media_player, move_to_new_screen_callback, episodes, tv_series): Screen.__init__(self, 'TvEpisodes', move_to_new_screen_callback) self.episodes = episodes self.media_player = media_player self.theme = self.config.theme self.tv_series = tv_series # Screen Title (Displayed at the bottom left corner) screen_title = Label(0.13, "screentitle", 0, 0.87, self.tv_series.title) self.add(screen_title) self.scroll_area = None self.title = None self.thumb = None self.menu = self._create_episode_menu() self.add(self.menu) self._create_episode_info_box() #List indicator self.li = ListIndicator(0.8, 0.9, 0.2, 0.045, ListIndicator.VERTICAL) self.li.set_maximum(len(self.episodes)) self.add(self.li) self.menu.connect("moved", self._update_episode_info) self.menu.connect("selected", self._handle_select) self.menu.connect("activated", self._on_menu_activated) def _create_episode_menu(self): """Create a list of available seasons.""" menu = TextMenu(0.4978, 0.1563, 0.4393, 0.0781) episodes_list = [[_("%(num)d. %(title)s") % \ {'num': episode.number, 'title': episode.title}, None, episode] for episode in self.episodes] menu.async_add(episodes_list) menu.active = True return menu def _create_thumbnail_texture(self): """Create a thumbnail texture. This is called as menu is scrolled.""" if self.thumb: self.thumb.hide() # Thumbnail. Use cover art if thumbnail doesn't exist thumbnail = self.menu.selected_userdata.thumbnail_url if(thumbnail is not None): pixbuf = gtk.gdk.pixbuf_new_from_file(thumbnail) thumb_width = 0.2928 thumb_height = 0.2799 thumb_x = 0.05 thumb_y = 0.2 else: thumb_width = 0.1098 thumb_height = 0.2799 thumb_x = 0.20 thumb_y = 0.15 if(self.tv_series.has_cover_art()): pixbuf = gtk.gdk.pixbuf_new_from_file( self.tv_series.cover_art_url) else: pixbuf = gtk.gdk.pixbuf_new_from_file( self.theme.getImage("default_movie_art")) self.thumb = EyeCandyTexture(thumb_x, thumb_y, thumb_width, thumb_height, pixbuf) self.add(self.thumb) def _create_episode_info_box(self): """ Create a texture that is displayed next to track list. This texture displays album cover art. """ self._create_thumbnail_texture() # Title self.title = Label(0.04, "title", 0.05, 0.55, self.menu.selected_userdata.title, font_weight="bold") self.title.set_ellipsize(pango.ELLIPSIZE_END) self.title.set_line_wrap(False) self.title.width = 0.4 self.add(self.title) # Plot plot = Label(0.029, "subtitle", 0, 0, self.menu.selected_userdata.plot) plot.width = 0.4 self.scroll_area = ScrollArea(0.05, 0.63, 0.4, 0.15, plot) self.scroll_area.connect("activated", self._on_scroll_area_activated) self.add(self.scroll_area) def _update_episode_info(self, event=None): '''Update information from this episode.''' self.li.set_current(self.menu.selected_index + 1) self._create_thumbnail_texture() self.title.set_text(self.menu.selected_userdata.title) self.title.width = 0.4 plot = Label(0.029, "subtitle", 0, 0, self.menu.selected_userdata.plot) plot.width = 0.4 self.scroll_area.set_content(plot) def _handle_up(self): '''Handle UserEvent.NAVIGATE_UP.''' if self.menu.active: self.menu.up() else: self.scroll_area.scroll_up() def _handle_down(self): '''Handle UserEvent.NAVIGATE_DOWN.''' if self.menu.active: self.menu.down() else: self.scroll_area.scroll_down() def _handle_left(self): '''Handle UserEvent.NAVIGATE_LEFT.''' self.menu.active = False self.scroll_area.active = True def _handle_right(self): '''Handle UserEvent.NAVIGATE_RIGHT.''' self.menu.active = True self.scroll_area.active = False def _handle_select(self, event=None): '''Handle UserEvent.NAVIGATE_SELECT.''' episode = self.menu.selected_userdata self.media_player.set_media(episode) self.media_player.play() self.callback("video_osd") def _on_menu_activated(self, event=None): '''Handle menu activation.''' self.scroll_area.active = False def _on_scroll_area_activated(self, event=None): '''Handle scroll_area activation.''' self.menu.active = False
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 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 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 TvEpisodes(Screen): '''Screen contains list of all episodes of one specific season.''' def __init__(self, media_player, move_to_new_screen_callback, episodes, tv_series): Screen.__init__(self, 'TvEpisodes', move_to_new_screen_callback) self.episodes = episodes self.media_player = media_player self.theme = self.config.theme self.tv_series = tv_series # Screen Title (Displayed at the bottom left corner) screen_title = Label(0.13, "screentitle", 0, 0.87, self.tv_series.title) self.add(screen_title) self.scroll_area = None self.title = None self.thumb = None self.menu = self._create_episode_menu() self.add(self.menu) self._create_episode_info_box() #List indicator self.li = ListIndicator(0.8, 0.9, 0.2, 0.045, ListIndicator.VERTICAL) self.li.set_maximum(len(self.episodes)) self.add(self.li) self.menu.connect("moved", self._update_episode_info) self.menu.connect("selected", self._handle_select) self.menu.connect("activated", self._on_menu_activated) def _create_episode_menu(self): """Create a list of available seasons.""" menu = TextMenu(0.4978, 0.1563, 0.4393, 0.0781) episodes_list = [[_("%(num)d. %(title)s") % \ {'num': episode.number, 'title': episode.title}, None, episode] for episode in self.episodes] menu.async_add(episodes_list) menu.active = True return menu def _create_thumbnail_texture(self): """Create a thumbnail texture. This is called as menu is scrolled.""" if self.thumb: self.thumb.hide() # Thumbnail. Use cover art if thumbnail doesn't exist thumbnail = self.menu.selected_userdata.thumbnail_url if (thumbnail is not None): pixbuf = gtk.gdk.pixbuf_new_from_file(thumbnail) thumb_width = 0.2928 thumb_height = 0.2799 thumb_x = 0.05 thumb_y = 0.2 else: thumb_width = 0.1098 thumb_height = 0.2799 thumb_x = 0.20 thumb_y = 0.15 if (self.tv_series.has_cover_art()): pixbuf = gtk.gdk.pixbuf_new_from_file( self.tv_series.cover_art_url) else: pixbuf = gtk.gdk.pixbuf_new_from_file( self.theme.getImage("default_movie_art")) self.thumb = EyeCandyTexture(thumb_x, thumb_y, thumb_width, thumb_height, pixbuf) self.add(self.thumb) def _create_episode_info_box(self): """ Create a texture that is displayed next to track list. This texture displays album cover art. """ self._create_thumbnail_texture() # Title self.title = Label(0.04, "title", 0.05, 0.55, self.menu.selected_userdata.title, font_weight="bold") self.title.set_ellipsize(pango.ELLIPSIZE_END) self.title.set_line_wrap(False) self.title.width = 0.4 self.add(self.title) # Plot plot = Label(0.029, "subtitle", 0, 0, self.menu.selected_userdata.plot) plot.width = 0.4 self.scroll_area = ScrollArea(0.05, 0.63, 0.4, 0.15, plot) self.scroll_area.connect("activated", self._on_scroll_area_activated) self.add(self.scroll_area) def _update_episode_info(self, event=None): '''Update information from this episode.''' self.li.set_current(self.menu.selected_index + 1) self._create_thumbnail_texture() self.title.set_text(self.menu.selected_userdata.title) self.title.width = 0.4 plot = Label(0.029, "subtitle", 0, 0, self.menu.selected_userdata.plot) plot.width = 0.4 self.scroll_area.set_content(plot) def _handle_up(self): '''Handle UserEvent.NAVIGATE_UP.''' if self.menu.active: self.menu.up() else: self.scroll_area.scroll_up() def _handle_down(self): '''Handle UserEvent.NAVIGATE_DOWN.''' if self.menu.active: self.menu.down() else: self.scroll_area.scroll_down() def _handle_left(self): '''Handle UserEvent.NAVIGATE_LEFT.''' self.menu.active = False self.scroll_area.active = True def _handle_right(self): '''Handle UserEvent.NAVIGATE_RIGHT.''' self.menu.active = True self.scroll_area.active = False def _handle_select(self, event=None): '''Handle UserEvent.NAVIGATE_SELECT.''' episode = self.menu.selected_userdata self.media_player.set_media(episode) self.media_player.play() self.callback("video_osd") def _on_menu_activated(self, event=None): '''Handle menu activation.''' self.scroll_area.active = False def _on_scroll_area_activated(self, event=None): '''Handle scroll_area activation.''' self.menu.active = False
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 TvSeries(Screen): '''Screen that contains all seasons of one TV series.''' def __init__(self, video_library, move_to_new_screen_callback, tv_series): Screen.__init__(self, 'TvSeries', move_to_new_screen_callback) self.theme = self.config.theme self.tv_series = tv_series self.video_library = video_library # Screen Title (Displayed at the bottom left corner) screen_title = Label(0.13, "screentitle", 0, 0.87, self.tv_series.title) self.add(screen_title) self.art = None self.menu = None self._create_series_cover_texture() self.menu = self._create_season_menu() self.add(self.menu) #List indicator self.li = ListIndicator(0.8, 0.9, 0.2, 0.045, ListIndicator.VERTICAL) self.li.set_maximum(len(self.tv_series.seasons)) self.add(self.li) self.menu.connect("moved", self._update_season_info) self.menu.connect("selected", self._handle_select) def _create_season_menu(self): """ Create a list of available seasons """ menu = TextMenu(0.4978, 0.1563, 0.4393, 0.0781) seasons = self.tv_series.seasons seasons.sort() seasons_list = [[_("Season %(num)s") % {'num': season}, None, season] \ for season in seasons] menu.async_add(seasons_list) menu.active = True return menu def _create_series_cover_texture(self): """ Create a texture that is displayed next to track list. This texture displays album cover art. """ if (self.tv_series.has_cover_art()): pixbuf = gtk.gdk.pixbuf_new_from_file(self.tv_series.cover_art_url) else: pixbuf = gtk.gdk.pixbuf_new_from_file( self.theme.getImage("default_movie_art")) self.art = EyeCandyTexture(0.16, 0.15, 0.2196, 0.5859, pixbuf) self.add(self.art) def _update_season_info(self, event=None): '''Update the ListIndicator.''' self.li.set_current(self.menu.selected_index + 1) def _handle_up(self): '''Handle UserEvent.NAVIGATE_UP.''' self.menu.up() def _handle_down(self): '''Handle UserEvent.NAVIGATE_DOWN.''' self.menu.down() def _handle_select(self, event=None): '''Handle UserEvent.NAVIGATE_SELECT.''' season = self.menu.selected_userdata episodes = self.video_library.get_episodes_from_season( self.tv_series.title, season) kwargs = {'episodes': episodes, 'tv_series': self.tv_series} self.callback("tv_episodes", kwargs)
class PhotoAlbums(Screen): '''Screen contains a list of photo albums and album previews.''' def __init__(self, image_library, move_to_new_screen_callback): Screen.__init__(self, 'PhotoAlbums', move_to_new_screen_callback) self.theme = self.config.theme self.image_library = image_library self.timeout = None # Timeout key (this is used when scrolling menu) # Screen Title (Displayed at the bottom left corner) screen_title = Label(0.13, "screentitle", 0, 0.87, _("Photographs")) self.add(screen_title) if self.image_library.get_number_of_albums() == 0: self._create_no_photos_information() else: # Album preview group self.preview = clutter.Group() self.preview.set_position(self.get_abs_x(0.07), self.get_abs_y(0.1953)) self.preview.show() self.add(self.preview) self.preview_fade = None self.menu = None self.in_behaviour = None self.out_behaviour = None self.in_opacity = None self.out_opacity = None self.preview_textures = None self.menu = self._create_album_menu() self.add(self.menu) self.li = None self._create_list_indicator() self._update_album_preview(self.menu.selected_userdata) self.menu.connect('selected', self._handle_select) self.menu.connect('moved', self._display_selected_album) def _create_no_photos_information(self): """ Create textures and labels for information screen. This is displayed instead of album list if there are no photos available and it helps users to add new photographs to the system. """ # Create warning icon warning_icon = Texture(self.theme.getImage("warning_icon"), 0.28, 0.27) self.add(warning_icon) # Create warning title info_title = Label(0.0625, "title", 0.3367, 0.2709, _("No photographs available!")) self.add(info_title) # Create warning help text message = _( "There are no indexed photographs in the Entertainer media " "library. To add photographs, start the Content management tool " "and open the 'Images' tab. Now click on the 'Add' button and " "select some folders which contain image files.") info = Label(0.0417, "menuitem_inactive", 0.2804, 0.45, message) info.set_size(0.5, 0.5859) self.add(info) def _create_album_menu(self): """ Create ImageAlbum-menu. This menu contains list of albums. It also displays number of photographs per album. """ menu = TextMenu(0.5271, 0.3385, 0.4393, 0.0781) menu.visible_rows = 7 albums = self.image_library.get_albums() albums_list = [[ album.get_title(), str(album.get_number_of_images()), album ] for album in albums if album.get_number_of_images() != 0] menu.async_add(albums_list) menu.active = True return menu def _create_album_preview(self, album): """ Create a clutter.Group that contains album preview actors. """ group = clutter.Group() group.set_position(self.get_abs_x(0.07), self.get_abs_y(0.1953)) # Preview images images = album.get_preview_images(3) self.preview_textures = [] max_w = 0.4026 max_h = 0.5599 abs_max_w = self.get_abs_x(max_w) abs_max_h = self.get_abs_y(max_h) for image in images: pix_buffer = gtk.gdk.pixbuf_new_from_file( image.get_thumbnail_url()) ratio = float(pix_buffer.get_width()) ratio /= float(pix_buffer.get_height()) # Resize and center preview texture if ratio > 1: texture = EyeCandyTexture(0.0, 0.0, max_w, max_h / ratio, pix_buffer) new_y = int((abs_max_h - abs_max_h / ratio) / 2.0) texture.set_position(0, new_y) else: texture = EyeCandyTexture(0.0, 0.0, max_w * ratio, max_h, pix_buffer) new_x = int((abs_max_w - abs_max_w * ratio) / 2.0) texture.set_position(new_x, 0) texture.set_rotation(clutter.Y_AXIS, 25, 0, 0, 0) texture.set_opacity(0) self.preview_textures.append(texture) group.add(texture) self.preview_textures[0].set_opacity(255) title = Label(0.03646, "title", 0.4649, 0, album.get_title(), font_weight="bold") title.width = 0.4758 title.set_ellipsize(pango.ELLIPSIZE_END) group.add(title) desc = Label(0.026, "subtitle", 0.4649, 0.0521, album.get_description()) desc.width = 0.4758 group.add(desc) return group def _update_album_preview(self, album): """ Update album preview. Display preview images from the current album. @param album: Currently selected album in menu """ if self.preview_fade is not None: gobject.source_remove(self.preview_fade) new = self._create_album_preview(album) if self.config.show_effects: old = self.preview new.set_opacity(0) self.preview = new self.add(self.preview) #Fade out timeline timeline1 = clutter.Timeline(500) alpha1 = clutter.Alpha(timeline1, clutter.EASE_IN_OUT_SINE) self.out_opacity = clutter.BehaviourOpacity(255, 0, alpha1) self.out_opacity.apply(old) timeline1.connect('completed', self._change_preview_timeline_ended, old) # Fade in timeline timeline2 = clutter.Timeline(500) alpha2 = clutter.Alpha(timeline2, clutter.EASE_IN_OUT_SINE) self.in_opacity = clutter.BehaviourOpacity(0, 255, alpha2) self.in_opacity.apply(new) # Start animation timeline1.start() timeline2.start() else: # Do it without animation if self.preview is not None: self.remove(self.preview) self.preview = new self.add(self.preview) if len(self.preview_textures) > 1: self.preview_fade = gobject.timeout_add(6000, self._change_preview_image) return False # see gobject.timeout_add() doc def _change_preview_timeline_ended(self, timeline, group): """ This is a callback function for preview updates. This is called when transition effect is finished. This method removes old preview group from the stage. """ self.remove(group) def _change_preview_image(self): """ Run a timeline that crossfades preview images. This method is a callback that is called every 4 seconds. """ if len(self.preview_textures) <= 1: self.preview_textures[0].set_opacity(255) elif self.config.show_effects: #Fade out timeline fade_out = clutter.Timeline(500) alpha_out = clutter.Alpha(fade_out, clutter.EASE_IN_OUT_SINE) self.out_behaviour = clutter.BehaviourOpacity(255, 0, alpha_out) self.out_behaviour.apply(self.preview_textures[0]) # Fade in timeline fade_in = clutter.Timeline(500) alpha_in = clutter.Alpha(fade_in, clutter.EASE_IN_OUT_SINE) self.in_behaviour = clutter.BehaviourOpacity(0, 255, alpha_in) self.in_behaviour.apply(self.preview_textures[1]) # Start animation fade_out.start() fade_in.start() else: self.preview_textures[0].set_opacity(0) self.preview_textures[1].set_opacity(255) # Scroll images self.preview_textures = self.preview_textures[1:] + \ self.preview_textures[:1] return True def _create_list_indicator(self): '''Create list indicator for albums list.''' self.li = ListIndicator(0.77, 0.9, 0.2, 0.045, ListIndicator.VERTICAL) albums = self.image_library.get_albums() albums_list = [[ album.get_title(), str(album.get_number_of_images()), album ] for album in albums if album.get_number_of_images() != 0] self.li.set_maximum(len(albums_list)) self.add(self.li) def _handle_up(self): '''Handle UserEvent.NAVIGATE_UP.''' self.menu.up() def _handle_down(self): '''Handle UserEvent.NAVIGATE_DOWN.''' self.menu.down() def _handle_select(self, event=None): '''Handle UserEvent.NAVIGATE_SELECT.''' album = self.menu.selected_userdata kwargs = {'title': album.get_title(), 'images': album.get_images()} self.callback("photographs", kwargs) def handle_user_event(self, event): '''Handle screen specific user events unless the library is empty.''' if self.image_library.get_number_of_albums() == 0: return else: Screen.handle_user_event(self, event) def _display_selected_album(self, event=None): '''Update of the list indicator''' self.li.set_current(self.menu.selected_index + 1) # 500ms timeout before preview is updated (fast scrolling) if self.timeout is not None: gobject.source_remove(self.timeout) self.timeout = gobject.timeout_add(500, self._update_album_preview, self.menu.selected_userdata)
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 Photographs(Screen): '''Screen displays a grid of selectable photograph thumbnails.''' def __init__(self, move_to_new_screen_callback, title, images): Screen.__init__(self, 'Photographs', move_to_new_screen_callback) self.images = images # Screen Title (Displayed at the bottom left corner) screen_title = Label(0.13, "screentitle", 0, 0.87, title) self.add(screen_title) # Image Title (over album list) self.image_title = Label(0.04167, "title", 0.0586, 0.7943, " ") self.image_title.set_ellipsize(pango.ELLIPSIZE_END) self.add(self.image_title) self.image_desc = Label(0.04167, "subtitle", 0.0586, 0.9115, " ") self.image_desc.set_line_wrap(True) self.image_desc.set_ellipsize(pango.ELLIPSIZE_END) self.add(self.image_desc) # Display throbber animation while loading photographs self.throbber = LoadingAnimation(0.9, 0.9) self.throbber.show() self.add(self.throbber) # List indicator self.li = None self._create_list_indicator() # Create photomenu self.menu = ImageMenu(0.03, 0.08, 0.12, self.y_for_x(0.12)) self.menu.items_per_col = 3 self.menu.visible_rows = 3 self.menu.visible_cols = 8 self.menu.active = True self.add(self.menu) photos = self.images photos_list = [[Texture(photo.get_thumbnail_url()), photo] \ for photo in photos] self.menu.async_add(photos_list) self.menu.connect("selected", self._handle_select) self.menu.connect('moved', self._update_image_info) self.menu.connect("filled", self._on_menu_filled) def _update_image_info(self, event=None): """Update image information box.""" image = self.images[self.menu.selected_index] name = image.get_title() desc = image.get_description() self.image_title.set_text(name) self.image_title.set_size(0.366, 0.04167) self.image_desc.set_text(desc) self.image_desc.set_size(0.366, 0.0911) self.li.set_current(self.menu.selected_index + 1) def _create_list_indicator(self): '''Create list indicator for albums list.''' self.li = ListIndicator(0.77, 0.8, 0.18, 0.045, ListIndicator.HORIZONTAL) self.li.set_maximum(len(self.images)) self.add(self.li) def _handle_up(self): '''Handle UserEvent.NAVIGATE_UP.''' self.menu.up() def _handle_down(self): '''Handle UserEvent.NAVIGATE_DOWN.''' self.menu.down() def _handle_left(self): '''Handle UserEvent.NAVIGATE_LEFT.''' self.menu.left() def _handle_right(self): '''Handle UserEvent.NAVIGATE_RIGHT.''' self.menu.right() def _handle_select(self, event=None): '''Handle UserEvent.NAVIGATE_SELECT.''' index = self.menu.selected_index kwargs = {'current_photo_index' : index, 'images' : self.images} self.callback("photo", kwargs) 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 Photographs(Screen): '''Screen displays a grid of selectable photograph thumbnails.''' def __init__(self, move_to_new_screen_callback, title, images): Screen.__init__(self, 'Photographs', move_to_new_screen_callback) self.images = images # Screen Title (Displayed at the bottom left corner) screen_title = Label(0.13, "screentitle", 0, 0.87, title) self.add(screen_title) # Image Title (over album list) self.image_title = Label(0.04167, "title", 0.0586, 0.7943, " ") self.image_title.set_ellipsize(pango.ELLIPSIZE_END) self.add(self.image_title) self.image_desc = Label(0.04167, "subtitle", 0.0586, 0.9115, " ") self.image_desc.set_line_wrap(True) self.image_desc.set_ellipsize(pango.ELLIPSIZE_END) self.add(self.image_desc) # Display throbber animation while loading photographs self.throbber = LoadingAnimation(0.9, 0.9) self.throbber.show() self.add(self.throbber) # List indicator self.li = None self._create_list_indicator() # Create photomenu self.menu = ImageMenu(0.03, 0.08, 0.12, self.y_for_x(0.12)) self.menu.items_per_col = 3 self.menu.visible_rows = 3 self.menu.visible_cols = 8 self.menu.active = True self.add(self.menu) photos = self.images photos_list = [[Texture(photo.get_thumbnail_url()), photo] \ for photo in photos] self.menu.async_add(photos_list) self.menu.connect("selected", self._handle_select) self.menu.connect('moved', self._update_image_info) self.menu.connect("filled", self._on_menu_filled) def _update_image_info(self, event=None): """Update image information box.""" image = self.images[self.menu.selected_index] name = image.get_title() desc = image.get_description() self.image_title.set_text(name) self.image_title.set_size(0.366, 0.04167) self.image_desc.set_text(desc) self.image_desc.set_size(0.366, 0.0911) self.li.set_current(self.menu.selected_index + 1) def _create_list_indicator(self): '''Create list indicator for albums list.''' self.li = ListIndicator(0.77, 0.8, 0.18, 0.045, ListIndicator.HORIZONTAL) self.li.set_maximum(len(self.images)) self.add(self.li) def _handle_up(self): '''Handle UserEvent.NAVIGATE_UP.''' self.menu.up() def _handle_down(self): '''Handle UserEvent.NAVIGATE_DOWN.''' self.menu.down() def _handle_left(self): '''Handle UserEvent.NAVIGATE_LEFT.''' self.menu.left() def _handle_right(self): '''Handle UserEvent.NAVIGATE_RIGHT.''' self.menu.right() def _handle_select(self, event=None): '''Handle UserEvent.NAVIGATE_SELECT.''' index = self.menu.selected_index kwargs = {'current_photo_index': index, 'images': self.images} self.callback("photo", kwargs) 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 Disc(Screen): '''Screen allows user to play tracks from the current Audio CD.''' def __init__(self, music_library, media_player): Screen.__init__(self, 'Disc') self.theme = self.config.theme self.music_library = music_library self.media_player = media_player # When album info is loaded we create Playlist object self.playlist = None self.art = None self.art2 = None self.in_behaviour = None self.out_behaviour = None self.li = None self.track_menu = None # Screen Title (Displayed at the bottom left corner) screen_title = Label(0.13, "screentitle", 0, 0.87, _("Audio Disc"), "screen_title") self.add(screen_title) # Display throbber animation while loading CD metadata self.throbber = LoadingAnimation(0.5, 0.5, 0.1) self.throbber.show() self.add(self.throbber) # Create and initialize screen items self.has_disc = True gobject.timeout_add(500, self._get_disc_information) def _get_disc_information(self): """ Fetch album information from the Internet and create widgets based on received data. """ try: disc = self.music_library.get_compact_disc_information() title = disc.title artist = disc.artist tracks = disc.tracks self.playlist = Playlist(tracks) self._create_album_info(title, artist, tracks, disc.length) self.track_menu = self._create_track_menu(tracks) self.add(self.track_menu) self._create_album_cover_texture(artist, title) self.li = ListIndicator(0.75, 0.8, 0.2, 0.045, ListIndicator.VERTICAL) self.li.set_maximum(len(tracks)) self.add(self.li) art_file = os.path.join(self.config.ALBUM_ART_DIR, artist + " - " + title + ".jpg") if artist != "Unknown artist" and not os.path.exists(art_file): art_search = AlbumArtDownloader(title, artist, self.config.ALBUM_ART_DIR, self._update_albumart) art_search.start() except cdrom.error: # No disc in drive self.has_disc = False no_disc = Label(0.05, "title", 0.5, 0.5, _("No audio disc in drive")) no_disc.set_anchor_point_from_gravity(clutter.GRAVITY_CENTER) self.add(no_disc) # Remove loading animation self.throbber.hide() self.remove(self.throbber) del self.throbber # This function should be called only once (gobject timeout) return False def _update_albumart(self, artist, title): """ Search album art for current audio disc. This function is called only if album art doesn't exist already. If album art is found then we replace current disc icon with the new album art. @param artist: Artist name @param title: Album title """ art_file = os.path.join(self.config.ALBUM_ART_DIR, artist + " - " + title + ".jpg") if os.path.exists(art_file): clutter.threads_enter() self.art2 = Texture(art_file, 0.1, 0.165) clutter.threads_leave() self.art2.set_size(self.get_abs_x(0.3148), self.get_abs_y(0.5599)) self.art2.set_opacity(0) self.add(self.art2) timeline_in = clutter.Timeline(35, 26) alpha_in = clutter.Alpha(timeline_in, clutter.smoothstep_inc_func) self.in_behaviour = clutter.BehaviourOpacity(0, 255, alpha_in) self.in_behaviour.apply(self.art2) timeline_out = clutter.Timeline(35, 26) alpha_out = clutter.Alpha(timeline_out, clutter.smoothstep_inc_func) self.out_behaviour = clutter.BehaviourOpacity(255, 0, alpha_out) self.out_behaviour.apply(self.art) timeline_out.start() timeline_in.start() def _create_album_cover_texture(self, artist, title): """ Create a texture that is displayed next to track list. This texture displays album cover art. @param artist: Artist @param title: Title of the album """ coverfile = os.path.join(self.config.ALBUM_ART_DIR, artist + " - " + title + ".jpg") if(os.path.exists(coverfile)): pixbuf = gtk.gdk.pixbuf_new_from_file(coverfile) else: pixbuf = gtk.gdk.pixbuf_new_from_file(self.theme.getImage("disc")) self.art = EyeCandyTexture(0.1, 0.13, 0.3148, 0.5599, pixbuf) self.art.set_rotation(clutter.Y_AXIS, 25, 0, 0, 0) self.add(self.art) def _create_album_info(self, title, artist_name, tracks, length): """ Create album info labels. @param title: Album title @param artist_name: Artist @param tracks: List of CompactDisc objects """ album = Label(0.04167, "text", 0.50146, 0.13, artist_name + " - " + title, font_weight="bold") album.set_size(0.4393, 0.06510) album.set_ellipsize(pango.ELLIPSIZE_END) self.add(album) minutes = str(length / 60) num_of_tracks = Label(0.02604, "subtitle", 0.50146, 0.18, _("%(total)s tracks, %(time)s minutes") % \ {'total': len(tracks), 'time': minutes}, font_weight="bold") self.add(num_of_tracks) def _create_track_menu(self, tracks): """ Create a track menu. This menu contains list of all tracks on album. @param tracks: List of CompactDisc objects """ menu = TextMenu(0.4978, 0.2344, 0.4393, 0.0781) menu.visible_rows = 7 tracks_list = [[track.title, track.length_string, index] \ for index, track in enumerate(tracks)] menu.async_add(tracks_list) menu.active = True menu.connect('selected', self._handle_select) menu.connect('moved', self._display_selected_track) return menu def _handle_up(self): '''Handle UserEvent.NAVIGATE_UP.''' self.track_menu.up() def _handle_down(self): '''Handle UserEvent.NAVIGATE_DOWN.''' self.track_menu.down() def _handle_select(self, event=None): '''Handle UserEvent.NAVIGATE_SELECT.''' track_index = self.track_menu.selected_userdata self.playlist.set_current(track_index) self.media_player.set_playlist(self.playlist) self.media_player.play() def handle_user_event(self, event): '''Handle user events unless there is no disc.''' if self.has_disc: Screen.handle_user_event(self, event) def _display_selected_track(self, event=None): '''Update of the list indicator.''' self.li.set_current(self.track_menu.selected_index + 1)