[Assorted-commits] SF.net SVN: assorted:[1140] music-labeler/trunk
Brought to you by:
yangzhang
From: <yan...@us...> - 2009-01-24 23:57:54
|
Revision: 1140 http://assorted.svn.sourceforge.net/assorted/?rev=1140&view=rev Author: yangzhang Date: 2009-01-24 23:57:46 +0000 (Sat, 24 Jan 2009) Log Message: ----------- - added multi-track tagging support with song labels in scrolled, tabular layout - added track loading from the "New" playlist - fixed up the pop-up window drawing - added semi-colon/return/shift-return handling Modified Paths: -------------- music-labeler/trunk/README music-labeler/trunk/src/ml.py Modified: music-labeler/trunk/README =================================================================== --- music-labeler/trunk/README 2009-01-24 11:01:40 UTC (rev 1139) +++ music-labeler/trunk/README 2009-01-24 23:57:46 UTC (rev 1140) @@ -10,8 +10,11 @@ Requirements: - [pygtk] 2.13 -- [Python Commons] +- [Mutagen] 1.14 +[pygtk]: http://www.pygtk.org/ +[Mutagen]: http://code.google.com/p/quodlibet/wiki/Development/Mutagen + Related ------- Modified: music-labeler/trunk/src/ml.py =================================================================== --- music-labeler/trunk/src/ml.py 2009-01-24 11:01:40 UTC (rev 1139) +++ music-labeler/trunk/src/ml.py 2009-01-24 23:57:46 UTC (rev 1140) @@ -1,9 +1,18 @@ #!/usr/bin/env python +from __future__ import with_statement from gtk import * from cgi import escape +import itertools import gtk.keysyms as k, gtk.gdk as gdk +from mutagen import File +from os.path import expanduser +cap = 50 +def trunc(s): + s = str(s) + return s[:cap] + '...' if len(s) > cap else s + class struct(object): pass def debug(*args): @@ -29,180 +38,214 @@ w.connect('delete-event', lambda *args: False) w.connect('destroy', lambda *args: main_quit()) - h = HBox() - w.add(h) + tab = Table(rows=1, columns=2) + # TODO: make main window scrollable + if 1: + s = ScrolledWindow() + s.add_with_viewport(tab) + s.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) + w.add(s) + else: + w.add(tab) es = [] + rows = itertools.count() - def refresh(): - 'Called when the list is changed.' - labels.sort(key = str.lower) - on_change() + def make_e(track): - e = Entry() - self = struct() + def refresh(): + 'Called when the list is changed.' + labels.sort(key = str.lower) + on_change() - def get_pieces(do_strip = False, truncate = False): - """Return left, mid, right, where mid is the part of the Entry that the - cursor is currently positioned over, and left and right is all the text - to the left and right, respectively. If do_strip is True, also strip off - whitespace around mid.""" - pos = e.get_position() - cur = e.get_text() - if truncate: cur = cur[:pos] - start = max(cur.rfind(sep, 0, pos) + 1, 0) - end = cur.find(sep, pos) % (len(cur) + 1) - mid = cur[start:end] - res = (cur[:start], (mid.strip() if do_strip else mid), cur[end:]) - debug(pos, start, end, mid, res) - return res + e = Entry() + self = struct() - def get_mid(): return get_pieces(True)[1] - def get_prefix(): return get_pieces(True, True)[1] + def get_pieces(do_strip = False, truncate = False): + """Return left, mid, right, where mid is the part of the Entry that the + cursor is currently positioned over, and left and right is all the text + to the left and right, respectively. If do_strip is True, also strip off + whitespace around mid.""" + pos = e.get_position() + cur = e.get_text() + if truncate: cur = cur[:pos] + start = max(cur.rfind(sep, 0, pos) + 1, 0) + end = cur.find(sep, pos) % (len(cur) + 1) + mid = cur[start:end] + res = (cur[:start], (mid.strip() if do_strip else mid), cur[end:]) + debug(pos, start, end, mid, res) + return res - @connecting(e, 'focus-out-event') - def on_focus_out(*args): - 'Destroy the pop-up completion window.' - debug('focus-out') + def get_mid(): return get_pieces(True)[1] + def get_prefix(): return get_pieces(True, True)[1] - @connecting(e, 'focus') - def on_focus(*args): - debug('focus') + @connecting(e, 'focus-out-event') + def on_focus_out(*args): + 'Destroy the pop-up completion window.' + debug('focus-out') + tw.hide() - @connecting(e, 'focus-in-event') - def on_focus_in(*args): - debug('focus-in') + @connecting(e, 'focus') + def on_focus(*args): + debug('focus') - @connecting_after(e, 'notify::cursor-position') - def on_change(*args): - 'Filter and highlight the list based on entered text.' - pat = get_prefix().lower() - # TODO: make the matching match up with the highlighting (use - # word-boundaries) - self.matches = matches = [lbl for lbl in labels if pat in lbl.lower()] - l.clear() - for m in matches: - pieces = [] - lasti = i = 0 - lp = len(pat) - if pat != '': - while i <= len(m) - lp: - if (i == 0 or not m[i-1].isalnum()) and m[i:i+lp].lower() == pat: - pieces += [m[lasti:i], (m[i:i+lp],)] - i += lp - lasti = i - else: - i += 1 - pieces += [m[lasti:]] - m = ''.join(('<b>' + escape(p[0]) + '</b>' - if type(p) == tuple - else escape(p)) - for p in pieces) - else: - m = escape(m) - l.append([m]) - # Something is always selected in the list. - t.get_selection().select_path(0) - t.scroll_to_cell(0) + @connecting(e, 'focus-in-event') + def on_focus_in(*args): + debug('focus-in') - @connecting(e, 'key-press-event') - def on_key_press(src, ev): - 'Handle special keys.' + @connecting_after(e, 'notify::cursor-position') + def on_change(*args): + 'Filter and highlight the list based on entered text.' + pat = get_prefix().lower() + # TODO: make the matching match up with the highlighting (use + # word-boundaries) + self.matches = matches = [lbl for lbl in labels if pat in lbl.lower()] + l.clear() + for m in matches: + pieces = [] + lasti = i = 0 + lp = len(pat) + if pat != '': + while i <= len(m) - lp: + if (i == 0 or not m[i-1].isalnum()) and m[i:i+lp].lower() == pat: + pieces += [m[lasti:i], (m[i:i+lp],)] + i += lp + lasti = i + else: + i += 1 + pieces += [m[lasti:]] + m = ''.join(('<b>' + escape(p[0]) + '</b>' + if type(p) == tuple + else escape(p)) + for p in pieces) + else: + m = escape(m) + l.append([m]) + # Something is always selected in the list. + t.get_selection().select_path(0) + t.scroll_to_cell(0) - def complete(): - '''Complete the entered text using currently highlighted list item and - advance focus to the next label (creating one if necessary).''' - # Complete. - mdl, itr = t.get_selection().get_selected() - assert mdl is l - (pos,) = l.get_path(itr) - left, mid, right = get_pieces(True) - debug(repr(left), repr(mid), repr(right)) - if left != '' and not left[-1:].isspace(): left += ' ' - before_pos = left + self.matches[pos] + sep - if not right[:0].isspace(): before_pos += ' ' - e.set_text(before_pos + right) - e.set_position(len(before_pos)) + @connecting(e, 'key-press-event') + def on_key_press(src, ev): + 'Handle special keys.' - if ev.keyval in [k.Down, k.Up] or \ - (ev.keyval in [k.n, k.p] and ev.state == gdk.CONTROL_MASK): - # Up/down selects prev/next row in the list, if possible, and scroll to - # center that row. - sel = t.get_selection() - mdl, itr = sel.get_selected() - assert mdl is l - dir = 1 if ev.keyval == k.Down or \ - (ev.keyval == k.n and ev.state == gdk.CONTROL_MASK) else -1 - if itr is not None: + def complete(): + '''Complete the entered text using currently highlighted list item and + advance focus to the next label (creating one if necessary).''' + # Complete. + mdl, itr = t.get_selection().get_selected() + assert mdl is l (pos,) = l.get_path(itr) - pos += dir - else: - pos = 0 if ev.keyval == k.Down else len(l) - 1 - if 0 <= pos < len(l): - sel.select_path(pos) - t.scroll_to_cell(pos, None, True, .5, .5) - return True - elif ev.keyval in [k.Tab]: - # Tab completes. - complete() - return True - elif ev.keyval in [k.Return]: - # If a new label is entered, add that to the list. If the label - # exists, behave as Tab. If nothing is entered, finish with this song - # and move on. - txt = get_mid() - if txt == '': - pass # TODO: move on to next song - elif txt not in labels: - labels.append(txt) - refresh() - elif txt in labels: + left, mid, right = get_pieces(True) + debug(repr(left), repr(mid), repr(right)) + if left != '' and not left[-1:].isspace(): left += ' ' + before_pos = left + self.matches[pos] + sep + if not right[:0].isspace(): before_pos += ' ' + e.set_text(before_pos + right) + e.set_position(len(before_pos)) + + def try_add(txt = None): + if txt is None: txt = get_mid() + if txt != '': + if txt in labels: + complete() + else: + labels.append(txt) + refresh() + + if ev.keyval in [k.Down, k.Up] or \ + (ev.keyval in [k.n, k.p] and ev.state == gdk.CONTROL_MASK): + # Up/down selects prev/next row in the list, if possible, and scroll to + # center that row. + sel = t.get_selection() + mdl, itr = sel.get_selected() + assert mdl is l + dir = 1 if ev.keyval == k.Down or \ + (ev.keyval == k.n and ev.state == gdk.CONTROL_MASK) else -1 + if itr is not None: + (pos,) = l.get_path(itr) + pos += dir + else: + pos = 0 if ev.keyval == k.Down else len(l) - 1 + if 0 <= pos < len(l): + sel.select_path(pos) + t.scroll_to_cell(pos, None, True, .5, .5) + return True + elif ev.keyval in [k.Tab]: + # Tab completes. complete() - return True + return True + elif ev.keyval in [k.Return]: + # If a new label is entered, add that to the list. If the label + # exists, behave as Tab. If nothing is entered, finish with this track + # and move on. + txt = get_mid() + if txt == '': + # Move on to next track. + dir,inc = ('prev',-1) if ev.state == gdk.SHIFT_MASK else ('next',1) + debug('moving to %s track' % dir) + nexte = es[(es.index(e) + inc) % len(es)] + nexte.grab_focus() + nexte.set_position(-1) + else: + try_add(txt) + return True + elif ev.keyval == k.semicolon: + # If a new label is entered, add that to the list. If the label + # exists, behave as Tab. + try_add() + return False - # The pop-up list box window. - tw = Window(WINDOW_POPUP) - tw.set_transient_for(w) - tw.set_default_size(100, 200) - tw.set_accept_focus(False) - l = ListStore(str) - t = TreeView(l) - t.set_headers_visible(False) - t.append_column(TreeViewColumn(None, CellRendererText(), markup = 0)) - s = ScrolledWindow() - s.add(t) - s.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) - tw.add(s) - refresh() - tw.show_all() + # The pop-up list box window. + tw = Window(WINDOW_POPUP) + tw.set_transient_for(w) + tw.set_default_size(100, 200) + tw.set_accept_focus(False) + l = ListStore(str) + t = TreeView(l) + t.set_headers_visible(False) + t.append_column(TreeViewColumn(None, CellRendererText(), markup = 0)) + s = ScrolledWindow() + s.add(t) + s.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) + tw.add(s) + refresh() + @connecting_after(e, 'focus-in-event') # TODO hack; too early on resizes + def show_popup(*args): + tw.show_all() + win = e.get_window() + _,_,ew,eh,_ = win.get_geometry() + ex,ey = win.get_origin() + if do_debug: + debug('size_request:', e.size_request()) + debug('alloc:', (e.get_allocation().x, e.get_allocation().y)) + debug('win.geom:', win.get_geometry()) + debug('win.orig:', win.get_origin()) + debug('win.pos:', win.get_position()) + tw.set_size_request(ew,-1) + tw.move(ex,ey+eh) + + row = rows.next() + caption = '%s - %s' % (trunc(track.get('TPE1','Unknown Artist')), + trunc(track.get('TIT2','Untitled'))) + tab.attach(Label(str = caption), 0, 1, row, row+1) + tab.attach(e, 1, 2, row, row+1) + es.append(e) + # Final steps. - h.pack_start(e) - @connecting_after(w, 'show') # initial, but doesn't work (too early) - @connecting_after(w, 'drag-end') # want to detect movement - @connecting_after(e, 'notify::cursor-position') - @connecting_after(e, 'focus-in-event') # hack to handle initial - def shown(*args): - ew,eh = e.size_request() - gdkwin = e.get_parent_window() - x,y = gdkwin.get_origin() - if do_debug: - a = e.get_allocation() - debug(ew, eh, a.x, a.y, a.width, a.height, gdkwin, (x,y), - gdkwin.get_position(), gdkwin.get_geometry()) - y += eh - # TODO: figure out how to position the pop-up window correctly. - tw.set_size_request(e.size_request()[0],-1) - tw.move(x,y) - w.move(0,0) + with file(expanduser('~/.quodlibet/playlists/New')) as f: + for line in f: + make_e(File(line.rstrip())) + w.set_position(WIN_POS_CENTER) + debug('size req:', tab.size_request()) + w.set_default_size(800,600) w.show_all() - es.append(e) main() if __name__ == '__main__': from os import listdir - from os.path import expanduser from urllib import quote, unquote labels = map(unquote, listdir(expanduser('~/.quodlibet/playlists/'))) - do_debug = True + do_debug = False mainwin() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |