[gedit] Added language support for external tools



commit fe308f6423662e64178b7f9e755af8697ddb9207
Author: Jesse van den Kieboom <jesse icecrew nl>
Date:   Fri May 22 03:02:19 2009 +0200

    Added language support for external tools
    
    Each tool can now be assigned any number of languages. Only tools that apply to the
    current documents language will be shown in the menu.
---
 plugins/externaltools/tools/__init__.py |   17 ++-
 plugins/externaltools/tools/library.py  |   39 +++-
 plugins/externaltools/tools/manager.py  |  390 ++++++++++++++++++++++++++++---
 plugins/externaltools/tools/tools.ui    |   54 ++++-
 4 files changed, 448 insertions(+), 52 deletions(-)

diff --git a/plugins/externaltools/tools/__init__.py b/plugins/externaltools/tools/__init__.py
index c722ced..0560b05 100644
--- a/plugins/externaltools/tools/__init__.py
+++ b/plugins/externaltools/tools/__init__.py
@@ -89,6 +89,18 @@ class ToolMenu(object):
         self._insert_directory(self._library.tree, self._menupath)
         self.filter(self._window.get_active_document())
 
+    def filter_language(self, language, item):
+        if not item.languages:
+            return True
+        
+        if not language and 'plain' in item.languages:
+            return True
+        
+        if language and (language.get_id() in item.languages):
+            return True
+        else:
+            return False
+
     def filter(self, document):
         if document is None:
             return
@@ -103,11 +115,14 @@ class ToolMenu(object):
             'titled': titled,
             'untitled': not titled,
         }
+        
+        language = document.get_language()
 
         for action in self._action_group.list_actions():
             item = action.get_data(self.ACTION_ITEM_DATA_KEY)
+
             if item is not None:
-                action.set_sensitive(states[item.applicability])
+                action.set_visible(states[item.applicability] and self.filter_language(language, item))
 
 class ExternalToolsWindowHelper(object):
     def __init__(self, plugin, window):
diff --git a/plugins/externaltools/tools/library.py b/plugins/externaltools/tools/library.py
index cf14e87..e42e58d 100644
--- a/plugins/externaltools/tools/library.py
+++ b/plugins/externaltools/tools/library.py
@@ -205,8 +205,26 @@ class Tool(object):
         self.filename = filename
         self.changed = False
         self._properties = dict()
+        self._transform = {
+            'Languages': [self._to_list, self._from_list]
+        }
         self._load()
 
+    def _to_list(self, value):
+        if value.strip() == '':
+            return []
+        else:
+            return map(lambda x: x.strip(), value.split(','))
+    
+    def _from_list(self, value):
+        return ','.join(value)
+
+    def _parse_value(self, key, value):
+        if key in self._transform:
+            return self._transform[key][0](value)
+        else:
+            return value
+
     def _load(self):
         if self.filename is None:
             return
@@ -230,18 +248,18 @@ class Tool(object):
                 (key, value) = [i.strip() for i in line[2:].split('=', 1)]
                 m = self.RE_KEY.match(key)
                 if m.group(3) is None:
-                    if m.group(0) not in self._properties:
-                        self._properties[m.group(1)] = value
+                    self._properties[m.group(1)] = self._parse_value(m.group(1), value)
                 elif lang is not None and lang.startswith(m.group(3)):
-                    self._properties[m.group(1)] = value
+                    self._properties[m.group(1)] = self._parse_value(m.group(1), value)
             except ValueError:
                 break
         fp.close()
         self.changed = False
-
+        
     def _set_property_if_changed(self, key, value):
         if value != self._properties.get(key):
             self._properties[key] = value
+
             self.changed = True
 
     def is_global(self):
@@ -321,6 +339,14 @@ class Tool(object):
     def set_save_files(self, value):
         self._set_property_if_changed('Save-files', value)
     save_files = property(get_save_files, set_save_files)
+    
+    def get_languages(self):
+        languages = self._properties.get('Languages')
+        if languages: return languages
+        return []
+    def set_languages(self, value):
+        self._set_property_if_changed('Languages', value)
+    languages = property(get_languages, set_languages)
 
     # There is no property for this one because this function is quite
     # expensive to perform
@@ -357,12 +383,15 @@ class Tool(object):
     def _dump_properties(self):
         lines = ['# [Gedit Tool]']
         for item in self._properties.iteritems():
-            if item[1] is not None:
+            if item[0] in self._transform:
+                lines.append('# %s=%s' % (item[0], self._transform[item[0]][1](item[1])))
+            elif item[1] is not None:
                 lines.append('# %s=%s' % item)
         return '\n'.join(lines) + '\n'
 
     def save_with_script(self, script):
         filename = self.library.get_full_path(self.filename, 'w')
+        
         fp = open(filename, 'w', 1)
 
         # Make sure to first print header (shebang, modeline), then
diff --git a/plugins/externaltools/tools/manager.py b/plugins/externaltools/tools/manager.py
index a5752ec..d81340f 100644
--- a/plugins/externaltools/tools/manager.py
+++ b/plugins/externaltools/tools/manager.py
@@ -26,6 +26,229 @@ from library import *
 from functions import *
 import hashlib
 from xml.sax import saxutils
+import gobject
+
+class LanguagesPopup(gtk.Window):
+    COLUMN_NAME = 0
+    COLUMN_ID = 1
+    COLUMN_ENABLED = 2
+
+    def __init__(self, languages):
+        gtk.Window.__init__(self, gtk.WINDOW_POPUP)
+        
+        self.set_default_size(200, 200)
+        self.props.can_focus = True
+
+        self.build()
+        self.init_languages(languages)
+
+        self.show()
+        self.map()
+        
+        self.grab_add()
+        
+        gtk.gdk.keyboard_grab(self.window, False, 0L)
+        gtk.gdk.pointer_grab(self.window, False, gtk.gdk.BUTTON_PRESS_MASK |
+                                                 gtk.gdk.BUTTON_RELEASE_MASK |
+                                                 gtk.gdk.POINTER_MOTION_MASK |
+                                                 gtk.gdk.ENTER_NOTIFY_MASK |
+                                                 gtk.gdk.LEAVE_NOTIFY_MASK |
+                                                 gtk.gdk.PROXIMITY_IN_MASK |
+                                                 gtk.gdk.PROXIMITY_OUT_MASK, None, None, 0L)
+
+        self.view.get_selection().select_path((0,))
+
+    def build(self):
+        self.model = gtk.ListStore(str, str, bool)
+        
+        self.sw = gtk.ScrolledWindow()
+        self.sw.show()
+        
+        self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        self.sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
+        
+        self.view = gtk.TreeView(self.model)
+        self.view.show()
+        
+        self.view.set_headers_visible(False)
+        
+        column = gtk.TreeViewColumn()
+        
+        renderer = gtk.CellRendererToggle()
+        column.pack_start(renderer, False)
+        column.set_attributes(renderer, active=self.COLUMN_ENABLED)
+        
+        renderer.connect('toggled', self.on_language_toggled)
+        
+        renderer = gtk.CellRendererText()
+        column.pack_start(renderer, True)
+        column.set_attributes(renderer, text=self.COLUMN_NAME)
+        
+        self.view.append_column(column)
+        self.view.set_row_separator_func(self.on_separator)
+        
+        self.sw.add(self.view)
+        
+        self.add(self.sw)
+    
+    def enabled_languages(self, model, path, piter, ret):
+        enabled = model.get_value(piter, self.COLUMN_ENABLED)
+        
+        if path == (0,) and enabled:
+            return True
+
+        if enabled:
+            ret.append(model.get_value(piter, self.COLUMN_ID))
+
+        return False
+    
+    def languages(self):
+        ret = []
+        
+        self.model.foreach(self.enabled_languages, ret)
+        return ret
+    
+    def on_separator(self, model, piter):
+        val = model.get_value(piter, self.COLUMN_NAME)
+        return val == '-'
+    
+    def init_languages(self, languages):
+        manager = gsv.LanguageManager()
+        langs = gedit.language_manager_list_languages_sorted(manager, True)
+        
+        self.model.append([_('All languages'), None, not languages])
+        self.model.append(['-', None, False])
+        self.model.append([_('Plain Text'), 'plain', 'plain' in languages])
+        self.model.append(['-', None, False])
+        
+        for lang in langs:
+            self.model.append([lang.get_name(), lang.get_id(), lang.get_id() in languages])
+
+    def correct_all(self, model, path, piter, enabled):
+        if path == (0,):
+            return False
+        
+        model.set_value(piter, self.COLUMN_ENABLED, enabled)
+
+    def on_language_toggled(self, renderer, path):
+        piter = self.model.get_iter(path)
+        
+        enabled = self.model.get_value(piter, self.COLUMN_ENABLED)
+        self.model.set_value(piter, self.COLUMN_ENABLED, not enabled)
+        
+        if path == '0':
+            self.model.foreach(self.correct_all, False)
+        else:
+            self.model.set_value(self.model.get_iter_first(), self.COLUMN_ENABLED, False)
+
+    def do_key_press_event(self, event):
+        if event.keyval == gtk.keysyms.Escape:
+            self.destroy()
+            return True
+        else:
+            event.window = self.view.get_bin_window()
+            return self.view.event(event)
+    
+    def do_key_release_event(self, event):
+        event.window = self.view.get_bin_window()
+        return self.view.event(event)
+    
+    def in_window(self, event, window=None):
+        if not window:
+            window = self.window
+
+        geometry = window.get_geometry()
+        origin = window.get_origin()
+        
+        return event.x_root >= origin[0] and \
+               event.x_root <= origin[0] + geometry[2] and \
+               event.y_root >= origin[1] and \
+               event.y_root <= origin[1] + geometry[3]
+    
+    def do_destroy(self):
+        gtk.gdk.keyboard_ungrab(0L)
+        gtk.gdk.pointer_ungrab(0L)
+        
+        return gtk.Window.do_destroy(self)
+    
+    def setup_event(self, event, window):
+        fr = event.window.get_origin()
+        to = window.get_origin()
+        
+        event.window = window
+        event.x += fr[0] - to[0]
+        event.y += fr[1] - to[1]
+    
+    def resolve_widgets(self, root):
+        res = [root]
+        
+        if isinstance(root, gtk.Container):
+            root.forall(lambda x, y: res.extend(self.resolve_widgets(x)), None)
+        
+        return res
+    
+    def resolve_windows(self, window):
+        if not window:
+            return []
+
+        res = [window]
+        res.extend(window.get_children())
+        
+        return res
+    
+    def propagate_mouse_event(self, event):
+        allwidgets = self.resolve_widgets(self.get_child())
+        allwidgets.reverse()
+        
+        orig = [event.x, event.y]
+
+        for widget in allwidgets:
+            windows = self.resolve_windows(widget.window)
+            windows.reverse()
+            
+            for window in windows:
+                if not (window.get_events() & event.type):
+                    continue
+
+                if self.in_window(event, window):                    
+                    self.setup_event(event, window)
+
+                    if widget.event(event):
+                        return True
+        
+        return False
+    
+    def do_button_press_event(self, event):
+        if not self.in_window(event):
+            self.destroy()
+        else:
+            return self.propagate_mouse_event(event)
+
+    def do_button_release_event(self, event):
+        if not self.in_window(event):
+            self.destroy()
+        else:
+            return self.propagate_mouse_event(event)
+
+    def do_scroll_event(self, event):
+        return self.propagate_mouse_event(event)
+    
+    def do_motion_notify_event(self, event):
+        return self.propagate_mouse_event(event)
+    
+    def do_enter_notify_event(self, event):
+        return self.propagate_mouse_event(event)
+
+    def do_leave_notify_event(self, event):
+        return self.propagate_mouse_event(event)
+    
+    def do_proximity_in_event(self, event):
+        return self.propagate_mouse_event(event)
+    
+    def do_proximity_out_event(self, event):
+        return self.propagate_mouse_event(event)
+
+gobject.type_register(LanguagesPopup)
 
 class Manager:
     LABEL_COLUMN = 0 # For Combo and Tree
@@ -47,7 +270,8 @@ class Manager:
             'on_tool_manager_dialog_focus_out': self.on_tool_manager_dialog_focus_out,
             'on_accelerator_key_press'        : self.on_accelerator_key_press,
             'on_accelerator_focus_in'         : self.on_accelerator_focus_in,
-            'on_accelerator_focus_out'        : self.on_accelerator_focus_out
+            'on_accelerator_focus_out'        : self.on_accelerator_focus_out,
+            'on_languages_button_clicked'     : self.on_languages_button_clicked
         }
 
         # Load the "main-window" widget from the ui file.
@@ -61,11 +285,11 @@ class Manager:
         
         self.view = self.ui.get_object('view')
         
-        for name in ['input', 'output', 'applicability', 'save-files']:
-            self.__init_combobox(name)
-
         self.__init_tools_model()
         self.__init_tools_view()
+
+        for name in ['input', 'output', 'applicability', 'save-files']:
+            self.__init_combobox(name)
         
         self.do_update()
     
@@ -75,6 +299,27 @@ class Manager:
             self.dialog.show()
         else:
             self.dialog.present()
+
+    def add_accelerator(self, item):
+        if not item.shortcut:
+            return
+        
+        if item.shortcut in self.accelerators:
+            self.accelerators[item.shortcut].append(item)
+        else:
+            self.accelerators[item.shortcut] = [item]
+
+    def remove_accelerator(self, item, shortcut=None):
+        if not shortcut:
+            shortcut = item.shortcut
+            
+        if not item.shortcut in self.accelerators:
+            return
+        
+        self.accelerators[item.shortcut].remove(item)
+        
+        if not self.accelerators[item.shortcut]:
+            del self.accelerators[item.shortcut]
         
     def __init_tools_model(self):
         self.tools = ToolLibrary()
@@ -87,8 +332,7 @@ class Manager:
 
         for item in self.tools.tree.tools:
             self.model.append([item.name, item])
-            if item.shortcut:
-                self.accelerators[item.shortcut] = item
+            self.add_accelerator(item)
 
     def __init_tools_view(self):
         # Tools column
@@ -161,12 +405,34 @@ class Manager:
         else:
             self.current_node.save()
 
+        self.update_remove_revert()
+
     def clear_fields(self):
         self['accelerator'].set_text('')
         self['commands'].get_buffer().set_text('')
 
         for nm in ('input', 'output', 'applicability', 'save-files'):
             self[nm].set_active(0)
+        
+        self['languages_label'].set_text(_('All Languages'))
+    
+    def fill_languages_button(self):
+        if not self.current_node or not self.current_node.languages:
+            self['languages_label'].set_text(_('All Languages'))
+        else:
+            manager = gsv.LanguageManager()
+            langs = []
+            
+            for lang in self.current_node.languages:
+                if lang == 'plain':
+                    langs.append(_('Plain Text'))
+                else:
+                    l = manager.get_language(lang)
+                    
+                    if l:
+                        langs.append(l.get_name())
+            
+            self['languages_label'].set_text(', '.join(langs))
     
     def fill_fields(self):
         node = self.current_node
@@ -194,10 +460,12 @@ class Manager:
                                     default(node.__getattribute__(nm.replace('-', '_')),
                                     model.get_value(piter, self.NAME_COLUMN)))
 
-    def do_update(self):
+        self.fill_languages_button()
+
+    def update_remove_revert(self):
         piter, node = self.get_selected_tool()
 
-        removable = piter is not None and node is not None and node.is_local()
+        removable = node is not None and node.is_local()
 
         self['remove-tool-button'].set_sensitive(removable)
         self['revert-tool-button'].set_sensitive(removable)
@@ -209,6 +477,11 @@ class Manager:
             self['remove-tool-button'].show()
             self['revert-tool-button'].hide()
 
+    def do_update(self):
+        self.update_remove_revert()
+
+        piter, node = self.get_selected_tool()
+
         if node is not None:
             self.current_node = node
             self.fill_fields()
@@ -239,16 +512,19 @@ class Manager:
         piter, node = self.get_selected_tool()
 
         if node.is_global():
+            shortcut = node.shortcut
+            
             if node.parent.revert_tool(node):
-                if self.current_node.shortcut and \
-                   self.current_node.shortcut in self.accelerators:
-                    del self.accelerators[self.current_node.shortcut]                
+                self.remove_accelerator(node, shortcut)
+                self.add_accelerator(node)
+
                 self['revert-tool-button'].set_sensitive(False)
                 self.fill_fields()
+                
+                self.model.row_changed(self.model.get_path(piter), piter)
         else:
             if node.parent.delete_tool(node):
-                if self.current_node.shortcut:
-                    del self.accelerators[self.current_node.shortcut]
+                self.remove_accelerator(node)
                 self.current_node = None
                 self.script_hash = None
                 if self.model.remove(piter):
@@ -277,26 +553,50 @@ class Manager:
         self.save_current_tool()
         self.do_update()
 
+    def accelerator_collision(self, name, node):
+        if not name in self.accelerators:
+            return []
+            
+        ret = []
+        
+        for other in self.accelerators[name]:
+            if not other.languages or not node.languages:
+                ret.append(other)
+                continue
+            
+            for lang in other.languages:
+                if lang in node.languages:
+                    ret.append(other)
+                    continue
+        
+        return ret
+
     def set_accelerator(self, keyval, mod):
         # Check whether accelerator already exists
+        self.remove_accelerator(self.current_node)
 
-        if self.current_node.shortcut:
-            del self.accelerators[self.current_node.shortcut]
         name = gtk.accelerator_name(keyval, mod)
-        if name != '':
-            if name in self.accelerators:
-                dialog = gtk.MessageDialog(self.dialog,
-                                           gtk.DIALOG_MODAL,
-                                           gtk.MESSAGE_ERROR,
-                                           gtk.BUTTONS_OK,
-                                           _('This accelerator is already bound to %s') % self.accelerators[name].name)
-                dialog.run()
-                dialog.destroy()
-                return False
-            self.current_node.shortcut = name
-            self.accelerators[name] = self.current_node
-        else:
-            self.current_node.shortcut = None
+
+        if name == '':
+            self.current_node.shorcut = None
+            return True
+            
+        col = self.accelerator_collision(name, self.current_node)
+        
+        if col:
+            dialog = gtk.MessageDialog(self.dialog,
+                                       gtk.DIALOG_MODAL,
+                                       gtk.MESSAGE_ERROR,
+                                       gtk.BUTTONS_OK,
+                                       _('This accelerator is already bound to %s') % (', '.join(map(lambda x: x.name, col)),))
+
+            dialog.run()
+            dialog.destroy()
+            return False
+
+        self.current_node.shortcut = name
+        self.add_accelerator(self.current_node)
+
         return True
 
     def on_accelerator_key_press(self, entry, event):
@@ -314,24 +614,24 @@ class Manager:
             return True
         elif event.keyval in range(gtk.keysyms.F1, gtk.keysyms.F12 + 1):
             # New accelerator
-            self.set_accelerator(event.keyval, mask)
-            entry.set_text(default(self.current_node.shortcut, ''))
-            self['commands'].grab_focus()
+            if self.set_accelerator(event.keyval, mask):
+                entry.set_text(default(self.current_node.shortcut, ''))
+                self['commands'].grab_focus()
+    
             # Capture all `normal characters`
             return True
         elif gtk.gdk.keyval_to_unicode(event.keyval):
             if mask:
                 # New accelerator
-                self.set_accelerator(event.keyval, mask)
-                entry.set_text(default(self.current_node.shortcut, ''))
-                self['commands'].grab_focus()
+                if self.set_accelerator(event.keyval, mask):
+                    entry.set_text(default(self.current_node.shortcut, ''))
+                    self['commands'].grab_focus()
             # Capture all `normal characters`
             return True
         else:
             return False
 
     def on_accelerator_focus_in(self, entry, event):
-        pass
         if self.current_node is None:
             return
         if self.current_node.shortcut:
@@ -342,6 +642,9 @@ class Manager:
     def on_accelerator_focus_out(self, entry, event):
         if self.current_node is not None:
             entry.set_text(default(self.current_node.shortcut, ''))
+            
+            piter, node = self.get_selected_tool()
+            self.model.row_changed(self.model.get_path(piter), piter)
 
     def on_tool_manager_dialog_response(self, dialog, response):
         if response == gtk.RESPONSE_HELP:
@@ -374,4 +677,19 @@ class Manager:
             
         cell.set_property('markup', markup)
 
+    def update_languages(self, popup):
+        self.current_node.languages = popup.languages()
+        self.g()
+        
+        self.fill_languages_button()
+
+    def on_languages_button_clicked(self, button):
+        popup = LanguagesPopup(self.current_node.languages)
+        popup.set_transient_for(self.dialog)
+        
+        origin = button.window.get_origin()
+        popup.move(origin[0], origin[1] - popup.allocation.height)
+        
+        popup.connect('destroy', self.update_languages)
+
 # ex:et:ts=4:
diff --git a/plugins/externaltools/tools/tools.ui b/plugins/externaltools/tools/tools.ui
index c107c53..60575c0 100644
--- a/plugins/externaltools/tools/tools.ui
+++ b/plugins/externaltools/tools/tools.ui
@@ -26,9 +26,7 @@
   </object>
   <object class="GtkListStore" id="model_input">
     <columns>
-      <!-- column-name gchararray -->
       <column type="gchararray"/>
-      <!-- column-name gchararray1 -->
       <column type="gchararray"/>
     </columns>
     <data>
@@ -319,14 +317,50 @@
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkComboBox" id="applicability">
+                          <object class="GtkHBox" id="hbox1">
                             <property name="visible">True</property>
-                            <property name="model">model_applicability</property>
                             <child>
-                              <object class="GtkCellRendererText" id="input_renderer"/>
-                              <attributes>
-                                <attribute name="text">0</attribute>
-                              </attributes>
+		                      <object class="GtkComboBox" id="applicability">
+		                        <property name="visible">True</property>
+		                        <property name="model">model_applicability</property>
+		                        <child>
+		                          <object class="GtkCellRendererText" id="applicability_renderer"/>
+		                          <attributes>
+		                            <attribute name="text">0</attribute>
+		                          </attributes>
+		                        </child>
+		                      </object>
+                              <packing>
+                                <property name="position">0</property>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkEventBox" id="languages_event_box">
+                              	<property name="visible">True</property>
+                              	<property name="visible-window">True</property>
+                              	<child>
+		                          <object class="GtkButton" id="languages_button">
+		                            <property name="visible">True</property>
+		                            <signal name="clicked" handler="on_languages_button_clicked"/>
+		                            <child>
+		                              <object class="GtkLabel" id="languages_label">
+		                                <property name="visible">True</property>
+		                                <property name="label" translatable="yes">All Languages</property>
+		                                <property name="xalign">0</property>
+		                                <property name="yalign">0.5</property>
+		                                <property name="ellipsize">PANGO_ELLIPSIZE_MIDDLE</property>
+		                              </object>
+		                            </child>
+		                          </object>
+		                        </child>
+		                      </object>
+                              <packing>
+                                <property name="position">1</property>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                              </packing>
                             </child>
                           </object>
                           <packing>
@@ -343,7 +377,7 @@
                             <property name="visible">True</property>
                             <property name="model">model_output</property>
                             <child>
-                              <object class="GtkCellRendererText" id="input_renderer1"/>
+                              <object class="GtkCellRendererText" id="output_renderer"/>
                               <attributes>
                                 <attribute name="text">0</attribute>
                               </attributes>
@@ -363,7 +397,7 @@
                             <property name="visible">True</property>
                             <property name="model">model_input</property>
                             <child>
-                              <object class="GtkCellRendererText" id="input_renderer2"/>
+                              <object class="GtkCellRendererText" id="input_renderer"/>
                               <attributes>
                                 <attribute name="text">0</attribute>
                               </attributes>



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