import gobject import gtk import gedit class KeySequenceManager(object): def __init__(self, widget, status_bar): self.status_bar = status_bar widget.connect("key-press-event", self._key_press_event) ## format: tree of "maps" (dictionaries), keyed by ## accelerators (a tuple (keyval, modifiers)) ending with a ## non-dictionary item consisting of a tuple (callback, ## user_data) self.registered_sequences = dict() ## a list of (accelerator, map) (where map is a dict) self.key_state = [] ## an object whose id() is the current status bar context message self.status_context_obj = None self.status_timeout_id = None def bind(self, desc, callback, *user_data): ## first find the correct "map" map = self.registered_sequences for key in desc.split(' ')[:-1]: accel = gtk.accelerator_parse(key) try: map = map[accel] except KeyError: entry = dict() map[accel] = entry map = entry ## now add the "leaf", i.e. the user accelerator itself key = desc.split(' ')[-1] accel = gtk.accelerator_parse(key) if accel in map: raise ValueError("key sequence '%s' already bound" % desc) map[accel] = (callback, user_data) def _update_status(self): if self.status_context_obj is not None: self.status_bar.pop(id(self.status_context_obj)) if self.key_state: self.status_context_obj = object() status_message = ' '.join(gtk.accelerator_name(*accel) for (accel, map) in self.key_state) self.status_bar.push(id(self.status_context_obj), status_message) def _status_timeout(self): self._status_timeout_id = None self._update_status() def _key_press_event(self, widget, event): if event.keyval >= 0xf000: return False if (event.keyval == gtk.keysyms.g and (event.state & gtk.accelerator_get_default_mod_mask()) == gtk.gdk.CONTROL_MASK): ## Quit! self.key_state = [] self._update_status() if self.status_timeout_id is not None: gobject.source_remove(self.status_timeout_id) self.status_timeout_id = None return True accel = (event.keyval, event.state & gtk.accelerator_get_default_mod_mask()) if self.key_state: prev_accel, prev_map = self.key_state[-1] else: prev_accel, prev_map = None, self.registered_sequences try: item = prev_map[accel] except KeyError: self.key_state = [] self._update_status() if self.status_timeout_id is not None: gobject.source_remove(self.status_timeout_id) self.status_timeout_id = None return False if isinstance(item, dict): ## non-leaf item, key sequence isn't complete self.key_state.append((accel, item)) else: assert isinstance(item, tuple) callback, user_data = item callback(*user_data) self.key_state = [] ## Now update the status bar if self.status_timeout_id is None and len(self.key_state) == 1: self.status_timeout_id = gobject.timeout_add(1000, self._status_timeout) else: self._update_status() ## returns True if the accelerator was handled by callback. return True class KeySequencePlugin(gedit.Plugin): def __init__(self): gedit.Plugin.__init__(self) def activate(self, window): window.connect("tab-added", self._tab_added) def _tab_added(self, window, tab): key_manager = KeySequenceManager(tab.get_view(), window.get_statusbar()) def callback(s): print s key_manager.bind("x s", callback, "hello") key_manager.bind("x 4 d", callback, "world") def deactivate(self, window): pass def update_ui(self, window): pass #def create_configure_dialog(self): # pass