[gedit-plugins] Implemented support for accelerators



commit c6ae7a42f845a603b2d4454e7de92864f43b3b1a
Author: Jesse van den Kieboom <jesse icecrew nl>
Date:   Sun Mar 21 22:53:16 2010 +0100

    Implemented support for accelerators

 plugins/commander/commander/commands/Makefile.am   |    1 +
 plugins/commander/commander/commands/__init__.py   |   70 +++++++++++-
 .../commander/commander/commands/accel_group.py    |  104 ++++++++++++++++++
 plugins/commander/commander/commands/method.py     |   13 ++-
 plugins/commander/commander/entry.py               |  111 +++++++++++++------
 5 files changed, 256 insertions(+), 43 deletions(-)
---
diff --git a/plugins/commander/commander/commands/Makefile.am b/plugins/commander/commander/commands/Makefile.am
index 00018e2..dd8830a 100644
--- a/plugins/commander/commander/commands/Makefile.am
+++ b/plugins/commander/commander/commands/Makefile.am
@@ -3,6 +3,7 @@
 plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/commander/commands
 
 plugin_PYTHON =	\
+	accel_group.py \
 	completion.py \
 	exceptions.py \
 	__init__.py \
diff --git a/plugins/commander/commander/commands/__init__.py b/plugins/commander/commander/commands/__init__.py
index cb7ee04..33e65df 100644
--- a/plugins/commander/commander/commands/__init__.py
+++ b/plugins/commander/commander/commands/__init__.py
@@ -13,7 +13,10 @@ import method
 import result
 import exceptions
 
-__all__ = ['is_commander_module', 'Commands']
+from accel_group import AccelGroup
+from accel_group import Accelerator
+
+__all__ = ['is_commander_module', 'Commands', 'Accelerator']
 
 def attrs(**kwargs):
 	def generator(f):
@@ -34,6 +37,9 @@ def autocomplete(d={}, **kwargs):
 
 	return attrs(autocomplete=ret)
 
+def accelerator(*args, **kwargs):
+	return attrs(accelerator=Accelerator(args, kwargs))
+
 def is_commander_module(mod):
 	if type(mod) == types.ModuleType:
 		return mod and ('__commander_module__' in mod.__dict__)
@@ -107,6 +113,7 @@ class Commands(Singleton):
 		self._modules = None
 		self._dirs = []
 		self._monitors = []
+		self._accel_group = None
 		
 		self._timeouts = {}
 		
@@ -126,7 +133,35 @@ class Commands(Singleton):
 			glib.source_remove(self._timeouts[k])
 		
 		self._timeouts = {}
-	
+
+	def accelerator_activated(self, accel, mod, state, entry):
+		self.run(state, mod.execute('', [], entry, 0, accel.arguments))
+
+	def scan_accelerators(self, modules=None):
+		if modules == None:
+			self._accel_group = AccelGroup()
+			modules = self.modules()
+
+		recurse_mods = []
+
+		for mod in modules:
+			if type(mod) == types.ModuleType:
+				recurse_mods.append(mod)
+			else:
+				accel = mod.accelerator()
+
+				if accel != None:
+					self._accel_group.add(accel, self.accelerator_activated, mod)
+
+		for mod in recurse_mods:
+			self.scan_accelerators(mod.commands())
+
+	def accelerator_group(self):
+		if not self._accel_group:
+			self.scan_accelerators()
+
+		return self._accel_group
+
 	def modules(self):
 		self.ensure()
 		return list(self._modules)
@@ -303,11 +338,31 @@ class Commands(Singleton):
 		
 		return mod
 
-	def remove_module_root(self, mod):
+	def remove_module_accelerators(self, modules):
+		recurse_mods = []
+
+		for mod in modules:
+			if type(mod) == types.ModuleType:
+				recurse_mods.append(mod)
+			else:
+				accel = mod.accelerator()
+
+				if accel != None:
+					self._accel_group.remove(accel)
+
+		for mod in recurse_mods:
+			self.remove_module_accelerators(mod.commands())
+
+	def remove_module(self, mod):
+		# Remove roots
 		for r in mod.roots():
 			if r in self._modules:
 				self._modules.remove(r)
-	
+
+		# Remove accelerators
+		if self._accel_group:
+			self.remove_module_accelerators([mod])
+
 	def reload_module(self, mod):
 		if isinstance(mod, basestring):
 			mod = self.resolve_module(mod)
@@ -316,7 +371,7 @@ class Commands(Singleton):
 			return
 
 		# Remove roots
-		self.remove_module_root(mod)
+		self.remove_module(mod)
 
 		# Now, try to reload the module
 		try:
@@ -332,13 +387,16 @@ class Commands(Singleton):
 		for r in mod.roots():
 			bisect.insort(self._modules, r)
 
+		if self._accel_group:
+			self.scan_accelerators([mod])
+
 	def on_timeout_delete(self, path, mod):
 		if not path in self._timeouts:
 			return False
 		
 		# Remove the module
 		mod.unload()
-		self.remove_module_root(mod)
+		self.remove_module(mod)
 		self._modules.remove(mod)
 
 		return False
diff --git a/plugins/commander/commander/commands/accel_group.py b/plugins/commander/commander/commands/accel_group.py
new file mode 100644
index 0000000..934f9dc
--- /dev/null
+++ b/plugins/commander/commander/commands/accel_group.py
@@ -0,0 +1,104 @@
+import gtk
+
+class Accelerator:
+	def __init__(self, accelerators, arguments={}):
+		if not hasattr(accelerators, '__iter__'):
+			accelerators = [accelerators]
+
+		self.accelerators = accelerators
+		self.arguments = arguments
+
+class AccelCallback:
+	def __init__(self, accel, callback, data):
+		self.accelerator = accel
+		self.callback = callback
+		self.data = data
+
+	def activate(self, state, entry):
+		self.callback(self.accelerator, self.data, state, entry)
+
+class AccelGroup:
+	def __init__(self, parent=None, name='', accelerators={}):
+		self.accelerators = dict(accelerators)
+		self.parent = parent
+		self.name = name
+
+	def add(self, accel, callback, data=None):
+		num = len(accel.accelerators)
+		mapping = self.accelerators
+
+		for i in range(num):
+			parsed = gtk.accelerator_parse(accel.accelerators[i])
+
+			if not gtk.accelerator_valid(*parsed):
+				return
+
+			named = gtk.accelerator_name(*parsed)
+			inmap = named in mapping
+
+			if i == num - 1 and inmap:
+				# Last one cannot be in the map
+				return
+			elif inmap and isinstance(mapping[named], AccelCallback):
+				# It's already mapped...
+				return
+			else:
+				if not inmap:
+					mapping[named] = {}
+
+				if i == num - 1:
+					mapping[named] = AccelCallback(accel, callback, data)
+
+				mapping = mapping[named]
+
+	def remove_real(self, accelerators, accels):
+		if not accels:
+			return
+
+		parsed = gtk.accelerator_parse(accels[0])
+
+		if not gtk.accelerator_valid(*parsed):
+			return
+
+		named = gtk.accelerator_name(*parsed)
+
+		if not named in accelerators:
+			return
+
+		if len(accels) == 1:
+			del accelerators[named]
+		else:
+			self.remove_real(accelerators[named], accels[1:])
+
+			if not accelerators[named]:
+				del accelerators[named]
+
+	def remove(self, accel):
+		self.remove_real(self.accelerators, accel.accelerators)
+
+	def activate(self, key, mod):
+		named = gtk.accelerator_name(key, mod)
+
+		if not named in self.accelerators:
+			return None
+
+		accel = self.accelerators[named]
+
+		if isinstance(accel, AccelCallback):
+			return accel
+		else:
+			return AccelGroup(self, named, accel)
+
+	def full_name(self):
+		name = ''
+
+		if self.parent:
+			name = self.parent.full_name()
+
+		if self.name:
+			if name:
+				name += ', '
+
+			name += self.name
+
+		return name
diff --git a/plugins/commander/commander/commands/method.py b/plugins/commander/commander/commands/method.py
index cc8ef58..dffab2e 100644
--- a/plugins/commander/commander/commands/method.py
+++ b/plugins/commander/commander/commands/method.py
@@ -19,7 +19,13 @@ class Method:
 			return getattr(self.method, 'autocomplete')
 
 		return None
-	
+
+	def accelerator(self):
+		if hasattr(self.method, 'accelerator'):
+			return getattr(self.method, 'accelerator')
+
+		return None
+
 	def args(self):
 		fp = self.func_props()
 		
@@ -52,7 +58,7 @@ class Method:
 	def oneline_doc(self):
 		return self.doc().split("\n")[0]
 	
-	def execute(self, argstr, words, entry, modifier):
+	def execute(self, argstr, words, entry, modifier, kk = {}):
 		fp = self.func_props()
 		
 		kwargs = {'argstr': argstr, 'args': words, 'entry': entry, 'view': entry.view(), 'modifier': modifier, 'window': entry.view().get_toplevel()}
@@ -85,6 +91,9 @@ class Method:
 		if not fp.keywords:
 			kwargs = {}
 
+		for k in kk:
+			kwargs[k] = kk[k]
+
 		return self.method(*args, **kwargs)
 
 	def __cmp__(self, other):
diff --git a/plugins/commander/commander/entry.py b/plugins/commander/commander/entry.py
index 928d38e..7c01931 100644
--- a/plugins/commander/commander/entry.py
+++ b/plugins/commander/commander/entry.py
@@ -11,6 +11,7 @@ import commands.completion
 import commands.module
 import commands.method
 import commands.exceptions
+import commands.accel_group
 
 import commander.utils as utils
 
@@ -58,6 +59,8 @@ class Entry(gtk.EventBox):
 		
 		self._history = History(os.path.expanduser('~/.gnome2/gedit/commander/history'))
 		self._prompt = None
+
+		self._accel_group = None
 		
 		hbox.pack_start(self._prompt_label, False, False, 0)
 		hbox.pack_start(self._entry, True, True, 0)
@@ -71,7 +74,7 @@ class Entry(gtk.EventBox):
 		
 		self.connect('destroy', self.on_destroy)
 		
-		self._history_prefix = None		
+		self._history_prefix = None
 		self._suspended = None
 		self._handlers = [
 			[0, gtk.keysyms.Up, self.on_history_move, -1],
@@ -147,23 +150,29 @@ class Entry(gtk.EventBox):
 	def on_entry_focus_out(self, widget, evnt):
 		if self._entry.flags() & gtk.SENSITIVE:
 			self.destroy()
-	
+
 	def on_entry_key_press(self, widget, evnt):
 		state = evnt.state & gtk.accelerator_get_default_mod_mask()
 		text = self._entry.get_text()
 		
-		if evnt.keyval == gtk.keysyms.Escape and self._info_window:
-			if self._suspended:
-				self._suspended.resume()
-
+		if evnt.keyval == gtk.keysyms.Escape:
 			if self._info_window:
-				self._info_window.destroy()
+				if self._suspended:
+					self._suspended.resume()
 
-			self._entry.set_sensitive(True)
-			return True
+				if self._info_window:
+					self._info_window.destroy()
 
-		if evnt.keyval == gtk.keysyms.Escape:
-			if text:
+				self._entry.set_sensitive(True)
+			elif self._accel_group:
+				self._accel_group = self._accel_group.parent
+
+				if not self._accel_group or not self._accel_group.parent:
+					self._entry.set_editable(True)
+					self._accel_group = None
+
+				self.prompt()
+			elif text:
 				self._entry.set_text('')
 			elif self._command_state:
 				self._command_state.clear()
@@ -174,6 +183,30 @@ class Entry(gtk.EventBox):
 
 			return True
 
+		if state or self._accel_group:
+			# Check if it should be handled by the accel group
+			group = self._accel_group
+
+			if not self._accel_group:
+				group = commands.Commands().accelerator_group()
+
+			accel = group.activate(evnt.keyval, state)
+
+			if isinstance(accel, commands.accel_group.AccelGroup):
+				self._accel_group = accel
+				self._entry.set_text('')
+				self._entry.set_editable(False)
+				self.prompt()
+
+				return True
+			elif isinstance(accel, commands.accel_group.AccelCallback):
+				self._entry.set_editable(True)
+				self.run_command(lambda: accel.activate(self._command_state, self))
+				return True
+
+		if not self._entry.get_editable():
+			return True
+
 		for handler in self._handlers:
 			if (handler[0] == None or handler[0] == state) and evnt.keyval == handler[1] and handler[2](handler[3], state):
 				return True
@@ -211,13 +244,16 @@ class Entry(gtk.EventBox):
 	def prompt(self, pr=''):
 		self._prompt = pr
 
+		if self._accel_group != None:
+			pr = '<i>%s</i>' % (saxutils.escape(self._accel_group.full_name()),)
+
 		if not pr:
 			pr = ''
 		else:
 			pr = ' ' + pr
-		
+
 		self._prompt_label.set_markup('<b>&gt;&gt;&gt;</b>%s' % pr)
-	
+
 	def make_info(self):
 		if self._info_window == None:
 			self._info_window = Info(self)
@@ -256,7 +292,7 @@ class Entry(gtk.EventBox):
 		if self._info_window and self._info_window.empty():
 			self._info_window.destroy()
 			self._entry.grab_focus()
-			self._entry.set_sensitive(True)			
+			self._entry.set_sensitive(True)
 	
 	def _show_wait_cancel(self):
 		self._cancel_button = self.info_add_action(gtk.STOCK_STOP, self.on_wait_cancel)
@@ -298,39 +334,24 @@ class Entry(gtk.EventBox):
 		self.hide()
 		gtk.EventBox.destroy(self)
 
-	def on_execute(self, dummy, modifier):
-		if self._info_window and not self._suspended:
-			self._info_window.destroy()
-
-		text = self._entry.get_text().strip()
-		words = list(self._re_complete.finditer(text))
-		wordsstr = []
-		
-		for word in words:
-			spec = self._complete_word_match(word)
-			wordsstr.append(spec[0])
-
-		if not wordsstr and not self._command_state:
-			self._entry.set_text('')
-			return
-		
+	def run_command(self, cb):
 		self._suspended = None
 
 		try:
-			ret = commands.Commands().execute(self._command_state, text, words, wordsstr, self, modifier)
+			ret = cb()
 		except Exception, e:
 			self.command_history_done()
 			self._command_state.clear()
-			
+
 			self.prompt()
-			
+
 			# Show error in info
 			self.info_show('<b><span color="#f66">Error:</span></b> ' + saxutils.escape(str(e)), True)
 
 			if not isinstance(e, commands.exceptions.Execute):
 				self.info_show(traceback.format_exc(), False)
 
-			return True
+			return None
 
 		if ret == commands.result.Result.SUSPEND:
 			# Wait for it...
@@ -342,7 +363,7 @@ class Entry(gtk.EventBox):
 		else:
 			self.command_history_done()
 			self.prompt('')
-			
+
 			if ret == commands.result.Result.PROMPT:
 				self.prompt(ret.prompt)
 			elif (ret == None or ret == commands.result.HIDE) and not self._prompt and (not self._info_window or self._info_window.empty()):
@@ -352,6 +373,26 @@ class Entry(gtk.EventBox):
 			else:
 				self._entry.grab_focus()
 
+		return ret
+
+	def on_execute(self, dummy, modifier):
+		if self._info_window and not self._suspended:
+			self._info_window.destroy()
+
+		text = self._entry.get_text().strip()
+		words = list(self._re_complete.finditer(text))
+		wordsstr = []
+		
+		for word in words:
+			spec = self._complete_word_match(word)
+			wordsstr.append(spec[0])
+
+		if not wordsstr and not self._command_state:
+			self._entry.set_text('')
+			return
+
+		self.run_command(lambda: commands.Commands().execute(self._command_state, text, words, wordsstr, self, modifier))
+
 		return True
 	
 	def on_complete(self, dummy, modifier):



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]