Title: Writing Plugins for gedit 3 with Python
Date:2014-01-01 21:19:46
Tags: gedit python plugin develop gnome
This is a guide to programming plugins for gedit 3, the default text editor for GNOME 3. gedit 3 uses the Libpeas GObject plugin system and the plugins can be written using C or Python. This guide will only cover writing plugins with Python.
Some of the information I provide is also found in the Python Plugin How To for Gedit found on live.gnome.org. However, I hope to provide a different approach to presenting the information and add some additional insights.
- Before you Begin
- The .plugin File
- Example #1 - A Simple Plugin
- Example #4 - Inserting Menu Items
- Example #5 - Adding Bottom and Side Panels
- Example #6 - Configure Dialog
- Using the API documentation
Before you Begin
- Make sure you have gedit 3.x or higher. The plugins for gedit 2.x are not compatible with 3.x.
- Python plugins in gedit 3 use PyGObject bindings for GLib, GObject, Gio, and GTK. This is the replacement for PyGTK. If you are not yet familiar with PyGObject, read the Introspection Porting Guide (don”t worry, it”s easy to move from PyGTK to PyGOBject).
- If you do not already have experience with Python and GTK then this guide is may not be right for you (yet). Spend some time learning how to create simple GTK applications with Python first.
The .plugin File
Every plugin begins with a .plugin file used to describe the plugin. Gedit will use the information provided in this file to load the plugin and include it in the list of available plugins.
In order for gedit to find this .plugin file, it must be placed in the \~/.local/share/gedit/plugins/ directory, which may need to be created if you have not yet installed any gedit plugins at the user-level.
Note: There is also a system-wide directory for plugins which is where you can find plugins that were installed along with gedit by your distribution”s software package manager.
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 - For python plugins, this is always python.
- Module - The python module for the plugin which uses the typical naming conventions for import. In the above .plugin file, Gedit expects to either find a file named example01.py in the same directory as the .plugin file or a directory named example01 which has an __init__.py file in it. See the Python Modules Documentation if you do not understand Python modules and packages.
- IAge - Plugins for gedit 3.x will always have a “3” for IAge.
The remaining lines in the .plugin file are pretty self-explanatory and are shown in the screenshots below.
Example #1 - A Simple Plugin
~~~~ {.sh_python}
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
~~~~
This example shows a very simple plugin. This plugin implements the Gedit.WindowActivatable interface for the Gedit.Window extension point–and that should not make any sense yet. The concept of extension points will be examined in more detail in a different example. For now, let”s just say that this example will be hooked in with each gedit window (usually just one).
To see how this plugin works:
- Make sure example01.plugin and example01.py have been copied to the \~/.local/share/gedit/plugins/ directory.
- Run gedit from a terminal so you can see the output from the print statements.
- Open the Preferences dialog (Edit > Preferences), select the Plugins tab, and activate the Example #1 plugin.
- Close the Preferences dialog and open a new document in Gedit.
- Go back into the Plugins and deactivate the Example #1 plugin.
You should see messages in your terminal window similar to the ones shown below.
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.
Since this is the first example, there are a few things to point out. First, the import statement is a bit different than you may be used to:
~~ {.sh_python} ~
from gi.repository import GObject, Gedit
~
This import statement uses PyGObject to import the GObject and gedit APIs.
The ExamplePlugin01 class inherits from both the GObject.Object object and the Gedit.WindowActivatable interface. Gedit plugins will always extend GObject.Object and implement one of the three interfaces for the extension point (again, extension points will be explained in a later example).
By implementing the Gedit.WindowActivatable interface, this plugin must define three methods: do_activate(), do_deactivate(), and do_update_state(). The activation occurs when either the plugin is activated in the Preferences dialog or when gedit starts. The deactivation occurs when the plugin is deactivated in the Preferences dialog or when gedit is closed. The update state occurs when something changes in the gedit window UI, such as adding/saving/removing a tab.
So, this plugin does not actuall do anything. It just demonstrates the minimum code necessary to implement a gedit plugin.
Example #2 - Extension Points
example02.py (partial listing)
~~~~ {.sh_python}
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)
# ...
~~~~
This plugin is just like Example #1 except that it implements each of the three extension interfaces. Most plugins implement just one extension interface based on the goals of the plugin.
Gedit.AppActivatable
Even when you have multiple gedit windows open, there is typically just one instance of the gedit application which manages the open windows. A plugin that implements the Gedit.AppActivatable interface is activated when the first gedit window is opened and deactivated when the last gedit window is closed (or from the Plugin manager in the Preferences dialog).
The plugin would then interact with gedit through the Gedit.App API.
Gedit.WindowActivatable
A plugin that implements the a Gedit.WindowActivatable interface is activated when each window is opened and deactivated when that window is closed. WindowActivatable plugins also implement the do_update_state() method which for when the UI for that gedit window changes.
The plugin would then interact with gedit through the Gedit.Window API.
Gedit.ViewActivatable
A plugin that implements the a Gedit.ViewActivatable interface is activated when a view is created and deactivated when that view is closed. A view is the widget that displays the text of the document. ViewActivatable plugins also implement the do_update_state() method which for when the UI for that gedit view changes.
The plugin would then interact with gedit through the Gedit.View API (which inherits GtkSource.View which inherits Gtk.TextView).
Choosing Which Extension to Implement
Most plugins will implement either the Gedit.WindowActivatable interface or the Gedit.ViewActivatable interface.
A plugin will typically implement the Gedit.WindowActivatable interface if it needs to manage or otherwise interact with multiple documents or if it needs to add items to the UI (such as menu and toobar items, side panels, bottom panels, etc.). Examples include the Embedded Terminal and Color Picker plugins.
A plugin will typically implement the Gedit.ViewActivatable interface if it only needs to manage how text is handled. Examples include the Smart Spaces and Bracket Completion plugins.
The example plugins in this guide typically implement the Gedit.WindowActivatable inteface.
Example #3 - Connecting to Signals
example03.py
~~~~ {.sh_python style=”height: 300px; overflow: auto;”}
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()
~~~~
Just like the rest of GTK, the various gedit objects emit signals for various events that occur. A plugin can connect handler methods (aka “callbacks”) to any of these signals.
Since a plugin may be activated and/or deactivated by the end-user, it is important to utilize the do_activate() and do_deactivate() methods to make sure that signals are connected and disconnected as the plugin is activated and deactivated.
This example connects to the “tab-added” and “tab-removed” signals of the GeditWindow. The handler_id for each of these connections, as returned by the connect() method, are stored in a list and attached to the window object using set_data(). When the plugin is deactivated, the list of handler IDs is retrieved using get_data() and iterated to disconnect each signal handler.
The output of this example plugin might look something like the following:
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
Connecting to signals to handle events is the foundation of writing gedit plugins that actually do something. Browse the Gedit 3 API to see the various objects and their signals. And don”t forget that you also have every parent object”s signals available as well. For example, the GeditWindow inherits GtkWindow, thus you could connect to the “set-focus” signal emitted by the GeditWindow.
Note: Signals are a fundamental concept of GTK programming and beyond the scope of this text. It is assumed that you are already familiar with events and signals in GTK.
Example #4 - Inserting Menu Items
example04.py
~~~~ {.sh_python style=”height: 300px; overflow: auto;”}
from gi.repository import GObject, Gtk, Gedit
UI_XML = “”“
“”“
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()
~~~~
Plugins that implement the Gedit.WindowActivatable interface often need to add items to the gedit menu. Since a Gedit.Window exposes it”s GtkUIManager through the get_ui_manager() method, adding a new menu items is as simple as merging a UI XML definition.
Gedit provides several \
Example #5 - Adding Bottom and Side Panels
example05.py
~~~~ {.sh_python style=”height: 300px; overflow: auto;”}
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
~~~~
Plugins that implement the Gedit.WindowActivatable interface also often add items to the gedit side or bottom panels. The Gedit.Window provides the get_side_panel() and get_bottom_panel() methods to get a Gedit.Panel object. The panel item is then added with add_item().
This example plugin adds a simple Gtk.Label to the side panel when it is activated and removes that panel item using remove_item() when the plugin is deactivated.
Example #6 - Configure Dialog
example06.py
~~~~ {.sh_python style=”height: 300px; overflow: auto;”}
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
~~~~
This example provides configuration settings to the user by implementing PeasGtk.Configurable and providing the do_create_configure_widget() method to build the widget that gedit will display in a configuration dialog. Unlike previous version of Gedit, this method returns the child widget displayed in the dialog and not the entire dialog itself. Gedit will handle putting this widget into a dialog and displaying it (see screenshot below).
A gedit plugin should ideally store user configuration settings using GSettings, the high-level API for application settings (in GNOME 3, this is the frontend API to dconf). The plugins shipped with gedit use the schema org.gnome.gedit.plugins for configuration settings. Using GSettings with Python/PyGObject provides a short tutorial.
There is an issue with the current implementation of GSettings which requires schemas to be compiled and copied to a system directory, thus negating the simplicity and convenience of dropping python plugins into a single directory. Bug #649717 is tracking this issue.
Using the API documentation
When working on gedit plugins you will probably need to refer to API documentation. As of now there is not much in the way of Python documentation. The reason is, in part, due to the fact that PyGObject maps the C API very closely. The PyGObject project is also relatively new. Lucky for us, the C API is quite sufficient.
Much of this is covered in the Introspection Porting Guide, however, here are a few examples to illustrate how the C API for these libraries easily translates to 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() |
With that, here are a few links to commonly used C libraries for gedit plugins and their corresponding PyGObject modules.
- Gedit 3 API - from gi.repository import Gedit
- GTK+ 3 API - from gi.repository import Gtk
- Gio API - from gi.repository import Gio
- GObject API - from gi.repository import GObject
- GDK-Pixbuf API - from gi.repository import GdkPixbuf
- GtkSourceView API - from gi.repository import GtkSource
- libpeas API - from gi.repository import Peas, PeasGtk