import pygtk import gobject import gtk import pango import clutter import threading from modules.music_player.backends.myth_music import Backend from modules.music_player.lastFM_interface import lastFM_interface from modules.music_player.music_object_row import MusicObjectRow from modules.music_player.playlist import Playlist from modules.music_player.play_screen import PlayScreen from ui_elements.image_clone import ImageClone from ui_elements.label_list import LabelList from ui_elements.option_dialog import OptionDialog class Module: CONTEXT_HEADINGS, CONTEXT_ROW, CONTEXT_ALBUM_LIST, CONTEXT_SONG_LIST, CONTEXT_PLAY_SCR = range(5) DIRECTION_LEFT, DIRECTION_RIGHT = range(2) title = "Music" num_columns = 6 sleep_time = 0.3 required_schema_version = 1013 version_check = True #Gets set to false if the version check fails delay = 1 def __init__(self, glossMgr, dbMgr): self.stage = glossMgr.get_stage() self.glossMgr = glossMgr self.dbMgr = dbMgr self.albums = [] self.artists = [] self.songs = [] self.backend = Backend(self) self.playlist = Playlist(self) #Do a check of the DB schema version dbSchema = self.dbMgr.get_setting("MusicDBSchemaVer") if not dbSchema is None: dbSchema = int(dbSchema) if not dbSchema == self.required_schema_version: print "Music DB Version error: Expected version %s, found version %s" % (self.required_schema_version, dbSchema) print "Music Player will not be available" self.version_check = False self.setup_ui() return self.artistImageRow = MusicObjectRow(self.glossMgr, self.stage.get_width(), 200, self.num_columns, self) self.play_screen = PlayScreen(self) #This is the current input context self.current_context = self.CONTEXT_ROW self.previous_context = None self.lastFM = lastFM_interface() self.base_dir = self.dbMgr.get_setting("MusicLocation") self.images_dir = self.get_images_dir() print "Music Base Dir: " + self.base_dir self.is_playing = False self.artists = self.backend.get_artists() self.timeout_id = 0 self.queue_id = 0 self.heading = None self.setup_ui() def setup_ui(self): self.menu_image = self.glossMgr.themeMgr.get_texture("music_menu_image", None, None) self.default_artist_cover = self.glossMgr.themeMgr.get_texture("music_default_artist_image", None, None).get_pixbuf() self.default_album_cover = self.glossMgr.themeMgr.get_texture("music_default_album_image", None, None).get_pixbuf() self.main_img = self.glossMgr.themeMgr.get_imageFrame("music_main_image") colour = clutter.color_parse('White') font_string = "Tahoma 30" heading_label = self.glossMgr.themeMgr.get_label("music_heading") self.heading = musicHeading(colour, font_string) self.heading.set_width(self.stage.get_width()) self.heading.set_position(0, heading_label.get_y()) #Get the images dir setting our of the DB #But also creates the setting if it doesn't already exist def get_images_dir(self): images_dir = self.dbMgr.get_setting("GlossMusicImgLocation") if images_dir is None: #We need to create the setting #Default value is the same as the music base_dir + covers images_dir = self.base_dir + "/.images/" images_dir = images_dir.replace("//", "/") #Just a silly little check self.dbMgr.set_setting("GlossMusicImgLocation", images_dir) return images_dir #Action to take when menu item is selected def action(self): return self def on_key_press_event (self, stage, event): if event.keyval == clutter.keysyms.p: if self.paused: self.unpause() else: self.pause() if event.keyval == clutter.keysyms.q: clutter.main_quit() #React based on the current input context if self.current_context == self.CONTEXT_ROW: if (event.keyval == clutter.keysyms.Left) or (event.keyval == clutter.keysyms.Right): self.previous_music_object = self.artistImageRow.get_current_object() #First check if there's any current timeouts and if there is, clear it #if not self.timeout_id == 0: gobject.source_remove(self.timeout_id) self.artistImageRow.sleep = True self.artistImageRow.input_queue.input(event) #self.artistImageRow.input_queue.connect("queue-flushed", self.start_delay, self.load_albums, None) #self.artistImageRow.objectLibrary[0].pause_threads() if self.queue_id == 0: self.queue_id = self.artistImageRow.input_queue.connect("queue-flushed", self.load_albums) self.artistImageRow.sleep = False if (event.keyval == clutter.keysyms.Left): self.direction = self.DIRECTION_LEFT if (event.keyval == clutter.keysyms.Right): self.direction = self.DIRECTION_RIGHT elif (event.keyval == clutter.keysyms.Down): self.list1.select_first_elegant() self.list2.show() self.current_context = self.CONTEXT_ALBUM_LIST elif (event.keyval == clutter.keysyms.Return): artist = self.artistImageRow.get_current_object() songs = self.backend.get_songs_by_artistID(artist.artistID) self.query_playlist_add(songs) self.previous_context = self.CONTEXT_ROW elif (event.keyval == clutter.keysyms.Escape): return True #self.stop() elif self.current_context == self.CONTEXT_ALBUM_LIST: if (event.keyval == clutter.keysyms.Up): self.artistImageRow.external_timeline = self.list1.timeline #If we're at the top of the list already, we change focus bar to the image_row if self.list1.selected == 0: self.list1.select_none_elegant() self.list2.hide() self.current_context = self.CONTEXT_ROW else: self.list1.input_queue.input(event) self.update_main_img() elif (event.keyval == clutter.keysyms.Down): self.list1.input_queue.input(event) #if self.artist_queue_id == 0: self.artist_queue_id = self.list1.input_queue.connect("queue-flushed", self.update_main_img) #self.update_main_img() elif (event.keyval == clutter.keysyms.Return): album = self.current_albums[self.list1.selected] songs = self.backend.get_songs_by_albumID(album.albumID) self.query_playlist_add(songs) self.previous_context = self.CONTEXT_ALBUM_LIST elif self.current_context == self.CONTEXT_SONG_LIST: pass elif self.current_context == self.CONTEXT_PLAY_SCR: if (event.keyval == clutter.keysyms.Escape): self.current_context = self.previous_context self.play_screen.undisplay() else: self.play_screen.on_key_press_event(stage, event) def query_playlist_add(self, songs): """Called when songs are to be added to the current playlist #When the user has selected some songs, this function is called to decide what to do with them #Options: #1) Append to current playlist #2) Append to current playlist and play next #3) Replace the current playlist Arguments: songs -- an array of song objects to be added """ #If the current playlist is empty, its a no brainer: if self.playlist.num_songs() == 0: self.playlist.append_songs(songs) self.play_screen.display(self.artistImageRow.get_current_texture()) self.playlist.play() self.current_context = self.CONTEXT_PLAY_SCR return option_dialog = OptionDialog(self.glossMgr) self.query_options = [] self.query_options.append(option_dialog.add_item("Append to current playlist")) self.query_options.append(option_dialog.add_item("Append to current playlist and play next")) self.query_options.append(option_dialog.add_item("Append to current playlist and play now")) self.query_options.append(option_dialog.add_item("Replace the current playlist")) option_dialog.connect("option-selected", self.option_dialog_cb, songs) option_dialog.display("What would you like to do with these songs?") def option_dialog_cb(self, data, result, songs): #Handle options if result == self.query_options[0]: self.playlist.append_songs(songs) self.play_screen.display(None) if result == self.query_options[1]: self.playlist.insert_songs(self.playlist.position+1, songs) self.play_screen.display(None) if result == self.query_options[2]: self.playlist.insert_songs(self.playlist.position+1, songs) self.playlist.play(song_no=self.playlist.position+1) self.play_screen.display(self.artistImageRow.get_current_texture()) if result == self.query_options[3]: self.playlist.stop() self.playlist.clear_songs() self.playlist.append_songs(songs) self.play_screen.clear_songs() self.playlist.play() self.play_screen.display(self.artistImageRow.get_current_texture()) #self.play_screen.append_playlist(self.playlist) self.current_context = self.CONTEXT_PLAY_SCR #.get_texture()) #Fills self.list2 with songs from an album def process_songlist_from_album(self, list_item, album): #print "got album %s" % album.name songs = self.backend.get_songs_by_albumID(album.albumID) self.list2.clear() for song in songs: tmpItem = self.list2.add_item(song.name) self.list2.display() #Loads albums into List1 def load_albums(self, queue): if self.artistImageRow.input_queue.handler_is_connected(self.queue_id): self.artistImageRow.input_queue.disconnect(self.queue_id) self.queue_id = 0 #Just a little test code self.artistImageRow.objectLibrary[0].unpause_threads() artist = self.artistImageRow.get_current_object() self.conn_id = self.backend.connect("query-complete", self.update_for_albums, artist) self.backend.get_albums_by_artistID(artist.artistID) self.heading.transition_heading(self.direction, self.previous_music_object, artist, None) #thread = threading.Thread(target=self.backend.get_albums_by_artistID, args=(artist.artistID,)) #thread.start() def update_for_albums(self, data, artist = None): if not artist == self.artistImageRow.get_current_object(): return if not self.backend.handler_is_connected(self.conn_id): return self.backend.disconnect(self.conn_id) self.current_albums = self.backend.get_albums_by_artistID(artist.artistID) #clutter.threads_enter() self.list1.clear() for album in self.current_albums: tmpItem = self.list1.add_item(album.name) tmpItem.connect("selected", self.process_songlist_from_album, album) self.list1.display() self.update_main_img() #clutter.threads_leave() def update_main_img(self, data = None): #clutter.threads_enter() pixbuf = self.current_albums[self.list1.selected].get_image() if not pixbuf is None: self.main_img.set_pixbuf(pixbuf) #clutter.threads_leave() self.main_img.show() else: #clutter.threads_enter() self.main_img.set_pixbuf(None) self.main_img.hide() #clutter.threads_leave() def begin(self, glossMgr): #If the schema version check failed, we quit if not self.version_check: self.glossMgr.display_msg("Error: MythMusic", "MythMusic version check failed. Please check you are running the correct version.") self.stop() return self.timeline_loading = clutter.Timeline(80,160) self.alpha = clutter.Alpha(self.timeline_loading, clutter.ramp_inc_func) self.opacity_behaviour = clutter.BehaviourOpacity(opacity_start=0, opacity_end=255, alpha=self.alpha) #Create a backdrop for the player. In this case we just use the same background as the menus self.backdrop = clutter.CloneTexture(glossMgr.background) #glossMgr.get_themeMgr().get_texture("background", None, None) self.backdrop.set_size(self.stage.get_width(), self.stage.get_height()) self.backdrop.set_opacity(0) self.backdrop.show() self.stage.add(self.backdrop) self.opacity_behaviour.apply(self.backdrop) self.loading_img = ImageClone(img_frame=glossMgr.get_current_menu().get_current_item().get_main_texture()) self.loading_img.show() self.stage.add(self.loading_img) glossMgr.get_current_menu().get_current_item().get_main_texture().hide() x = int( (self.stage.get_width() - self.loading_img.get_width()) / 2 ) y = int( (self.stage.get_height() - self.loading_img.get_height()) / 2 ) knots = (\ (int(self.loading_img.get_x()), int(self.loading_img.get_y()) ),\ (x, y)\ ) self.path_behaviour = clutter.BehaviourPath(knots = knots, alpha = self.alpha) self.path_behaviour.apply(self.loading_img) self.artistImageRow.objectLibrary = self.artists self.artistImageRow.connect("load-complete", self.display, glossMgr) #self.timeline_loading.connect("completed", self.artistImageRow.load_image_range_cb, 0, len(self.artists)-1, False) self.timeline_loading.connect("completed", self.flush_gobject_queue) self.timeline_loading.start() #thread.start_new_thread(self.artistImageRow.load_image_range, (0, len(self.artists)-1, True)) #gobject.idle_add(self.artistImageRow.load_image_range, 0, len(self.artists)-1, True) def flush_gobject_queue(self, data=None): """ mc = gobject.main_context_default() while mc.pending(): print "Iteration: " + str(mc.iteration(False)) print "finiahed loop" """ self.artistImageRow.load_image_range(0, len(self.artists)-1, False) def display(self, data, glossMgr): self.timeline_display = clutter.Timeline(10,40) self.alpha = clutter.Alpha(self.timeline_display, clutter.ramp_inc_func) self.opacity_behaviour_incoming = clutter.BehaviourOpacity(opacity_start=0, opacity_end=255, alpha=self.alpha) self.opacity_behaviour_outgoing = clutter.BehaviourOpacity(opacity_start=255, opacity_end=0, alpha=self.alpha) #Fade the backdrop in self.opacity_behaviour_outgoing.apply(self.loading_img) self.stage.add(self.artistImageRow) self.artistImageRow.set_position(0, 40) self.artistImageRow.set_opacity(0) self.artistImageRow.select_first() self.artistImageRow.show() self.opacity_behaviour_incoming.apply(self.artistImageRow) #Just a nasty temp label for outputting stuff self.list1 = LabelList() self.list1.setup_from_theme_id(self.glossMgr.themeMgr, "music_albums") self.list1.input_queue.connect("queue-flushed", self.update_main_img) #self.list1.set_position(self.stage.get_width()/3, 350) self.stage.add(self.list1) self.list2 = LabelList() self.list2.setup_from_theme_id(self.glossMgr.themeMgr, "music_songs") #self.list2.set_position( (600), 350) self.stage.add(self.list2) #The preview img #self.main_img = ImageFrame(None, 300, True) #clutter.Texture() #self.main_img.set_position(50, 300) self.main_img.set_rotation(clutter.Y_AXIS, 45, self.main_img.get_width(), 0, 0) self.main_img.show() self.stage.add(self.main_img) self.stage.add(self.heading) self.heading.show() #Set the initial album self.direction = self.DIRECTION_LEFT self.previous_music_object = None self.load_albums(None) self.timeline_display.start() def stop(self): #We hid it, we must unhid it self.glossMgr.get_current_menu().get_current_item().get_main_texture().show() self.loading_img.hide() self.timeline_remove = clutter.Timeline(20,40) self.alpha = clutter.Alpha(self.timeline_remove, clutter.ramp_inc_func) self.opacity_behaviour = clutter.BehaviourOpacity(opacity_start=255, opacity_end=0, alpha=self.alpha) self.remove_list = [] self.opacity_behaviour.apply(self.backdrop) self.remove_list.append(self.backdrop) self.opacity_behaviour.apply(self.artistImageRow) self.remove_list.append(self.artistImageRow) self.opacity_behaviour.apply(self.list1) self.remove_list.append(self.list1) self.opacity_behaviour.apply(self.list2) self.remove_list.append(self.list2) self.opacity_behaviour.apply(self.main_img) self.remove_list.append(self.main_img) self.opacity_behaviour.apply(self.heading) self.remove_list.append(self.heading) self.opacity_behaviour.apply(self.loading_img) self.remove_list.append(self.loading_img) self.timeline_remove.connect("completed", self.destroy) self.timeline_remove.start() def destroy(self, data): self.glossMgr.currentPlugin = None for actor in self.remove_list: self.stage.remove(actor) def pause(self): pass def unpause(self): pass class musicHeading(clutter.Group): DIRECTION_LEFT, DIRECTION_RIGHT = range(2) def __init__(self, colour = None, font_string = None): clutter.Group.__init__(self) if colour is None: colour = clutter.color_parse('White') if font_string is None: font_string = "Tahoma 30" #There has be 3 of these labels, one left, center and right self.label_1 = clutter.Label() self.label_2 = clutter.Label() self.label_3 = clutter.Label() self.label_1.set_font_name(font_string) self.label_2.set_font_name(font_string) self.label_3.set_font_name(font_string) self.label_1.set_alignment(pango.ALIGN_CENTER) self.label_2.set_alignment(pango.ALIGN_CENTER) self.label_3.set_alignment(pango.ALIGN_CENTER) self.label_1.set_color(colour) self.label_2.set_color(colour) self.label_3.set_color(colour) self.label_current = self.label_1 self.add(self.label_1) self.add(self.label_2) self.add(self.label_3) self.label_1.show() self.label_2.show() self.label_3.show() def transition_heading(self, direction, old_music_item, new_music_item, timeline = None): start_timeline = False if timeline is None: #Completely generic timeline definition timeline = clutter.Timeline(20, 90) start_timeline = True self.alpha = clutter.Alpha(timeline, clutter.ramp_inc_func) if direction == self.DIRECTION_LEFT: if self.label_current == self.label_1: next_label = self.label_2 elif self.label_current == self.label_2: next_label = self.label_3 elif self.label_current == self.label_3: next_label = self.label_1 else: if self.label_current == self.label_1: next_label = self.label_3 elif self.label_current == self.label_2: next_label = self.label_1 elif self.label_current == self.label_3: next_label = self.label_2 next_label.set_opacity(0) next_label.set_scale(0.5, 0.5) next_label.set_text(new_music_item.name) if direction == self.DIRECTION_RIGHT: next_label.set_x( self.label_current.get_x() + (self.label_current.get_width())) outgoing_x = self.label_current.get_x() - int(self.label_current.get_width()/2) else: next_label.set_x( self.label_current.get_x() - (next_label.get_width())) outgoing_x = self.label_current.get_x() + int(self.label_current.get_width()) incoming_x = self.label_current.get_x() + int(self.label_current.get_width()/2) - int(next_label.get_width()/2) knots_incoming =(\ (next_label.get_x(), next_label.get_y()), (incoming_x, next_label.get_y()) ) knots_outgoing =(\ (self.label_current.get_x(), self.label_current.get_y()), (outgoing_x, next_label.get_y()) ) self.behaviourIncomingPath = clutter.BehaviourPath(knots = knots_incoming, alpha = self.alpha) self.behaviourOutgoingPath = clutter.BehaviourPath(knots = knots_outgoing, alpha = self.alpha) self.behaviourIncomingOpacity = clutter.BehaviourOpacity(opacity_start = 0, opacity_end = 255, alpha = self.alpha) self.behaviourOutgoingOpacity = clutter.BehaviourOpacity(opacity_start = 255, opacity_end = 0, alpha = self.alpha) self.behaviourIncomingScale = clutter.BehaviourScale(x_scale_start = 0.5, y_scale_start = 0.5, x_scale_end = 1, y_scale_end = 1, alpha = self.alpha) self.behaviourOutgoingScale = clutter.BehaviourScale(x_scale_start = 1, y_scale_start = 1, x_scale_end = 0.5, y_scale_end = 0.5, alpha = self.alpha) self.behaviourIncomingPath.apply(next_label) self.behaviourIncomingOpacity.apply(next_label) self.behaviourIncomingScale.apply(next_label) self.behaviourOutgoingPath.apply(self.label_current) self.behaviourOutgoingOpacity.apply(self.label_current) self.behaviourOutgoingScale.apply(self.label_current) timeline.connect("completed", self.set_next_heading, next_label) if start_timeline: timeline.start() def set_next_heading(self, data, new_heading): self.label_current = new_heading def set_width(self, width): self.width = width self.label_1.set_width(width) self.label_2.set_width(width) self.label_3.set_width(width) def get_width(self): return self.width