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.
A continuación hay algunos links a bibliotecas de C usadas frecuentemente para plugins de gedit y sus correspondientes módulos PYGObject.