Wednesday, 26 October 2011

Escribiendo plugins para Gedit 3 con Python


As part of my contribution to gedit project, I am translating some documents.
Here you can find a Tutorial on how to write plugins for Gedit 3 in Python in my mother tongue language, which is Spanish. I hope you find it as useful as I did :)

Escribiendo plugins para Gedit 3 con Python

La versión en su idioma original se encuentra  acá.
Esta es una guía para programar plugins para Gedit 3, el editor de textos por defecto de GNOME 3. 
Gedit usa el sistema de plugins Libpeas Gobject y los plugins se pueden escribir en C o Python. Esta guía cubrirá la escritura de plugins usando Python.
Parte de la información que proporciono también se encuentra en Python Plugin How To for Gedit en live.gnome.org. Sin embargo, espero proporcionar un enfoque diferente sobre la presentación de la información y agregar algunos principios adicionales.

Antes de empezar

Asegúrese de que tiene gedit 3.xo superior. Los plugins para gedit 2.x no son compatibles con 3.x.

Los Plugins en Python de gedit 3 utilizan los enlaces para PyGObject GObject GLib, Gio y GTK. Este es el reemplazo de PyGTK. Si usted todavía no está familiarizado con PyGObject, lea Introspection Porting Guide(no se preocupe, es fácil pasar de PyGTK a PyGOBject).

Ss usted no tiene experiencia con Python y GTK entonces esta guía, puede no ser adecuada para usted (aún). Dedique algo de tiempo a aprender a crear pequeñas aplicaciones GTK con Python primero.

El archivo .plugin

Todo plugin empieza con un archivo .plugin usado para describir el plugin. Gedit utilizará la información proporcionada en este archivo para cargar el plug-in e incluirlo en la lista de plugins disponibles.
Para que gedit pueda encontrar este archivo . plugin, se debe colocar en el directorio ~ / .local / share / gedit / plugins /, que puede ser necesario que lo cree si no ha instalado ningún plugin de gedit a nivel de usuario .

Nota: Existe un directorio de todo el sistema para plugins, que es donde puede encontrar plugins que fueron instalados con gedit por el administrador de paquetes de software de su distribución.
example01.plugin

[Plugin]

Loader=python

Module=example01

IAge=3

Name=Example #1

Description=A minimal plugin

Authors=Micah Carrick

Copyright=Copyright © 2011 Micah Carrick

Website=http://www.micahcarrick.com

Version=3.0.0


Loader: Para plugins en Python, siempre va Python
Module: El módulo de python del plugin que utiliza las convenciones típicas de nomenclatura a importar. En el archivo .plugin de arriba, Gedit espera encontrar un archivo llamado example01.py en el mismo directorio que el archivo .plugin o un directorio llamado example01que contiene un archivo __init__.py. Vea Python Modules Documentation si no entiende los módulos o paquetes de Python.
iAge:  Los plugins para gedit 3.x siempre tendrá n un "3" para iAge.

Las restantes líneas del archivo .plugin se explican bastante por sí mismas y se muestran en las capturas de pantalla siguientes:







Ejemplo 1: Un plugin simple




from gi.repository import GObject, Gedit



class ExamplePlugin01(GObject.Object, Gedit.WindowActivatable):

    __gtype_name__ = "ExamplePlugin01"

    window = GObject.property(type=Gedit.Window)



    def __init__(self):

        GObject.Object.__init__(self)



    def do_activate(self):

        print "Window %s activated." % self.window



    def do_deactivate(self):

        print "Window %s deactivated." % self.window



    def do_update_state(self):

        print "Window %s state updated." % self.window


Este ejemplo muestra un plugin muy simple. Este plugin implementa la interfaz Gedit.WindowActivatable para el punto de extensión Gedit.Window (lo cual no debería tener ningún sentido por ahora). El concepto de puntos de extensión se examinarán con más detalle en un ejemplo diferente. Por ahora, diremos que este ejemplo se colocará en cada ventana de gedit (por lo general sólo una).

Para ver cómo funciona este plugin:
1. Asegúrese de que example01.plugin y example01.py se copiaron al directorio ~/.local/share/gedit/plugins/
2.Ejecute gedit desde una terminal para que pueda ver la salida de las sentencias print.
3. Abra el cuadro de diálogos de Preferencias (Edición > Preferencias). Elija la pestaña de Plugins y active el plugin Example #1.
4.Cierre el cuadro de diálogos de Preferencias y abra un nuevo documento con Gedit.
5.Vuelva a Plugins y desactive el plugin Example #1.
En la ventana de la terminal debería ver los siguientes mensajes:
Window <Window object at 0x1104e10 (GeditWindow at 0x1180020)> activated.
Window <Window object at 0x1104e10 (GeditWindow at 0x1180020)> state updated.
Window <Window object at 0x1104e10 (GeditWindow at 0x1180020)> deactivated.

Dado que este es el primer ejemplo, hay algunas cosas a destacar. En primer lugar, la declaración de importación es un poco diferente de lo a que puede estar acostumbrado:

Esta declaración de importación utiliza PyGObject para importar GObject y las APIs de gedit.
La clase ExamplePlugin01 hereda del objeto Gobject.Object y de la interfaz Gedit.WindowActivatable. Los plugins de Gedit siempre extenderán el Gobject.Object e implementan una de las tres interfaces del punto de extensión (nuevamente, los puntos de extensión se explicarán en un ejemplo posterior).
Al implementar la interfaz Gedit.WindowActivatable, este plugin debe definir tres métodos: do_activate(), do_deactivate(), and do_update_state(). La activación ocurre cuando el plugin se activa desde el cuadro de diálogo de Preferencias o cuando gedit se inicia. La desactivación ocurre cuando se desactiva el plugin desde el cuadro de diálogo de Preferencias o cuando se cierra gedit. El estado de actualización ocurre cuando algo cambia en la ventana UI del gedit, como por ejemplo agregar, guardar o eliminar una pestaña.
En consecuencia, este plugin no hace nada en sí mismo. Es solo un ejemplo con el mínimo código necesario para implementar un plugin de gedit.


Ejemplo 2: Puntos de extensión




from gi.repository import GObject, Gedit



class ExampleAppActivatable(GObject.Object, Gedit.AppActivatable):

    __gtype_name__ = "ExampleAppActivatable"

    app = GObject.property(type=Gedit.App)



    # ...



class ExampleWindowActivatable(GObject.Object, Gedit.WindowActivatable):

    __gtype_name__ = "ExampleWindowActivatable"

    window = GObject.property(type=Gedit.Window)



    # ...



class ExampleViewActivatable(GObject.Object, Gedit.ViewActivatable):

    __gtype_name__ = "ExampleViewActivatable"

    view = GObject.property(type=Gedit.View)



    # ...


Este plugin es similar al Ejemplo #1 excepto porque implementa cada una de las tres interfaces de extensión. La mayoría de los plugins implementan sólo una interfaz de extensión basándose en los objetivos del plugin.

Gedit.AppActivatable

Incluso cuando tiene múltiples ventanas de Gedit abiertas, hay sólo una instancia de la aplicación que administra las ventanas abiertas. Un plugin que implementa la interfaz Gedit.AppActivatable se activa cuando la primera ventana de gedi se abre y se desactiva cuando la última ventana de gedit se cierra (o desde el administrador de plugins en el cuadro de diálogo Preferencias).
El plugin interactuaría entonces con gedit a través de la API Gedit.App.

Gedit.WindowActivatable

Un plugin que implementa la interfaz Gedit.WindowActivatable se activa cuando cada ventana se abre y se desactiva cuando la ventana se cierra. Los plugins WindowActivatable también implementan el método do_update_state() para cuando la UI de esa ventana cambia.
El plugin interactuaría entonces con la API Gedit.Window.

Gedit.ViewActivatable

Un plugin que implementa la interfaz Gedit.ViewActivatable se activa cuando se crea una vista y se desactiva cuando esa vista se cierra. Una vista es el widget que muestra el texto del documento.
Los plugins ViewActivatable también implementan el método do_update_state() para cuando la UI de esa ventana cambia.
El plugin interactuaría con gedit por medio de la API Gedit.View (que hereda de Gedit.View, que a su vez hereda de Gtk.TextVie).


Eligiendo la extensión a implementar
La mayoría de los plugins implementan la interfaz Gedit.WindowActivatable o Gedit.ViewActivatable.
Un plugin implementará la interfaz Gedit.WindowActivatable si necesita administrar o interactuar con múltiples documentos o si necesita agregar elementos a la UI (como menúes y barras de herramientas, paneles laterales, paneles inferiores, etc). Ejemplos de plugins de este tipo son la terminal embebida y el selector de color.
Un plugin implementará la interfaz Gedit.ViewActivatable si necesita administrar cómo es manejado el texto. Ejemplos de plugins de este tipo son los plugins de espacios inteligentes y de completitud de paréntesis.
Los ejemplos en esta guía implementan la interfaz Gedit.WindowActivatable.

Ejemplo 3: Conexión a señales.
Download Example #3


from gi.repository import GObject, Gtk, Gedit



class ExamplePlugin03(GObject.Object, Gedit.WindowActivatable):

    __gtype_name__ = "ExamplePlugin03"

    window = GObject.property(type=Gedit.Window)



    def __init__(self):

        GObject.Object.__init__(self)



    def do_activate(self):

        print "Activating plugin..."

        handlers = []

        handler_id = self.window.connect("tab-added", self.on_tab_added)

        handlers.append(handler_id)

        print "Connected handler %s" % handler_id

   

        handler_id = self.window.connect("tab-removed", self.on_tab_removed)

        handlers.append(handler_id)

        print "Connected handler %s" % handler_id

   

        self.window.set_data("ExamplePlugin03Handlers", handlers)



    def do_deactivate(self):

        print "Deactivating plugin..."

        handlers = self.window.get_data("ExamplePlugin03Handlers")

        for handler_id in handlers:

            self.window.disconnect(handler_id)

            print "Disconnected handler %s" % handler_id



    def do_update_state(self):

        pass



    def on_tab_added(self, window, tab, data=None):

        document = tab.get_document()

        print "'%s' has been added." % document.get_short_name_for_display()



    def on_tab_removed(self, window, tab, data=None):

        document = tab.get_document()

        print "'%s' has been removed." % document.get_short_name_for_display()


Como el resto de GTK, los distintos objetos de gedit emiten señales para los diversos eventos que ocurren. Un plugin puede conectar métodos manejadores (callbacks) a cualquiera de estas señales.
Como un plugin puede ser activado o desactivado por el usuario final, es importante utilizar los métodos do_activate() y do_deactivate() para asegurarse de que las señales son conectadas y desconectadas según el plugin es activado o desactivado.
Este ejemplo se conecta a las señales "tab-added" y "tab-removed"de GeditWindow. Cada handler_id de estas conexiones, tal como son devueltos por el método connect(), se almacenan en una lista y se adjuntan al objeto ventana usando set_data(). Cuando se desactiva el plugin, la lista de handler_ids se devuelve usando get_data() y es iterada para desconectar cada manejador de señal .
La salida de este plugin debería ser similar a la siguiente:

Activating plugin...
Connected handler 2271
Connected handler 2272
''Unsaved Document 1'' has been added.
''Unsaved Document 1'' has been removed.
''Unsaved Document 2'' has been added.
''Unsaved Document 2'' has been removed.
Deactivating plugin...
Disconnected handler 2271
Disconnected handler 2272


Conectarse a señales para manejar eventos es la base para escribir plugins de gedit que verdaderamente hagan algo. Navegue Gedit 3 API para ver los diversos objetos y sus señales. Y no se olvide que también tiene disponibles a las señales de los objetos padres. Por ejemplo, GeditWindow hereda de GtkWindow, entonces se podría conectar con la señal "set-focus" signal emitida por GeditWindow.
Nota: las señales son un concepto fundamental de la programación GTK y están más allá del alcance de este texto. Se asume que está familiarizado con eventos y señales en GTK

Ejemplo 4: Insertando items de menú.
Download Example #4

from gi.repository import GObject, Gtk, Gedit



UI_XML = """<ui>

<menubar name="MenuBar">

    <menu name="ToolsMenu" action="Tools">

      <placeholder name="ToolsOps_3">

        <menuitem name="ExampleAction" action="ExampleAction"/>

      </placeholder>

    </menu>

</menubar>

</ui>"""



class ExamplePlugin04(GObject.Object, Gedit.WindowActivatable):

    __gtype_name__ = "ExamplePlugin04"

    window = GObject.property(type=Gedit.Window)

  

    def __init__(self):

        GObject.Object.__init__(self)



    def _add_ui(self):

        manager = self.window.get_ui_manager()

        self._actions = Gtk.ActionGroup("Example04Actions")

        self._actions.add_actions([

            ('ExampleAction', Gtk.STOCK_INFO, "Say _Hello",

                None, "Say hello to the current document",

                self.on_example_action_activate),

        ])

        manager.insert_action_group(self._actions)

        self._ui_merge_id = manager.add_ui_from_string(UI_XML)

        manager.ensure_update()

    

    def do_activate(self):

        self._add_ui()



    def do_deactivate(self):

        self._remove_ui()



    def do_update_state(self):

        pass



    def on_example_action_activate(self, action, data=None):

        view = self.window.get_active_view()

        if view:

            view.get_buffer().insert_at_cursor("Hello World!")

    

    def _remove_ui(self):

        manager = self.window.get_ui_manager()

        manager.remove_ui(self._ui_merge_id)

        manager.remove_action_group(self._actions)

        manager.ensure_update()


Los plugins que implementan la interfaz Gedit.WindowActivatable frecuentemente necesitan agregar items al menú de gedit. Como Gedit.Window expone su GtkUIManager a través del método get_ui_manager(), agregar un nuevo ítem al menú es tan simple como mezclar una definición UI XML.
Gedit provee varios elementos <placeholder> que un plugin puede elegir para agregarle items de menú. Este plugin ejemplo inserta un ítem de menú al placeholder ToolsOps_3". Puede ver la lista de los placeholder disponibles en gedit-ui.xml.
Este ejemplo agrega un item al menú “Tools” llamado “Say Hello”. Cuando se lo selecciona, la frase “Hello world” es insertada en el documento actual.

Nuevamente, es importante para el plugin insertar ítems de menú en el método do_activate() y eliminar dichos ítems en el método do_deactivate(). Sólo para mantener el código cuidadosamente organizado, este ejemplo pone el código para agregar y eliminar el UI XML del plugin en métodos llamados add_ui() y remove_ui() respectivamente.

Ejemplo 5: agregando paneles laterales e inferiores
Download Example #5


from gi.repository import GObject, Gtk, Gedit



class ExamplePlugin05(GObject.Object, Gedit.WindowActivatable):

    __gtype_name__ = "ExamplePlugin05"

    window = GObject.property(type=Gedit.Window)

  

    def __init__(self):

        GObject.Object.__init__(self)

     

    def do_activate(self):

        icon = Gtk.Image.new_from_stock(Gtk.STOCK_YES, Gtk.IconSize.MENU)

        self._side_widget = Gtk.Label("This is the side panel.")

        panel = self.window.get_side_panel()

        panel.add_item(self._side_widget, "ExampleSidePanel", "Example #5", icon)

        panel.activate_item(self._side_widget)



    def do_deactivate(self):

        panel = self.window.get_side_panel()

        panel.remove_item(self._side_widget)



    def do_update_state(self):

        pass



</ui>"""



class ExamplePlugin04(GObject.Object, Gedit.WindowActivatable):

    __gtype_name__ = "ExamplePlugin04"

    window = GObject.property(type=Gedit.Window)

  

    def __init__(self):

        GObject.Object.__init__(self)

 

    def _add_ui(self):

        manager = self.window.get_ui_manager()

        self._actions = Gtk.ActionGroup("Example04Actions")

        self._actions.add_actions([

            ('ExampleAction', Gtk.STOCK_INFO, "Say _Hello",

                None, "Say hello to the current document",

                self.on_example_action_activate),

        ])

        manager.insert_action_group(self._actions)

        self._ui_merge_id = manager.add_ui_from_string(UI_XML)

        manager.ensure_update()

     

    def do_activate(self):

        self._add_ui()



    def do_deactivate(self):

        self._remove_ui()



    def do_update_state(self):

        pass

 

    def on_example_action_activate(self, action, data=None):

        view = self.window.get_active_view()

        if view:

            view.get_buffer().insert_at_cursor("Hello World!")

     

    def _remove_ui(self):

        manager = self.window.get_ui_manager()

        manager.remove_ui(self._ui_merge_id)

        manager.remove_action_group(self._actions)

        manager.ensure_update()



Los plugins que implementan la interfaz Gedit.WindowActivatable también frecuentemente agregan items a los paneles laterales o inferior. Gedit.Window proporciona los métodos get_side_panel() y get_bottom_panel() para obtener un objeto Gedit.Panel. El ítem del panel luego se agrega con add_item().
Este plugin ejemplo agrega un simple Gtk.Label al panel lateral cuando es activado y elimina dicho ítem del panel usando remove_item() cuando el plugin es desactivado.
 
Ejemplo 6: El cuadro de diálogo de configuración
Download Example #6

from gi.repository import GObject, Gtk, Gedit, PeasGtk



class ExamplePlugin06(GObject.Object, Gedit.AppActivatable, PeasGtk.Configurable):

    __gtype_name__ = "ExamplePlugin06"

    window = GObject.property(type=Gedit.Window)

  

    def __init__(self):

        GObject.Object.__init__(self)

       

    def do_activate(self):

        pass

   

    def do_create_configure_widget(self):

        widget = Gtk.CheckButton("A configuration setting.")

        widget.set_border_width(6)

        return widget

       

    def do_deactivate(self):

        pass



    def do_update_state(self):

        pass


Este ejemplo provee las opciones de configuración al usuario al implementar PeasGtk.Configurable y proveyendo el método do_create_configure_widget() para construir el widget que gedit mostrará en un cuadro de diálogo de configuración. A diferencia de versiones anteriores de Gedit, este método devuelve el widget hijo en un cuadro de diálogo y no el cuadro de diálogo completo en sí mismo. Gedit manejará la inserción de este widget en un cuadro de diálogo y su presentación (mire la captura de pantalla de abajo).


Un plugin de gedit debería idealmente guardar las opciones de configuración de usuario usando Gsettings, la API de alto nivel para opciones de configuración de la aplicación (en GNOME 3 es la API frontend de dconf). Los plugins que vienen con gedit usan el esquema org.gnome.gedit.plugins para opciones de configuración. Using GSettings with Python/PyGObject proporciona un tutorial corto.
Hay un problema con la implementación actual de Gsettings ya que requiere compilar y copiar los esquemas en un directorio del sistema, de esta forma negando la simplicidad y conveniencia de guardar los plugins de python en un único directorio. Bug #649717 está siguiendo dicho problema.


Usando la documentación de la API
Al trabajar en los plugins de gedit probablemente necesitará referirse a la documentación de la API.
Hasta ahora no hay mucha documentación de Python. La razón es, en parte, porque PyGObject mapea la API de C muy de cerca. El proyecto PyGObject es también relativamente nuevo. Por suerte para nosotros, la API de C es suficiente.
Mucho de esto está cubierto en Introspection Porting Guide. Sin embargo, a continuación hay algunos ejemplos para ilustrar cómo la API de C de estas bibliotecas se traducen fácilmente a Python.


C API Python (PyGObject)
gedit_window_close_tab() Gedit.Window.close_tab()
gedit_document_get_location() Gedit.Document.get_location()
gtk_button_new() Gtk.Button()
gdk_pixbuf_new_from_file() GdkPixbuf.Pixbuf.new_from_file()



A continuación hay algunos links a bibliotecas de C usadas frecuentemente para plugins de gedit y sus correspondientes módulos PYGObject.





 





 















 

 

1 comment:

  1. gracias no sabes cuento e he buscado este tutorial

    ReplyDelete