diff options
| author | Tim Redfern <tim@eclectronics.org> | 2013-11-18 14:40:41 +0000 |
|---|---|---|
| committer | Tim Redfern <tim@eclectronics.org> | 2013-11-18 14:40:41 +0000 |
| commit | a36735b4585521218268da5fed2214ba239b4c2a (patch) | |
| tree | 77f951acb62d61c88b0b01d4bcb34367e44abc91 /lyricstimer/keyview.py | |
| parent | 6c5ba0ff0a4301b932c70aee6b4c87b2092d8bff (diff) | |
simple lyrics script
Diffstat (limited to 'lyricstimer/keyview.py')
| -rw-r--r-- | lyricstimer/keyview.py | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/lyricstimer/keyview.py b/lyricstimer/keyview.py new file mode 100644 index 0000000..ea4c3f3 --- /dev/null +++ b/lyricstimer/keyview.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from xml.sax.saxutils import escape, quoteattr +import gtk, gtk.glade +from time import time + +import pyxhook + +# Want to handle/show these in a nice emacsy way +MODIFIERS = ( + 'Control_L', + 'Control_R', + 'Alt_L', + 'Alt_R', + 'Super_L', + 'Mode_switch' +) + +CHORD_PREFIXES = ( + 'f1-', + 'f2-' +) + +SHIFT_KEYS = ( + 'Shift_L', + 'Shift_R', +) + +# Alter the appearance of some key events +KEY_MAP = { + 'F1':'f1-', + 'F2':'f2-', + 'F3':'f3', + 'F4':'f4', + 'F5':'f5', + 'F6':'f6', + 'F7':'f7', + 'F8':'f8', + 'F9':'f9', + 'F10':'f10', + 'F11':'f11', + 'F12':'f12', + 'Return':'↲', + 'Right': '→', + 'Left': '←', + 'Up': '↑', + 'Down': '↓', + 'Control_L':'Ctrl-', + 'Control_R':'Ctrl-', + 'Mode_switch':'Mod4-', + 'Alt_L':'Alt-', + 'Alt_R':'Alt-', + 'Shift_L':'⇪-', + 'Shift_R':'⇪-', + 'space': ' ', + 'parenleft': '(', + 'parenright': ')', + 'bracketleft': '[', + 'bracketright': ']', + 'braceleft': '{', + 'braceright': '}', + 'BackSpace': '⇤', + 'Delete': 'DEL', + 'Tab': '↹', + 'bar': '|', + 'minus': '-', + 'plus': '+', + 'asterisk': '*', + 'equal': '=', + 'less': '<', + 'greater': '>', + 'semicolon': ';', + 'colon': ':', + 'comma': ',', + 'apostrophe': "'", + 'quotedbl' : '"', + 'underscore' : '_', + 'numbersign' : '#', + 'percent' : '%', + 'exclam' : '!', + 'period' : '.', + 'slash' : '/', + 'backslash' : '\\', + 'question' : '?', + 'adiaeresis': 'ä', + 'odiaeresis': 'ö', + 'udiaeresis': 'ü', + 'ssharp': 'ß', + 'ampersand': '&', + 'section': '§', +} + +def get_hook_manager(): + hm = pyxhook.HookManager() + hm.HookKeyboard() + hm.HookMouse() + #hm.KeyDown = hm.printevent + #hm.KeyUp = self.hook_manager_event #hm.printevent + #hm.MouseAllButtonsDown = hm.printevent + #hm.MouseAllButtonsUp = hm.printevent + hm.start() + return hm + + +class GTKKeyView: + def __init__(self, hm): + + xml = gtk.glade.XML('keyview.libglade') + self.window = xml.get_widget('window1') + self.window.connect('destroy', self.quit) + self.key_strokes = xml.get_widget('label1') + self.key_strokes.set_alignment(0, 0) + self.menu = xml.get_widget('config-menu') + self.font_dialog = xml.get_widget('fontselectiondialog1') + self.font = 'Courier Bold 30' #None # text of font description from selection dialog + self.init_menu() + self.pressed_modifiers = {} # keep track of whats pushed + self.hm = hm + self.active = True + hm.KeyDown = self.hook_manager_down_event + hm.KeyUp = self.hook_manager_up_event + xml.signal_autoconnect(self) + self.max_lines = 3 + self.show_backspace = True + self.show_shift = False + self.keys = [] #stack of keys typed + self.last_key = 0 # keep track of time + + def init_menu(self): + self.font_item = gtk.MenuItem('set font...') + self.font_item.connect('activate', self.font_select) + + self.on_item = gtk.CheckMenuItem('listening') + self.on_item.set_active(True) + self.on_item.connect('activate', self.toggle_active) + + self.quit_item = gtk.MenuItem('quit') + self.quit_item.connect('activate', self.quit) + + for item in [self.font_item, self.on_item, self.quit_item]: + self.menu.append(item) + item.show() + + def font_select(self, widget): + response = self.font_dialog.run() + self.font_dialog.hide() + self.font = self.font_dialog.get_font_name() + cur_text = self.key_strokes.get_text() + self.update_text(cur_text, self.font) + + + def toggle_active(self, widget): + # active is state after click + self.active = widget.get_active() + + + def quit(self, widget): + self.hm.cancel() + gtk.main_quit() + + + def on_eventbox1_popup_menu(self, *args): + self.menu.show() + + def on_eventbox1_button_press_event(self, widget, event): + if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3: + self.menu.popup(None, None, None, event.button, event.get_time()) + + def update_text(self, text, font_desc=None): + """ + see + http://www.pygtk.org/docs/pygtk/class-gtklabel.html + and + http://www.pygtk.org/docs/pygtk/pango-markup-language.html + """ + if font_desc: + font_desc_text = 'font_desc=%s' % quoteattr(font_desc) + else: + font_desc_text = '' + pango_markup = """<span %s>%s</span>""" % (font_desc_text, escape(text)) + self.key_strokes.set_markup(pango_markup) + + def hook_manager_up_event(self, event): + if event.Key in self.pressed_modifiers: + del self.pressed_modifiers[event.Key] + + def hook_manager_down_event(self, event): + #hm.printevent(event) + if self.active: + e_key = event.Key + + # hack to deal with ctr modifiers in emacsy way + modifiers = [] + postfix = '' + for modifier in self.pressed_modifiers.keys(): + mod = KEY_MAP.get(modifier, modifier) + if self.keys and not self.keys[-1].text == mod: + modifiers.append(mod) + postfix = ' ' + + if e_key in MODIFIERS: + self.pressed_modifiers[e_key] = 1 + elif e_key == 'BackSpace' and not self.show_backspace: + if self.keys: + self.keys.pop() + elif e_key in SHIFT_KEYS: + if self.show_shift: + self.pressed_modifiers[e_key] = 1 + else: + txt = KEY_MAP.get(e_key, e_key) + + prefix = ''.join(modifiers) + txt = Text(txt, prefix, postfix) + + isseq = ( + len(self.keys) > 0 and + (self.keys[-1].is_chord_prefix + or + self.keys[-1].is_char and time() - self.last_key < 1) + ) + + if not (txt.is_char and isseq): + self.keys.append(Text("\n")) + + self.keys.append(txt) + + self.last_key = time() + + + # limit line lengths + self.keys = limit_text(self.keys, self.max_lines) + new_text = ''.join([repr(x) for x in self.keys]) + + self.update_text(new_text, self.font) + +def limit_text(text_list, max_lines): + r""" + >>> lines = [Text('\n')]*4 + >>> len(limit_text(lines, 2)) #only 1 newline is possible since we're limiting to two lines + 1 + >>> lines = [Text(x) for x in 'foo\nbar\nbaz'] + >>> len(limit_text(lines, 2)) #from bar to end + 7 + + """ + # limit line lengths + new_line_idx = [i for i,x in enumerate(text_list) if x.text == '\n'] + if len(new_line_idx) >= max_lines: + new_start = new_line_idx[-max_lines] + 1 + text_list = text_list[new_start:] + return text_list + +class Text(object): + """Simple class to hold text and pre/postfix for it + """ + def __init__(self, text, prefix='', postfix=''): + self.text = text + self.prefix = prefix + self.postfix = postfix + + def __repr__(self): + return '%s%s%s' %(self.prefix, self.text, self.postfix) + + @property + def is_char(self): + t = self.text + return len(t) == 1 and (t.isalnum() or t.isspace()) + @property + def is_chord_prefix(self): + return self.text in CHORD_PREFIXES + +def main(): + gtk.gdk.threads_init() + hm = get_hook_manager() + view = GTKKeyView(hm) + w = view.window + w.resize(360, 150) + w.set_keep_above(True) #ensure visibility + w.show() + + try: + gtk.main() + except KeyboardInterrupt, e: + # kill the hook manager thread + view.hm.cancel() + +def test(): + import doctest + doctest.testmod() + +if __name__ == '__main__': + main() + + + |
