PythonPluginHowTo - GNOME Live! - The Wrong Side Of Code

Most plugin writers find the way gedit plugins work counterintuitive at first glance. It's important to read this .... opening of a tab or the saving of a file). This is ...
95KB taille 4 téléchargements 154 vues
Writing Gedit Plugins in Python

Gedit/PythonPluginHowTo - GNOME Live! This howto 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 gedit plugin infrastructure Writing the plugin  Minimal plugin  Minimal plugin  Implementing the window controller  Implementing the window controller  Making use of side/bottom panels  Events  Adding a configure dialog for your plugin  Writing plugins without wrappers  Template Template generator Most important API  gedit  gedit.App  gedit.Window  gedit.Panel  gedit.Tab  gedit.gedit.View  gedit.Document  gedit.Encoding

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 ~/.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)

Writing Gedit Plugins in Python Now, gedit is an application which usually only runs one instance, even if you launch it multiple times. When gedit starts it will look for an existing gedit instance, hand 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 Copyright=Copyright © 2006 Jesse van den Kieboom Website=http://www.gedit.org To explain the specified fields:        

Loader: which loaded 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 import gedit class ExamplePyPlugin(gedit.Plugin): def activate(self, window): pass

Writing Gedit Plugins in Python def deactivate(self, window): pass def update_ui(self, window): pass 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. import gedit class ExamplePyWindowHelper: def __init__(self, plugin, window): print "Plugin created for", window self._window = window self._plugin = plugin def deactivate(self): print "Plugin stopped for", self._window self._window = None self._plugin = None def update_ui(self): # Called whenever the window has been updated (active tab # changed, etc.) print "Plugin update for", self._window class ExamplePyPlugin(gedit.Plugin): def __init__(self): gedit.Plugin.__init__(self) self._instances = {} def activate(self, window): self._instances[window] = ExamplePyWindowHelper(self, window) def deactivate(self, window): self._instances[window].deactivate() del self._instances[window] def update_ui(self, window): 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.

Writing Gedit Plugins in Python

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. from gettext import gettext as _ import gtk import gedit # Menu item example, insert a new item in the Tools menu ui_str = """ """ class ExamplePyWindowHelper: def __init__(self, plugin, window): self._window = window self._plugin = plugin # Insert menu items self._insert_menu() def deactivate(self): # Remove any installed menu items self._remove_menu() self._window = None self._plugin = None self._action_group = None def _insert_menu(self): # Get the GtkUIManager manager = self._window.get_ui_manager() # Create a new action group self._action_group = gtk.ActionGroup("ExamplePyPluginActions") self._action_group.add_actions([("ExamplePy", None, _("Clear document"), None, _("Clear the document"), self.on_clear_document_activate)]) # Insert the action group manager.insert_action_group(self._action_group, -1) # Merge the UI self._ui_id = manager.add_ui_from_string(ui_str) def _remove_menu(self): # Get the GtkUIManager manager = self._window.get_ui_manager() # Remove the ui

Writing Gedit Plugins in Python manager.remove_ui(self._ui_id) # Remove the action group manager.remove_action_group(self._action_group) # Make sure the manager updates manager.ensure_update() def update_ui(self): self._action_group.set_sensitive(self._window.get_active_document() != None) # Menu activate handlers def on_clear_document_activate(self, action): doc = self._window.get_active_document() if not doc: return doc.set_text('') I 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

Writing Gedit Plugins in Python 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 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: import dl class Enchant(object): def __init__(self, lang='en_US'): try: self.library = dl.open('libenchant.so') broker = self.library.call('enchant_broker_init') self.dict = self.library.call( 'enchant_broker_request_dict', broker, lang) except dl.error: self.incorrect_spelling = lambda x: False def incorrect_spelling(self, word): return bool(self.library.call('enchant_dict_check', self.dict, word, len(word)))

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

Writing Gedit Plugins in Python 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.  

              

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)

gedit.Panel 

add_item(item, "name", image) : Adds an item (a gtkWidget) to the panel. image is the icon (usually a gtk.Image).

Writing Gedit Plugins in Python

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_interal(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_dispaly() : 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 defaultencoding). 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`_)

Writing Gedit Plugins in Python 

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)'