def library_return_list_items(self, itemtype, genre=None, artist=None, album=None, year=None, ignore_case=True): # Returns all items of tag 'itemtype', in alphabetical order, # using mpd's 'list'. If searchtype is passed, use # a case insensitive search, via additional 'list' # queries, since using a single 'list' call will be # case sensitive. results = [] searches = self.library_compose_list_count_searchlist(genre, artist, album, year) if len(searches) > 0: for s in searches: # If we have untagged tags (''), use search instead # of list because list will not return anything. if '' in s: items = [] songs, playtime, num_songs = self.library_return_search_items(genre, artist, album, year) for song in songs: items.append(mpdh.get(song, itemtype)) else: items = mpdh.call(self.client, 'list', itemtype, *s) for item in items: if len(item) > 0: results.append(item) else: if genre is None and artist is None and album is None and year is None: for item in mpdh.call(self.client, 'list', itemtype): if len(item) > 0: results.append(item) if ignore_case: results = misc.remove_list_duplicates(results, case=False) results.sort(locale.strcoll) return results
def on_playlist_save(self, _action): plname = self.prompt_for_playlist_name(_("Save Playlist"), 'savePlaylist') if plname: if self.playlist_name_exists(_("Save Playlist"), 'savePlaylistError', plname): return self.playlist_create(plname) mpdh.call(self.client, 'playlistclear', plname) self.add_selected_to_playlist(plname)
def on_playlist_menu_click(self, action): plname = misc.unescape_html(action.get_name().replace("Playlist: ", "")) response = ui.show_msg(self.window, _("Would you like to replace the existing playlist or append these songs?"), _("Existing Playlist"), "existingPlaylist", (_("Replace playlist"), 1, _("Append songs"), 2), default=self.config.existing_playlist_option) if response == 1: # Overwrite self.config.existing_playlist_option = response mpdh.call(self.client, 'playlistclear', plname) self.add_selected_to_playlist(plname) elif response == 2: # Append songs: self.config.existing_playlist_option = response self.add_selected_to_playlist(plname)
def mpd_connect(self): host, port, password = misc.mpd_env_vars() if not host: host = self.config.host[self.config.profile_num] if not port: port = self.config.port[self.config.profile_num] if not password: password = self.config.password[self.config.profile_num] mpdh.call(self.client, 'connect', host, port) if password: mpdh.call(self.client, 'password', password)
def searchfilter_on_enter(self, _entry): model, selected = self.current.get_selection().get_selected_rows() song_id = None if len(selected) > 0: # If items are selected, play the first selected item: song_id = self.current_get_songid(model.get_iter(selected[0]), model) elif len(model) > 0: # If nothing is selected: play the first item: song_id = self.current_get_songid(model.get_iter_first(), model) if song_id: self.searchfilter_toggle(None) mpdh.call(self.client, "playid", song_id)
def on_current_click(self, _treeview, path, _column): model = self.current.get_model() if self.filterbox_visible: self.searchfilter_on_enter(None) return try: i = model.get_iter(path) mpdh.call(self.client, "playid", self.current_get_songid(i, model)) except: pass self.sel_rows = False self.iterate_now()
def on_current_click(self, _treeview, path, _column): model = self.current.get_model() if self.filterbox_visible: self.searchfilter_on_enter(None) return try: i = model.get_iter(path) mpdh.call(self.client, 'playid', self.current_get_songid(i, model)) except: pass self.sel_rows = False self.iterate_now()
def searchfilter_on_enter(self, _entry): model, selected = self.current.get_selection().get_selected_rows() song_id = None if len(selected) > 0: # If items are selected, play the first selected item: song_id = self.current_get_songid(model.get_iter(selected[0]), model) elif len(model) > 0: # If nothing is selected: play the first item: song_id = self.current_get_songid(model.get_iter_first(), model) if song_id: self.searchfilter_toggle(None) mpdh.call(self.client, 'playid', song_id)
def playlist_name_exists(self, title, role, plname, skip_plname=""): # If the playlist already exists, and the user does not want to replace it, return True; In # all other cases, return False playlists = mpdh.call(self.client, 'listplaylists') if playlists is None: playlists = mpdh.call(self.client, 'lsinfo') for item in playlists: if 'playlist' in item: if mpdh.get(item, 'playlist') == plname and plname != skip_plname: if ui.show_msg(self.window, _("A playlist with this name already exists. Would you like to replace it?"), title, role, gtk.BUTTONS_YES_NO) == gtk.RESPONSE_YES: return False else: return True return False
def on_remove(self): treeviewsel = self.current_selection model, selected = treeviewsel.get_selected_rows() if len(selected) == len(self.currentdata) and not self.filterbox_visible: # Everything is selected, clear: mpdh.call(self.client, "clear") elif len(selected) > 0: # we are manipulating the model manually for speed, so... self.current_update_skip = True selected.reverse() if not self.filterbox_visible: # If we remove an item from the filtered results, this # causes a visual refresh in the interface. self.current.set_model(None) mpdh.call(self.client, "command_list_ok_begin") for path in selected: if not self.filterbox_visible: rownum = path[0] else: rownum = self.filter_row_mapping[path[0]] i = self.currentdata.get_iter((rownum, 0)) mpdh.call(self.client, "deleteid", self.current_get_songid(i, self.currentdata)) # Prevents the entire playlist from refreshing: self.current_songs.pop(rownum) self.currentdata.remove(i) mpdh.call(self.client, "command_list_end") if not self.filterbox_visible: self.current.set_model(model)
def on_remove(self): treeviewsel = self.current_selection model, selected = treeviewsel.get_selected_rows() if len(selected) == len( self.currentdata) and not self.filterbox_visible: # Everything is selected, clear: mpdh.call(self.client, 'clear') elif len(selected) > 0: # we are manipulating the model manually for speed, so... self.current_update_skip = True selected.reverse() if not self.filterbox_visible: # If we remove an item from the filtered results, this # causes a visual refresh in the interface. self.current.set_model(None) mpdh.call(self.client, 'command_list_ok_begin') for path in selected: if not self.filterbox_visible: rownum = path[0] else: rownum = self.filter_row_mapping[path[0]] i = self.currentdata.get_iter((rownum, 0)) mpdh.call(self.client, 'deleteid', self.current_get_songid(i, self.currentdata)) # Prevents the entire playlist from refreshing: self.current_songs.pop(rownum) self.currentdata.remove(i) mpdh.call(self.client, 'command_list_end') if not self.filterbox_visible: self.current.set_model(model)
def populate(self): if self.connected(): self.playlistsdata.clear() playlistinfo = [] playlists = mpdh.call(self.client, 'listplaylists') if playlists is None: playlists = mpdh.call(self.client, 'lsinfo') for item in playlists: if 'playlist' in item: playlistinfo.append(misc.escape_html(mpdh.get(item, 'playlist'))) playlistinfo.sort(key=lambda x: x.lower()) # Remove case sensitivity for item in playlistinfo: self.playlistsdata.append([gtk.STOCK_JUSTIFY_FILL, item]) if mpdh.mpd_major_version(self.client) >= 0.13: self.populate_playlists_for_menu(playlistinfo)
def library_get_path_files_recursive(self, path): results = [] for item in mpdh.call(self.client, 'lsinfo', path): if 'directory' in item: results = results + self.library_get_path_files_recursive(mpdh.get(item, 'directory')) elif 'file' in item: results.append(mpdh.get(item, 'file')) return results
def libsearchfilter_do_search(self, searchby, todo): if not self.prevlibtodo_base in todo: # Do library search based on first two letters: self.prevlibtodo_base = todo[:2] self.prevlibtodo_base_results = mpdh.call(self.client, 'search', searchby, self.prevlibtodo_base) subsearch = False else: subsearch = True # Now, use filtering similar to playlist filtering: # this make take some seconds... and we'll escape the search text because # we'll be searching for a match in items that are also escaped. todo = misc.escape_html(todo) todo = re.escape(todo) todo = '.*' + todo.replace(' ', ' .*').lower() regexp = re.compile(todo) matches = [] if searchby != 'any': for row in self.prevlibtodo_base_results: if regexp.match(unicode(mpdh.get(row, searchby)).lower()): matches.append(row) else: for row in self.prevlibtodo_base_results: for meta in row: if regexp.match(unicode(mpdh.get(row, meta)).lower()): matches.append(row) break if subsearch and len(matches) == len(self.librarydata): # nothing changed.. return self.library.freeze_child_notify() currlen = len(self.librarydata) newlist = [] for item in matches: if 'file' in item: newlist.append([self.sonatapb, self.library_set_data(path=mpdh.get(item, 'file')), self.parse_formatting(self.config.libraryformat, item, True)]) for i, item in enumerate(newlist): if i < currlen: j = self.librarydata.get_iter((i, )) for index in range(len(item)): if item[index] != self.librarydata.get_value(j, index): self.librarydata.set_value(j, index, item[index]) else: self.librarydata.append(item) # Remove excess items... newlen = len(newlist) if newlen == 0: self.librarydata.clear() else: for i in range(currlen-newlen): j = self.librarydata.get_iter((currlen-1-i,)) self.librarydata.remove(j) self.library.thaw_child_notify() if len(matches) == 0: gobject.idle_add(self.filtering_entry_make_red, self.searchtext) else: gobject.idle_add(self.library.set_cursor,'0') gobject.idle_add(self.filtering_entry_revert_color, self.searchtext)
def playlist_create(self, playlistname, oldname=None): mpdh.call(self.client, 'rm', playlistname) if oldname is not None: mpdh.call(self.client, 'rename', oldname, playlistname) else: mpdh.call(self.client, 'save', playlistname) self.populate() self.iterate_now()
def library_return_count(self, genre=None, artist=None, album=None, year=None): # Because mpd's 'count' is case sensitive, we have to # determine all equivalent items (case insensitive) and # call 'count' for each of them. Using 'list' + 'count' # involves much less data to be transferred back and # forth than to use 'search' and count manually. searches = self.library_compose_list_count_searchlist(genre, artist, album, year) playtime = 0 num_songs = 0 for s in searches: count = mpdh.call(self.client, 'count', *s) playtime += int(mpdh.get(count, 'playtime')) num_songs += int(mpdh.get(count, 'songs')) return (playtime, num_songs)
def library_return_search_items(self, genre=None, artist=None, album=None, year=None): # Returns all mpd items, using mpd's 'search', along with # playtime and num_songs. searches = self.library_compose_search_searchlist(genre, artist, album, year) for s in searches: args_tuple = tuple(map(str, s)) playtime = 0 num_songs = 0 results = [] if '' in s and mpdh.mpd_major_version(self.client) <= 0.13: # Can't search for empty tags, search broader and filter instead: # Strip empty tag args from tuple: pos = list(args_tuple).index('') strip_type = list(args_tuple)[pos-1] new_lst = [] for i, item in enumerate(list(args_tuple)): if i != pos and i != pos-1: new_lst.append(item) args_tuple = tuple(new_lst) else: strip_type = None if len(args_tuple) == 0: return None, 0, 0 items = mpdh.call(self.client, 'search', *args_tuple) if items is not None: for item in items: if strip_type is None or (strip_type is not None and not strip_type in item.keys()): match = True pos = 0 # Ensure that if, e.g., "foo" is searched, "foobar" isn't returned too for arg in args_tuple[::2]: if arg in item and unicode(mpdh.get(item, arg)).upper() != unicode(args_tuple[pos+1]).upper(): match = False break pos += 2 if match: results.append(item) num_songs += 1 playtime += int(mpdh.get(item, 'time', '0')) return (results, int(playtime), num_songs)
def library_return_search_items(self, genre=None, artist=None, album=None, year=None): # Returns all mpd items, using mpd's 'search', along with # playtime and num_songs. searches = self.library_compose_search_searchlist(genre, artist, album, year) for s in searches: args_tuple = tuple(map(str, s)) playtime = 0 num_songs = 0 results = [] if '' in s and mpdh.mpd_major_version(self.client) <= 0.13: # Can't search for empty tags, search broader and filter instead: # Strip empty tag args from tuple: pos = list(args_tuple).index('') strip_type = list(args_tuple)[pos-1] new_lst = [] for i, item in enumerate(list(args_tuple)): if i != pos and i != pos-1: new_lst.append(item) args_tuple = tuple(new_lst) else: strip_type = None if len(args_tuple) == 0: return None, 0, 0 items = mpdh.call(self.client, 'search', *args_tuple) if items is not None: for item in items: if strip_type is None or (strip_type is not None and not strip_type in item.keys()): match = True pos = 0 # Ensure that if, e.g., "foo" is searched, "foobar" isn't returned too for arg in args_tuple[::2]: if arg in item and unicode(mpdh.get(item, arg)).upper() != unicode(args_tuple[pos+1]).upper(): match = False break pos += 2 if match: results.append(item) num_songs += 1 playtime += mpdh.get(item, 'time', 0, True) return (results, int(playtime), num_songs)
def library_return_search_items(self, genre=None, artist=None, album=None, year=None): # Returns all mpd items, using mpd's 'search', along with # playtime and num_songs. Note that if one of the args is # '', the search results will only be correct for mpd=0.14 searches = self.library_compose_search_searchlist(genre, artist, album, year) for s in searches: args_tuple = tuple(map(str, s)) playtime = 0 num_songs = 0 results = [] items = mpdh.call(self.client, 'search', *args_tuple) if items is not None: for item in items: results.append(item) num_songs += 1 playtime += int(mpdh.get(item, 'time', '0')) return (results, int(playtime), num_songs)
def on_playlist_menu_click(self, action): plname = misc.unescape_html(action.get_name().replace("Playlist: ", "")) response = ui.show_msg(self.window, _("Would you like to replace the existing playlist or append these songs?"), _("Existing Playlist"), "existingPlaylist", (_("Replace playlist"), 1, _("Append songs"), 2), default=self.config.existing_playlist_option) if response == 1: # Overwrite self.config.existing_playlist_option = response self.playlist_create(plname) elif response == 2: # Append songs: self.config.existing_playlist_option = response mpdh.call(self.client, 'command_list_ok_begin') for song in self.get_current_songs(): mpdh.call(self.client, 'playlistadd', plname, mpdh.get(song, 'file')) mpdh.call(self.client, 'command_list_end') return
def on_sort_reverse(self, _action): if self.connected(): if not self.currentdata: return while gtk.events_pending(): gtk.main_iteration() top = 0 bot = len(self.currentdata) - 1 mpdh.call(self.client, 'command_list_ok_begin') while top < bot: mpdh.call(self.client, 'swap', top, bot) top = top + 1 bot = bot - 1 mpdh.call(self.client, 'command_list_end') self.iterate_now()
def on_sort_reverse(self, _action): if self.connected(): if not self.currentdata: return while gtk.events_pending(): gtk.main_iteration() top = 0 bot = len(self.currentdata) - 1 mpdh.call(self.client, "command_list_ok_begin") while top < bot: mpdh.call(self.client, "swap", top, bot) top = top + 1 bot = bot - 1 mpdh.call(self.client, "command_list_end") self.iterate_now()
def library_populate_filesystem_data(self, path): # List all dirs/files at path bd = [] if path == '/' and self.lib_view_filesystem_cache is not None: # Use cache if possible... bd = self.lib_view_filesystem_cache else: for item in mpdh.call(self.client, 'lsinfo', path): if 'directory' in item: name = mpdh.get(item, 'directory').split('/')[-1] data = self.library_set_data(path=mpdh.get(item, 'directory')) bd += [('d' + unicode(name).lower(), [self.openpb, data, misc.escape_html(name)])] elif 'file' in item: data = self.library_set_data(path=mpdh.get(item, 'file')) bd += [('f' + unicode(mpdh.get(item, 'file')).lower(), [self.sonatapb, data, self.parse_formatting(self.config.libraryformat, item, True)])] bd.sort(key=misc.first_of_2tuple) if path != '/' and len(bd) > 0: bd = self.library_populate_add_parent_rows() + bd if path == '/': self.lib_view_filesystem_cache = bd return bd
def library_return_count(self, genre=None, artist=None, album=None, year=None): # Because mpd's 'count' is case sensitive, we have to # determine all equivalent items (case insensitive) and # call 'count' for each of them. Using 'list' + 'count' # involves much less data to be transferred back and # forth than to use 'search' and count manually. searches = self.library_compose_list_count_searchlist(genre, artist, album, year) playtime = 0 num_songs = 0 for s in searches: if '' in s and mpdh.mpd_major_version(self.client) <= 0.13: # Can't return count for empty tags, use search instead: _results, playtime, num_songs = self.library_return_search_items(genre=genre, artist=artist, album=album, year=year) else: count = mpdh.call(self.client, 'count', *s) playtime += mpdh.get(count, 'playtime', 0, True) num_songs += mpdh.get(count, 'songs', 0, True) return (playtime, num_songs)
def library_return_count(self, genre=None, artist=None, album=None, year=None): # Because mpd's 'count' is case sensitive, we have to # determine all equivalent items (case insensitive) and # call 'count' for each of them. Using 'list' + 'count' # involves much less data to be transferred back and # forth than to use 'search' and count manually. searches = self.library_compose_list_count_searchlist(genre, artist, album, year) playtime = 0 num_songs = 0 for s in searches: if '' in s and mpdh.mpd_major_version(self.client) <= 0.13: # Can't return count for empty tags, use search instead: _results, playtime, num_songs = self.library_return_search_items(genre=genre, artist=artist, album=album, year=year) else: count = mpdh.call(self.client, 'count', *s) playtime += int(mpdh.get(count, 'playtime')) num_songs += int(mpdh.get(count, 'songs')) return (playtime, num_songs)
def on_playlist_menu_click(self, action): plname = misc.unescape_html(action.get_name().replace( "Playlist: ", "")) response = ui.show_msg( self.window, _("Would you like to replace the existing playlist or append these songs?" ), _("Existing Playlist"), "existingPlaylist", (_("Replace playlist"), 1, _("Append songs"), 2), default=self.config.existing_playlist_option) if response == 1: # Overwrite self.config.existing_playlist_option = response self.playlist_create(plname) elif response == 2: # Append songs: self.config.existing_playlist_option = response mpdh.call(self.client, 'command_list_ok_begin') for song in self.get_current_songs(): mpdh.call(self.client, 'playlistadd', plname, mpdh.get(song, 'file')) mpdh.call(self.client, 'command_list_end') return
def _execute_prev(self): mpdh.call(self.client, 'previous')
def _execute_next(self): mpdh.call(self.client, 'next')
def _execute_stop(self): mpdh.call(self.client, 'stop')
def _execute_pause(self): mpdh.call(self.client, 'pause', 1)
def sort(self, mode, column=None): if self.connected(): if not self.currentdata: return while gtk.events_pending(): gtk.main_iteration() songs = [] track_num = 0 if mode[0:3] == 'col': col_num = int(mode.replace('col', '')) if column.get_sort_indicator(): # If this column was already sorted, reverse list: self.column_sorted = (column, self.column_sorted[1]) self.on_sort_reverse(None) return else: self.column_sorted = (column, gtk.SORT_DESCENDING) mode = "col" # If the first tag in the format is song length, we will make sure to compare # the same number of items in the song length string (e.g. always use # ##:##:##) and pad the first item to two (e.g. #:##:## -> ##:##:##) custom_sort = False if mode == 'col': custom_sort, custom_pos = self.sort_get_first_format_tag( self.config.currentformat, col_num, 'L') for track in self.current_songs: record = {} # Those items that don't have the specified tag will be put at # the end of the list (hence the 'zzzzzzz'): zzz = 'zzzzzzzz' if mode == 'artist': record["sortby"] = (misc.lower_no_the( mpdh.get(track, 'artist', zzz)), mpdh.get(track, 'album', zzz).lower(), mpdh.getnum(track, 'disc', '0', True, 0), mpdh.getnum(track, 'track', '0', True, 0)) elif mode == 'album': record["sortby"] = (mpdh.get(track, 'album', zzz).lower(), mpdh.getnum(track, 'disc', '0', True, 0), mpdh.getnum(track, 'track', '0', True, 0)) elif mode == 'file': record["sortby"] = mpdh.get(track, 'file', zzz).lower().split('/')[-1] elif mode == 'dirfile': record["sortby"] = mpdh.get(track, 'file', zzz).lower() elif mode == 'col': # Sort by column: record["sortby"] = misc.unbold( self.currentdata.get_value( self.currentdata.get_iter((track_num, 0)), col_num).lower()) if custom_sort: record["sortby"] = self.sanitize_songlen_for_sorting( record["sortby"], custom_pos) else: record["sortby"] = mpdh.get(track, mode, zzz).lower() record["id"] = int(track["id"]) songs.append(record) track_num = track_num + 1 songs.sort(key=lambda x: x["sortby"]) pos = 0 mpdh.call(self.client, 'command_list_ok_begin') for item in songs: mpdh.call(self.client, 'moveid', item["id"], pos) pos += 1 mpdh.call(self.client, 'command_list_end') self.iterate_now() self.header_update_column_indicators()
def _execute_play(self): mpdh.call(self.client, 'play')
def _execute_bool(self, cmd): """Set the reverse the value of cmd""" mpdh.call(self.client, cmd, int(not int(self.status[cmd])))
def on_dnd(self, treeview, drag_context, x, y, selection, _info, timestamp): drop_info = treeview.get_dest_row_at_pos(x, y) if selection.data is not None: if not os.path.isdir(misc.file_from_utf8(self.config.musicdir[self.config.profile_num])): return # DND from outside sonata: uri = selection.data.strip() path = urllib.url2pathname(uri) paths = path.rsplit("\n") mpdpaths = [] # Strip off paranthesis so that we can DND entire music dir # if we wish. musicdir = self.config.musicdir[self.config.profile_num][:-1] for i, path in enumerate(paths): paths[i] = path.rstrip("\r") if paths[i].startswith("file://"): paths[i] = paths[i][7:] elif paths[i].startswith("file:"): paths[i] = paths[i][5:] if paths[i].startswith(musicdir): paths[i] = paths[i][len(self.config.musicdir[self.config.profile_num]) :] if len(paths[i]) == 0: paths[i] = "/" listallinfo = mpdh.call(self.client, "listallinfo", paths[i]) for item in listallinfo: if "file" in item: mpdpaths.append(mpdh.get(item, "file")) elif mpdh.mpd_major_version(self.client) >= 0.14: # Add local file, available in mpd 0.14. This currently won't # work because python-mpd does not support unix socket paths, # which is needed for authentication for local files. It's also # therefore untested. if os.path.isdir(misc.file_from_utf8(paths[i])): filenames = misc.get_files_recursively(paths[i]) else: filenames = [paths[i]] for filename in filenames: if os.path.exists(misc.file_from_utf8(filename)): mpdpaths.append("file://" + urllib.quote(filename)) if len(mpdpaths) > 0: # Items found, add to list at drop position: if drop_info: destpath, position = drop_info if position in (gtk.TREE_VIEW_DROP_BEFORE, gtk.TREE_VIEW_DROP_INTO_OR_BEFORE): songid = destpath[0] else: songid = destpath[0] + 1 else: songid = len(self.currentdata) for mpdpath in mpdpaths: mpdh.call(self.client, "addid", mpdpath, songid) self.iterate_now() return # Otherwise, it's a DND just within the current playlist model = treeview.get_model() _foobar, selected = self.current_selection.get_selected_rows() # calculate all this now before we start moving stuff drag_sources = [] for path in selected: index = path[0] i = model.get_iter(path) songid = self.current_get_songid(i, model) text = model.get_value(i, 1) drag_sources.append([index, i, songid, text]) # Keep track of the moved iters so we can select them afterwards moved_iters = [] # We will manipulate self.current_songs and model to prevent the entire playlist # from refreshing offset = 0 mpdh.call(self.client, "command_list_ok_begin") for source in drag_sources: index, i, songid, text = source if drop_info: destpath, position = drop_info dest = destpath[0] + offset if dest < index: offset = offset + 1 if position in (gtk.TREE_VIEW_DROP_BEFORE, gtk.TREE_VIEW_DROP_INTO_OR_BEFORE): self.current_songs.insert(dest, self.current_songs[index]) if dest < index + 1: self.current_songs.pop(index + 1) mpdh.call(self.client, "moveid", songid, dest) else: self.current_songs.pop(index) mpdh.call(self.client, "moveid", songid, dest - 1) model.insert(dest, model[index]) moved_iters += [model.get_iter((dest,))] model.remove(i) else: self.current_songs.insert(dest + 1, self.current_songs[index]) if dest < index: self.current_songs.pop(index + 1) mpdh.call(self.client, "moveid", songid, dest + 1) else: self.current_songs.pop(index) mpdh.call(self.client, "moveid", songid, dest) model.insert(dest + 1, model[index]) moved_iters += [model.get_iter((dest + 1,))] model.remove(i) else: # dest = int(self.status['playlistlength']) - 1 dest = len(self.currentdata) - 1 mpdh.call(self.client, "moveid", songid, dest) self.current_songs.insert(dest + 1, self.current_songs[index]) self.current_songs.pop(index) model.insert(dest + 1, model[index]) moved_iters += [model.get_iter((dest + 1,))] model.remove(i) # now fixup for source in drag_sources: if dest < index: # we moved it back, so all indexes inbetween increased by 1 if dest < source[0] < index: source[0] += 1 else: # we moved it ahead, so all indexes inbetween decreased by 1 if index < source[0] < dest: source[0] -= 1 mpdh.call(self.client, "command_list_end") # we are manipulating the model manually for speed, so... self.current_update_skip = True if drag_context.action == gtk.gdk.ACTION_MOVE: drag_context.finish(True, True, timestamp) self.header_hide_all_indicators(self.current, False) self.iterate_now() gobject.idle_add(self.dnd_retain_selection, treeview.get_selection(), moved_iters)
def sort(self, mode, column=None): if self.connected(): if not self.currentdata: return while gtk.events_pending(): gtk.main_iteration() songs = [] track_num = 0 if mode[0:3] == "col": col_num = int(mode.replace("col", "")) if column.get_sort_indicator(): # If this column was already sorted, reverse list: self.column_sorted = (column, self.column_sorted[1]) self.on_sort_reverse(None) return else: self.column_sorted = (column, gtk.SORT_DESCENDING) mode = "col" # If the first tag in the format is song length, we will make sure to compare # the same number of items in the song length string (e.g. always use # ##:##:##) and pad the first item to two (e.g. #:##:## -> ##:##:##) custom_sort = False if mode == "col": custom_sort, custom_pos = self.sort_get_first_format_tag(self.config.currentformat, col_num, "L") for track in self.current_songs: record = {} # Those items that don't have the specified tag will be put at # the end of the list (hence the 'zzzzzzz'): zzz = "zzzzzzzz" if mode == "artist": record["sortby"] = ( misc.lower_no_the(mpdh.get(track, "artist", zzz)), mpdh.get(track, "album", zzz).lower(), mpdh.getnum(track, "disc", "0", True, 0), mpdh.getnum(track, "track", "0", True, 0), ) elif mode == "album": record["sortby"] = ( mpdh.get(track, "album", zzz).lower(), mpdh.getnum(track, "disc", "0", True, 0), mpdh.getnum(track, "track", "0", True, 0), ) elif mode == "file": record["sortby"] = mpdh.get(track, "file", zzz).lower().split("/")[-1] elif mode == "dirfile": record["sortby"] = mpdh.get(track, "file", zzz).lower() elif mode == "col": # Sort by column: record["sortby"] = misc.unbold( self.currentdata.get_value(self.currentdata.get_iter((track_num, 0)), col_num).lower() ) if custom_sort: record["sortby"] = self.sanitize_songlen_for_sorting(record["sortby"], custom_pos) else: record["sortby"] = mpdh.get(track, mode, zzz).lower() record["id"] = int(track["id"]) songs.append(record) track_num = track_num + 1 songs.sort(key=lambda x: x["sortby"]) pos = 0 mpdh.call(self.client, "command_list_ok_begin") for item in songs: mpdh.call(self.client, "moveid", item["id"], pos) pos += 1 mpdh.call(self.client, "command_list_end") self.iterate_now() self.header_update_column_indicators()
def current_update(self, prevstatus_playlist, new_playlist_length): if self.connected(): if self.sonata_loaded(): playlistposition = self.current.get_visible_rect()[1] self.current.freeze_child_notify() if not self.current_update_skip: if not self.filterbox_visible: self.current.set_model(None) if prevstatus_playlist: changed_songs = mpdh.call(self.client, "plchanges", prevstatus_playlist) else: changed_songs = mpdh.call(self.client, "plchanges", 0) self.current_songs = [] newlen = int(new_playlist_length) currlen = len(self.currentdata) for track in changed_songs: pos = int(mpdh.get(track, "pos")) items = [] for part in self.columnformat: items += [self.parse_formatting(part, track, True)] if pos < currlen: # Update attributes for item: i = self.currentdata.get_iter((pos,)) trackid = int(mpdh.get(track, "id")) if trackid != self.currentdata.get_value(i, 0): self.currentdata.set_value(i, 0, trackid) for index in range(len(items)): if items[index] != self.currentdata.get_value(i, index + 1): self.currentdata.set_value(i, index + 1, items[index]) self.current_songs[pos] = track else: # Add new item: self.currentdata.append([int(mpdh.get(track, "id"))] + items) self.current_songs.append(track) if newlen == 0: self.currentdata.clear() self.current_songs = [] else: # Remove excess songs: for i in range(currlen - newlen): it = self.currentdata.get_iter((currlen - 1 - i,)) self.currentdata.remove(it) self.current_songs = self.current_songs[:newlen] if not self.filterbox_visible: self.current.set_model(self.currentdata) self.current_update_skip = False # Update statusbar time: self.total_time = 0 for track in self.current_songs: try: self.total_time = self.total_time + int(mpdh.get(track, "time")) except: pass if "pos" in self.songinfo(): currsong = int(mpdh.get(self.songinfo(), "pos")) self.boldrow(currsong) self.prev_boldrow = currsong if self.filterbox_visible: # Refresh filtered results: self.prevtodo = ( "RETAIN_POS_AND_SEL" ) # Hacky, but this ensures we retain the self.current position/selection self.plpos = playlistposition self.searchfilter_feed_loop(self.filterpattern) elif self.sonata_loaded(): self.playlist_retain_view(self.current, playlistposition) self.current.thaw_child_notify() self.header_update_column_indicators() self.update_statusbar() ui.change_cursor(None)
def _execute_pp(self): if self.status['state'] in ['play']: mpdh.call(self.client, 'pause', 1) elif self.status['state'] in ['pause', 'stop']: mpdh.call(self.client, 'play')
def library_populate_toplevel_data(self, genreview=False, artistview=False, albumview=False): bd = self.library_get_toplevel_cache(genreview, artistview, albumview) if bd is not None: # We have our cached data, woot. return bd bd = [] if genreview or artistview: # Only for artist/genre views, album view is handled differently # since multiple artists can have the same album name if genreview: items = self.library_return_list_items('genre') pb = self.genrepb else: items = self.library_return_list_items('artist') pb = self.artistpb if not (self.NOTAG in items): items.append(self.NOTAG) for item in items: if genreview: playtime, num_songs = self.library_return_count(genre=item) data = self.library_set_data(genre=item) else: playtime, num_songs = self.library_return_count(artist=item) data = self.library_set_data(artist=item) display = misc.escape_html(item) display += self.add_display_info(num_songs, int(playtime)/60) bd += [(misc.lower_no_the(item), [pb, data, display])] elif albumview: albums = [] untagged_found = False for item in mpdh.call(self.client, 'listallinfo', '/'): if 'file' in item and 'album' in item: album = mpdh.get(item, 'album') artist = mpdh.get(item, 'artist', self.NOTAG) year = mpdh.get(item, 'date', self.NOTAG) filepath = os.path.dirname(mpdh.get(item, 'file')) data = self.library_set_data(album=album, artist=artist, year=year, path=filepath) albums.append(data) if album == self.NOTAG: untagged_found = True if not untagged_found: albums.append(self.library_set_data(album=self.NOTAG)) albums = misc.remove_list_duplicates(albums, case=False) albums = self.list_identify_VA_albums(albums) for item in albums: album, artist, year, path = self.library_get_data(item, 'album', 'artist', 'year', 'path') playtime, num_songs = self.library_return_count(artist=artist, album=album, year=year) if num_songs > 0: data = self.library_set_data(artist=artist, album=album, year=year, path=path) cache_data = self.library_set_data(artist=artist, album=album, path=path) display = misc.escape_html(album) if artist and year and len(artist) > 0 and len(year) > 0 and artist != self.NOTAG and year != self.NOTAG: display += " <span weight='light'>(" + misc.escape_html(artist) + ", " + misc.escape_html(year) + ")</span>" elif artist and len(artist) > 0 and artist != self.NOTAG: display += " <span weight='light'>(" + misc.escape_html(artist) + ")</span>" elif year and len(year) > 0 and year != self.NOTAG: display += " <span weight='light'>(" + misc.escape_html(year) + ")</span>" display += self.add_display_info(num_songs, int(playtime)/60) pb = self.artwork.get_library_artwork_cached_pb(cache_data, self.albumpb) bd += [(misc.lower_no_the(album), [pb, data, display])] bd.sort(locale.strcoll, key=misc.first_of_2tuple) if genreview: self.lib_view_genre_cache = bd elif artistview: self.lib_view_artist_cache = bd elif albumview: self.lib_view_album_cache = bd return bd
def current_update(self, prevstatus_playlist, new_playlist_length): if self.connected(): if self.sonata_loaded(): playlistposition = self.current.get_visible_rect()[1] self.current.freeze_child_notify() if not self.current_update_skip: if not self.filterbox_visible: self.current.set_model(None) if prevstatus_playlist: changed_songs = mpdh.call(self.client, 'plchanges', prevstatus_playlist) else: changed_songs = mpdh.call(self.client, 'plchanges', 0) self.current_songs = [] newlen = int(new_playlist_length) currlen = len(self.currentdata) for track in changed_songs: pos = int(mpdh.get(track, 'pos')) items = [] for part in self.columnformat: items += [self.parse_formatting(part, track, True)] if pos < currlen: # Update attributes for item: i = self.currentdata.get_iter((pos, )) trackid = int(mpdh.get(track, 'id')) if trackid != self.currentdata.get_value(i, 0): self.currentdata.set_value(i, 0, trackid) for index in range(len(items)): if items[index] != self.currentdata.get_value( i, index + 1): self.currentdata.set_value( i, index + 1, items[index]) self.current_songs[pos] = track else: # Add new item: self.currentdata.append([int(mpdh.get(track, 'id'))] + items) self.current_songs.append(track) if newlen == 0: self.currentdata.clear() self.current_songs = [] else: # Remove excess songs: for i in range(currlen - newlen): it = self.currentdata.get_iter((currlen - 1 - i, )) self.currentdata.remove(it) self.current_songs = self.current_songs[:newlen] if not self.filterbox_visible: self.current.set_model(self.currentdata) self.current_update_skip = False # Update statusbar time: self.total_time = 0 for track in self.current_songs: try: self.total_time = self.total_time + int( mpdh.get(track, 'time')) except: pass if 'pos' in self.songinfo(): currsong = int(mpdh.get(self.songinfo(), 'pos')) self.boldrow(currsong) self.prev_boldrow = currsong if self.filterbox_visible: # Refresh filtered results: self.prevtodo = "RETAIN_POS_AND_SEL" # Hacky, but this ensures we retain the self.current position/selection self.plpos = playlistposition self.searchfilter_feed_loop(self.filterpattern) elif self.sonata_loaded(): self.playlist_retain_view(self.current, playlistposition) self.current.thaw_child_notify() self.header_update_column_indicators() self.update_statusbar() ui.change_cursor(None)
def on_dnd(self, treeview, drag_context, x, y, selection, _info, timestamp): drop_info = treeview.get_dest_row_at_pos(x, y) if selection.data is not None: if not os.path.isdir( misc.file_from_utf8( self.config.musicdir[self.config.profile_num])): return # DND from outside sonata: uri = selection.data.strip() path = urllib.url2pathname(uri) paths = path.rsplit('\n') mpdpaths = [] # Strip off paranthesis so that we can DND entire music dir # if we wish. musicdir = self.config.musicdir[self.config.profile_num][:-1] for i, path in enumerate(paths): paths[i] = path.rstrip('\r') if paths[i].startswith('file://'): paths[i] = paths[i][7:] elif paths[i].startswith('file:'): paths[i] = paths[i][5:] if paths[i].startswith(musicdir): paths[i] = paths[i][ len(self.config.musicdir[self.config.profile_num]):] if len(paths[i]) == 0: paths[i] = "/" listallinfo = mpdh.call(self.client, 'listallinfo', paths[i]) for item in listallinfo: if 'file' in item: mpdpaths.append(mpdh.get(item, 'file')) elif mpdh.mpd_major_version(self.client) >= 0.14: # Add local file, available in mpd 0.14. This currently won't # work because python-mpd does not support unix socket paths, # which is needed for authentication for local files. It's also # therefore untested. if os.path.isdir(misc.file_from_utf8(paths[i])): filenames = misc.get_files_recursively(paths[i]) else: filenames = [paths[i]] for filename in filenames: if os.path.exists(misc.file_from_utf8(filename)): mpdpaths.append("file://" + urllib.quote(filename)) if len(mpdpaths) > 0: # Items found, add to list at drop position: if drop_info: destpath, position = drop_info if position in (gtk.TREE_VIEW_DROP_BEFORE, gtk.TREE_VIEW_DROP_INTO_OR_BEFORE): songid = destpath[0] else: songid = destpath[0] + 1 else: songid = len(self.currentdata) for mpdpath in mpdpaths: mpdh.call(self.client, 'addid', mpdpath, songid) self.iterate_now() return # Otherwise, it's a DND just within the current playlist model = treeview.get_model() _foobar, selected = self.current_selection.get_selected_rows() # calculate all this now before we start moving stuff drag_sources = [] for path in selected: index = path[0] i = model.get_iter(path) songid = self.current_get_songid(i, model) text = model.get_value(i, 1) drag_sources.append([index, i, songid, text]) # Keep track of the moved iters so we can select them afterwards moved_iters = [] # We will manipulate self.current_songs and model to prevent the entire playlist # from refreshing offset = 0 mpdh.call(self.client, 'command_list_ok_begin') for source in drag_sources: index, i, songid, text = source if drop_info: destpath, position = drop_info dest = destpath[0] + offset if dest < index: offset = offset + 1 if position in (gtk.TREE_VIEW_DROP_BEFORE, gtk.TREE_VIEW_DROP_INTO_OR_BEFORE): self.current_songs.insert(dest, self.current_songs[index]) if dest < index + 1: self.current_songs.pop(index + 1) mpdh.call(self.client, 'moveid', songid, dest) else: self.current_songs.pop(index) mpdh.call(self.client, 'moveid', songid, dest - 1) model.insert(dest, model[index]) moved_iters += [model.get_iter((dest, ))] model.remove(i) else: self.current_songs.insert(dest + 1, self.current_songs[index]) if dest < index: self.current_songs.pop(index + 1) mpdh.call(self.client, 'moveid', songid, dest + 1) else: self.current_songs.pop(index) mpdh.call(self.client, 'moveid', songid, dest) model.insert(dest + 1, model[index]) moved_iters += [model.get_iter((dest + 1, ))] model.remove(i) else: #dest = int(self.status['playlistlength']) - 1 dest = len(self.currentdata) - 1 mpdh.call(self.client, 'moveid', songid, dest) self.current_songs.insert(dest + 1, self.current_songs[index]) self.current_songs.pop(index) model.insert(dest + 1, model[index]) moved_iters += [model.get_iter((dest + 1, ))] model.remove(i) # now fixup for source in drag_sources: if dest < index: # we moved it back, so all indexes inbetween increased by 1 if dest < source[0] < index: source[0] += 1 else: # we moved it ahead, so all indexes inbetween decreased by 1 if index < source[0] < dest: source[0] -= 1 mpdh.call(self.client, 'command_list_end') # we are manipulating the model manually for speed, so... self.current_update_skip = True if drag_context.action == gtk.gdk.ACTION_MOVE: drag_context.finish(True, True, timestamp) self.header_hide_all_indicators(self.current, False) self.iterate_now() gobject.idle_add(self.dnd_retain_selection, treeview.get_selection(), moved_iters)