# Copyright (c) 2011 Edward Langley # All rights reserved. import mpdprotocol import responsedict import urwid import itertools from twisted.internet import defer class SelectableText(urwid.Text): def keypress(self, __, key): return key def selectable(self): return True class Playlist(urwid.WidgetWrap): def __init__(self, columns, col_display, plylst=None): if plylst == None: plylst = responsedict.ResponseDict() self.columns = columns self.display_pref = col_display self.texts = self.get_texts(plylst.as_list()) self.body = urwid.SimpleFocusListWalker([]) self.make_playlist() self.titles = self.make_columns() self.header = urwid.Pile([self.titles, urwid.Divider('-')]) widget = urwid.ListBox(self.body) widget = urwid.Frame(widget, self.header) urwid.WidgetWrap.__init__(self, widget) def make_columns(self): return urwid.Columns([ ('weight',self.display_pref[c].weight,SelectableText(self.display_pref[c].dispname)) for c in self.columns ],1) def get_texts(self, choices): #TODO: re-work column info to separate data from presentation info texts = [] for x in choices: texts.append([]) for col in self.columns: display = self.display_pref[col] texts[-1].append((display.weight,display.transform(x.get(col,[""])[0]))) return texts def update_texts(self, choices): self.texts = self.get_texts(choices.as_list()) self.update_playlist() def make_body(self): for x in self.texts: new_widg = [('weight',wgt,SelectableText(it,wrap='clip')) for wgt,it in x] new_widg = urwid.Columns(new_widg, 1) new_widg = urwid.AttrMap(new_widg, 'unselected', 'selected') self.body.append(new_widg) def update_playlist(self): del self.body[:] self.make_body() def make_playlist(self): self.make_body() def exit_on_q(key): if key in ('q', 'Q'): try: raise urwid.ExitMainLoop() finally: reactor.stop() cols = [ 'Pos', 'Date', 'Album', 'Artist', 'AlbumArtist', 'Title', ] import collections class DisplayDict(collections.Mapping): def __init__(self, data): self.row_class=collections.namedtuple('displaytuple', ['dispname','weight','transform']) self.data = self.normalize(data) def normalize(self, data): out = {} for key,val in data.iteritems(): dispname,weight,transform = None,1,lambda x:x if hasattr(val, '__iter__'): if len(val) == 3: dispname,weight,transform = val if len(val) == 2: dispname,weight = val if dispname is None: dispname = key out[key] = self.row_class(dispname, weight, transform) return out def __getitem__(self, key): head, _, tail = key.partition('.') if tail: try: return getattr(self.data[head], tail) except AttributeError, e: raise KeyError('Key not found: %s' % key) else: return self.data[head] def __iter__(self): return iter(self.data) def __len__(self): return len(self.data) col_display = DisplayDict( dict( Pos=('#',1,lambda x: x and str(int(x) + 1)), Date=('Year', 4), Album=(None,8), Artist=(None,8), AlbumArtist=('Album Artist',5), Title=(None,13), )) @defer.inlineCallbacks def get_playlist(): client = yield mpdprotocol.get_client() method, data = yield client.sendCommand('playlistinfo') loop.widget.update_texts(data) loop.draw_screen() while True: idle = yield client.idle(['playlist']) method, data = yield client.sendCommand('playlistinfo') loop.widget.update_texts(data) loop.draw_screen() from twisted.internet import reactor palette = [ ('selected', 'white', 'black'), ('selected', 'black', 'white'), ] loop = urwid.MainLoop(Playlist(cols, col_display), palette, event_loop=urwid.TwistedEventLoop(), unhandled_input=exit_on_q) reactor.callWhenRunning(get_playlist) loop.run()