Python Plugin How To for gedit 2
This document describes how to create plugins for gedit 2 using Python, you can find the tutorial for the new Python plugins of gedit 3 here.
This document describes how to create plugins for Gedit using Python. Below follows a list of the different topics you need to know about. Note also that knowledge of GTK (and more importantly, PyGTK) is useful in creating an actual plugin. An extensive tutorial can be found here.
Contents
Files needed
Every Python plugin needs at least two files. One file (pluginname.gedit-plugin) is to tell Gedit where the plugin can be found, what it's called, a short description, who's the author, etc. This file is in the .desktop format (see example). The second file is the actual Python code. Both of these files need to be placed in either the system-wide plugins directory /usr/lib/gedit-2/plugins/, or in the user plugins directory, which may need to be created, ~/.gnome2/gedit/plugins/.
Gedit plugin infrastructure
Most plugin writers find the way Gedit plugins work counterintuitive at first glance. It's important to read this section carefully. A Python plugin is derived from the gedit.Plugin base class which defines the following functions (which you can override):
- activate(self, gedit.Window)
- deactivate(self, gedit.Window)
- update_ui(self, gedit.Window)
Now, Gedit is an application which usually only runs one instance, even if you launch it multiple times. When Gedit starts it looks for an existing Gedit instance, hands over control to that instance and exits. Plugins are therefore only instantiated once (during load) of the master Gedit application. However, plugins usually apply to windows. This is what the activate and deactivate are for. Whenever a new window is created the activate function on your plugin will be called with one argument (the window). On the other hand, when a window is destroyed the deactivate function is called.
The approach I find easy to implement (also implemented in the example) is to create a new class. Whenever a new Gedit window is created and activate is run, I instantiate a new object of this class and attach it to the window. This object will now handle that window and do whatever the plugin needs to do. This way every window has a separate object controlling it.
The update_ui function is called when the plugin is requested to see if it needs to update its UI.
Writing the plugin
Now we are ready to actually write a plugin. As stated in Files needed we first need to create a .gedit-plugin file which will give Gedit information about our new plugin.
examplepy.gedit-plugin
[Gedit Plugin] Loader=python Module=examplepy IAge=2 Name=Example py Description=A Python plugin example Authors=Jesse van den Kieboom <jesse@icecrew.nl> Copyright=Copyright © 2006 Jesse van den Kieboom <jesse@icecrew.nl> Website=http://www.gedit.org
To explain the specified fields:
Loader: which loader Gedit needs to use; for Python plugins this is python
Module: the file in which your plugin is located (leaving out the extension .py). You can also specify a directory here. As with normal Python applications you need to put a __init__.py in that directory)
IAge: the plugin version, this is always 2
- Name: the name of the plugin. This will show up in the plugins list in the preference dialog
- Description: a short description of the plugin. This will show up in the plugins list in the preference dialog
- Authors: a list of people who wrote the plugin. This will show up in the about dialog of the plugin (plugins list in the preference dialog)
- Copyright: the plugins copyright. This will show up in the about dialog of the plugin (plugins list in the preference dialog)
- Website: optionally, the plugins website
Now we need to create the plugin itself. The plugin is called examplepy, so we create examplepy.py. At the most minimal level, it just defines one class derived from gedit.Plugin defining activate, deactivate and update_ui:
Minimal plugin
Our first plugin! When we copy these two files to ~/.gnome2/gedit/plugins and restart Gedit the plugin will be detected. When you open the plugins list in the preferences dialog you should see something like this:
Implementing the window controller
Of course, this plugin doesn't do anything. We can activate it, but it's not of much use, yet. Before implementing useful stuff, let's first get the structure right. We're now going to implement the idea I introduced above, creating a separate class (ExamplePyWindowHelper) which will control a window.
1 import gedit
2
3 class ExamplePyWindowHelper:
4 def __init__(self, plugin, window):
5 print "Plugin created for", window
6 self._window = window
7 self._plugin = plugin
8
9 def deactivate(self):
10 print "Plugin stopped for", self._window
11 self._window = None
12 self._plugin = None
13
14 def update_ui(self):
15 # Called whenever the window has been updated (active tab
16 # changed, etc.)
17 print "Plugin update for", self._window
18
19 class ExamplePyPlugin(gedit.Plugin):
20 def __init__(self):
21 gedit.Plugin.__init__(self)
22 self._instances = {}
23
24 def activate(self, window):
25 self._instances[window] = ExamplePyWindowHelper(self, window)
26
27 def deactivate(self, window):
28 self._instances[window].deactivate()
29 del self._instances[window]
30
31 def update_ui(self, window):
32 self._instances[window].update_ui()
What you see here is that when activate is called we instantiate a new ExamplePyWindowHelper. The instance is stored per window object. When deactivate or update_ui is called the plugin instance is retrieved and either stopped (deactivate) or updated (update_ui).
The plugin still doesn't do anything except printing some messages when it's created, stopped and updated. But we do have a good starting point now to really implement something.
Adding a menu item
So what we are going to do now is add a menu item in the menu bar. What we'll implement is that when the menu item is activated the currently active buffer is cleared.
1 from gettext import gettext as _
2
3 import gtk
4 import gedit
5
6 # Menu item example, insert a new item in the Tools menu
7 ui_str = """<ui>
8 <menubar name="MenuBar">
9 <menu name="ToolsMenu" action="Tools">
10 <placeholder name="ToolsOps_2">
11 <menuitem name="ExamplePy" action="ExamplePy"/>
12 </placeholder>
13 </menu>
14 </menubar>
15 </ui>
16 """
17 class ExamplePyWindowHelper:
18 def __init__(self, plugin, window):
19 self._window = window
20 self._plugin = plugin
21
22 # Insert menu items
23 self._insert_menu()
24
25 def deactivate(self):
26 # Remove any installed menu items
27 self._remove_menu()
28
29 self._window = None
30 self._plugin = None
31 self._action_group = None
32
33 def _insert_menu(self):
34 # Get the GtkUIManager
35 manager = self._window.get_ui_manager()
36
37 # Create a new action group
38 self._action_group = gtk.ActionGroup("ExamplePyPluginActions")
39 self._action_group.add_actions([("ExamplePy", None, _("Clear document"),
40 None, _("Clear the document"),
41 self.on_clear_document_activate)])
42
43 # Insert the action group
44 manager.insert_action_group(self._action_group, -1)
45
46 # Merge the UI
47 self._ui_id = manager.add_ui_from_string(ui_str)
48
49 def _remove_menu(self):
50 # Get the GtkUIManager
51 manager = self._window.get_ui_manager()
52
53 # Remove the ui
54 manager.remove_ui(self._ui_id)
55
56 # Remove the action group
57 manager.remove_action_group(self._action_group)
58
59 # Make sure the manager updates
60 manager.ensure_update()
61
62 def update_ui(self):
63 self._action_group.set_sensitive(self._window.get_active_document() != None)
64
65 # Menu activate handlers
66 def on_clear_document_activate(self, action):
67 doc = self._window.get_active_document()
68 if not doc:
69 return
70
71 doc.set_text('')
We left out the ExamplePyPlugin because nothing has changed there. So what happens here. When the plugin instance is created it inserts a menu item. Gedit uses GtkUIManager and defines placeholders for plugins to insert menu items in. This makes it easy for us to insert a menu item. We add a Clear document item to the tools menu and register the on_clear_document_activate callback to be called when the item is activated. In the callback we simply retrieve the currently active document and clear the text.
Additionally we change the sensitivity of the action group in update_ui. The action group will be insensitive when there is no currently active document (because we can't clear a document that doesn't exist). That's everything there is to it. We now have our first functional Python plugin!
Making use of side/bottom panels
In the context of the above examples, the code
self._window.get_side_panel()
would return a gedit.Panel object corresponding to the side panel, and
self._window.get_bottom_panel()
would return the bottom panel. Methods of the gedit.Panel object can be found in the API section.
Events
This is a very, very incomplete description of signals. A proper discussion of signals can be found in the pyGTK tutorial. You will need to consult this.
Your plugin can react to certain events (such as the opening of a tab or the saving of a file). This is accomplished by listening to signals from various Gedit objects (gedit.Window and so on). A full specification of the signals emitted by each object can be found in the C API.
To listen for a certain signal, use the connect() function of the object. The syntax is as follows:
object.connect("signal", handler, other_arg1, ...)
The signal that you are listening for is the first argument, in quotes. The second argument is the name of a function that will handle the signal event. The arguments to this function will vary by signal; consult the reference for your particular signal to find out all the information. connect() function returns an integer that server as an identified. An example in actual use would be:
l_id = doc.connect("loaded", self.add, view).
where doc is a gedit.Document and the add function is defined in the plugin class. You will also want to tell doc that l_id is a handler, and so you'll need a function that looks something like
doc.set_data("ExamplePyPluginHandlerId", l_id)
When cleaning up, remember to kill all your signal handlers. Given the above example, cleanup code would look something like this:
l_id = doc.get_data("ExamplePyPluginHandlerId") doc.disconnect(l_id) doc.set_data("ExamplePyPluginHandlerId", None)
Adding a configure dialog for your plugin
If you want to be able to display a configuration dialog for your plugin, you have to add two additional methods to your plugin class.
Gedit will handle calling the run() method on the dialog for you.
Writing plugins without wrappers
If you want to write a plugin using Python, but the appropriate wrappers for a specific library are unavailable, you can use the "dl" module to access the library's functions. For example, if you want to access the Enchant spell checking library you could use the following code:
1 import dl
2 class Enchant(object):
3 def __init__(self, lang='en_US'):
4 try:
5 self.library = dl.open('libenchant.so')
6
7 broker = self.library.call('enchant_broker_init')
8 self.dict = self.library.call(
9 'enchant_broker_request_dict', broker, lang)
10
11 except dl.error:
12 self.incorrect_spelling = lambda x: False
13
14 def incorrect_spelling(self, word):
15 return bool(self.library.call('enchant_dict_check',
16 self.dict, word, len(word)))
Note: The "dl" module has been deprecated in Python 2.5, you should use the "ctypes" module instead.
Drag and Drop
The MDI document area is a drag and drop target. You can use the following data types from your source:
Data Type |
Purpose |
text/uri-list |
Opens a tab for each filename passed. |
This section is incomplete
Template generator
A Python plugin template can be easily generated using the little script attached to this page: gedit-py-plugin.py (mind that this is just a very simple little script, there is a much better one in the tools/ directory in Gedit CVS)
Most important API
Below follows a list of the most important Gedit specific API functions you need to know about. Note that this list is incomplete. A full documentation of the Gedit C API is contained in the docs/ directory of the gedit-2.15.3 release, and can be found online here. This is useful because the C API and the Python API are very similar — generally, the C method foo_bar(foo_instance) is replaced by foo_instance.bar(). Wherever the API is incomplete, this can be used as a reference.
gedit
The gedit module defines two functions to get the default gedit.App and to get a gedit.Tab from a gedit.Document.
app_get_default() |
gets the default gedit.App instance |
tab_get_from_document(gedit.Document) |
gets the gedit.Tab belonging to a gedit.Document |
gedit.App
The gedit application provides functions to get all the gedit.Windows, create new windows, get all the documents, etc.
create_window([gdk.Screen]) |
creates a new gedit.Window on a gdk.Screen. If gdk.Screen is omitted then the default screen will be used |
get_windows() |
get a list of gedit.Window |
get_active_window() |
get the currently active window |
get_documents() |
get all the gedit.Document in all the windows |
get_views() |
get all the gedit.View in all the windows |
gedit.Window
The gedit window based on gtk.Window.
Methods
Method
Description
create_tab(jump_to)
create a new empty document. jump_to (boolean) specifies whether to select the new document when it's created
create_tab_from_uri(uri, encoding, line_pos, create, jump_to)
open a file. uri (string) specifies the file to open. encoding (gedit.Encoding) specifies the encoding to use (None for default), line_pos (int) specifies to which line to jump when the file is opened. create (boolean) specifies whether to create a new file if it doesn't exist yet. jump_to (boolean) specifies whether to select the new document when it's created
close_tab(tab)
close a tab (gedit.Tab)
close_all_tabs()
closes all the tabs in the window
get_active_tab()
gets the currently active tab (gedit.Tab)
set_active_tab(tab)
sets the currently active tab (gedit.Tab)
get_active_view()
gets the currently active view (gedit.View)
get_active_document()
gets the currently active document (gedit.Document)
get_documents()
gets a list of the documents (gedit.Document) in the window
get_unsaved_documents()
gets a list of unsaved documents (gedit.Document)
get_views()
get a list of the views (gedit.View) in the window
get_group()
gets the gtk.WindowGroup the window belongs to
get_side_panel()
gets the sidepanel (gedit.Panel)
get_bottom_panel()
gets the bottompanel (gedit.Panel)
get_statusbar()
gets the statusbar (gtk.Statusbar)
get_ui_manager()
gets the ui manager (gtk.UIManager)
get_state()
gets the state of the window (cumulative state of the different documents)
Signals
Signal Name
Description
Callback
tab-added
Tab added to window
def callback (tab, ...)
tab-removed
Tab removed from window
def callback (tab, ...)
tabs-reordered
Tabs reordered
def callback (...)
active-tab-changed
Active tab has been changed
def callback (tab, ...)
active-tab-state-changed
Active tab has changed state
def callback (...)
gedit.Panel
add_item(item, "name", image) |
Adds an item (a gtkWidget) to the panel. image is the icon (usually a gtk.Image). |
remove_item(item) |
Removes an item from the panel. item is the gtkWidget that was specified in add_item. |
activate_item(item) |
Switches to an existing panel item. item is the gtkWidget that was specified in add_item. |
set_property("visible", state) |
Shows or hides the entire panel. state can be True to display the panel, or False to hide it. |
gedit.Tab
The tab is based on a gtk.VBox and is the controller of gedit.View and gedit.Document.
get_view() |
get the view (gedit.View) |
get_document() |
get the document (gedit.Document) |
get_state() |
get the state |
set_auto_save_enabled(enabled) |
sets whether autosave is enabled |
get_auto_save_enabled() |
gets whether autosave is enabled |
set_auto_save_interval(interval) |
sets the interval on which to perform autosave |
get_auto_save_interval() |
gets the interval on which to perform autosave |
gedit.View
The view is based on a gtksourceview and is the view for gedit.Document.
cut_clipboard() |
cut selection to clipboard |
copy_clipboard() |
copy selection to clipboard |
paste_clipboard() |
paste clipboard at cursor position |
delete_selection() |
deletes the selected text |
select_all() |
select all text |
scroll_to_cursor() |
scroll to the cursor position |
set_colors(def, background, text, selection, sel_text) |
sets the different colors to use. def (boolean) specifies whether to use the default colors. background, text, selection and sel_text are gdk.Color objects which specify the corresponding colors |
set_font(def, font_name) |
sets the font to use. def (boolean) specifies whether to use the default font. font_name (string) specifies the font to use |
gedit.Document
The document is based on a gtksourcebuffer and is the document for gedit.View.
get_uri() |
gets the documents uri (or None if the document is unsaved) |
get_uri_for_display() |
gets the documents uri for display (or None if the document is unsaved) |
get_short_name_for_display() |
gets the documents short name for display |
get_mime_type() |
gets the documents mime type |
get_readonly() |
gets whether the document is read only |
load(uri, encoding, line_pos, create) |
loads a file. uri (string) specifies the file. encoding (gedit.Encoding) specifies the encoding (None for the default encoding). line_pos (int) specifies the line to scroll to after loading. create (boolean) specifies whether to create the file if it doesn't exist yet. |
insert_file(iter, uri, encoding) |
inserts the contents of a file. iter (gtk.TextIter) specifies where to insert the file. uri specifies the file to insert. encoding (gedit.Encoding) specifies the encoding (None for the default encoding) |
load_cancel() |
cancels the loading of a file |
is_untouched() |
gets whether the document has been changed since the last time it was saved |
is_untitled() |
gets whether the document has a title |
get_deleted() |
gets whether the document has been deleted |
goto_line(line) |
scroll to a line number. line (int) specifies the line number to scroll to |
set_search_text(text, flags) |
set text to search for |
get_search_text() |
gets the text to search for |
get_can_search_again() |
gets whether the end of the document has not yet been reached |
search_forward |
|
replace_all |
|
search_backward |
|
set_language(lang) |
sets the source language of the document to lang (gtk.SourceLanguage) |
get_language() |
gets the source language of the document (gtk.SourceLanguage) |
get_encoding() |
gets the encoding of the document (gedit.Encoding) |
gedit.Encoding
copy() |
|
free() |
|
get_charset() |
gets the actual character set like 'UTF-8' |
get_name() |
gets the name of the encoding like 'Unicode' |
to_string() |
gets its string representation like 'Unicode (UTF-8)' |