[Assorted-commits] SF.net SVN: assorted:[1115] music-labeler/trunk/src/ml.py
Brought to you by:
yangzhang
From: <yan...@us...> - 2008-12-31 01:08:44
|
Revision: 1115 http://assorted.svn.sourceforge.net/assorted/?rev=1115&view=rev Author: yangzhang Date: 2008-12-31 01:08:35 +0000 (Wed, 31 Dec 2008) Log Message: ----------- new interaction model: single text box for all labels, delimited by separator char, a la delicious/google bookmarks Modified Paths: -------------- music-labeler/trunk/src/ml.py Modified: music-labeler/trunk/src/ml.py =================================================================== --- music-labeler/trunk/src/ml.py 2008-12-30 10:32:13 UTC (rev 1114) +++ music-labeler/trunk/src/ml.py 2008-12-31 01:08:35 UTC (rev 1115) @@ -4,18 +4,26 @@ from cgi import escape import gtk.keysyms as k, gtk.gdk as gdk +class struct(object): pass + +def debug(*args): + if do_debug: print ' '.join(map(str,args)) + def connecting(widget, signal): def wrapper(handler): widget.connect(signal, handler) return handler return wrapper -#class CompletionEntry(Entry): -# def __init__(self, *args): -# Entry.__init__(self, *args) -# def asdf +def connecting_after(widget, signal): + def wrapper(handler): + widget.connect_after(signal, handler) + return handler + return wrapper def mainwin(): + sep = ';' + w = Window() w.set_title('Music Labeler') w.connect('delete-event', lambda *args: False) @@ -26,140 +34,154 @@ es = [] - def addlabel(): - 'Add a label field' + def refresh(): + 'Called when the list is changed.' + labels.sort(key = str.lower) + on_change() - def refresh(): - 'Called when the list is changed.' - labels.sort(key = str.lower) - on_change() + e = Entry() + self = struct() - e = Entry() + 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.' - print '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): - print 'focus' - #t.destroy() + @connecting(e, 'focus-out-event') + def on_focus_out(*args): + 'Destroy the pop-up completion window.' + debug('focus-out') - @connecting(e, 'focus-in-event') - def on_focus_in(*args): - print 'focus-in' + @connecting(e, 'focus') + def on_focus(*args): + debug('focus') - @connecting(e, 'changed') - def on_change(*args): - 'Filter and highlight the list based on entered text.' - pat = e.get_text().lower() - # TODO: make the matching match up with the highlighting (use - # word-boundaries) - 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) + @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) - 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 + @connecting(e, 'key-press-event') + def on_key_press(src, ev): + 'Handle special keys.' + + 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)) + + 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) - e.set_text(labels[pos]) - e.set_position(-1) - # Advance. - fieldno = es.index(e) - nexte = addlabel() if fieldno == len(es) - 1 else es[fieldno + 1] - nexte.grab_focus() - - 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. + 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: 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 = e.get_text() - if txt == '': - pass # TODO: move on to next song - elif txt not in labels: - labels.append(txt) - refresh() - elif txt in labels: - complete() - return True + return True - # 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() - ew,eh = e.size_request() - #tw.move() - 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() + ew,eh = e.size_request() + # TODO: figure out how to position the pop-up window correctly. + #tw.move() + tw.show_all() - # Final steps. - h.pack_start(e) - w.show_all() - es.append(e) - return e - - addlabel() + # Final steps. + h.pack_start(e) + w.show_all() + es.append(e) main() if __name__ == '__main__': @@ -167,4 +189,5 @@ from os.path import expanduser from urllib import quote, unquote labels = map(unquote, listdir(expanduser('~/.quodlibet/playlists/'))) + do_debug = False mainwin() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |