How to create a flexible editor
1. Introduction
This tutorial explains how to get working most of all PlugBoard components toghether.
This document would like to be a first shot example made step by step for all parts of, so its target are mainly new comers to PlugBoard.
However, before using this framework then understand this tutorial, you must know the zope.interface and setuptools libraries.
Note
This tutorial uses some components which need to be able to access Python library path
(for instance site-packages
).
We will use an XML config and PyGTK 2.8
2. Creating the project
Now let's create the skeleton directory structure:
plugboardctl.py create PlugEditor
Notice how this script created for you the setup.py
file specifying inside the
current version of PlugBoard as dependency and a starting entrypoint
string for our application plugins.
3. The main file
PlugBoard, as described in the architecture document, offers
an IApplication
interface which is the principal component of an application.
Its implementation is found at plugboard.application.Application
, so write this simply code:
plugeditor/main.py from plugboard import application def main(): app = application.Application()
Now we created our IApplication
implementation instance. After that, we said in setup.py
there's a string which defines a Python path for plugin entrypoints, for instance plugeditor.plugins
. So, since we are using a setuptools-based plugin management to find installed plugins, install the IPluginResource
as follows:
plugeditor/main.py from plugboard import application, plugin def main(): app = application.Application() pr = plugin.SetuptoolsPluginResource(app, 'plugeditor.plugins') pr.refresh()
SetuptoolsPluginResource
is a builtin implementation of IPluginResource
, which accepts as the first argument the plugin entrypoint path. The method call pr.refresh()
is used the retrieve the list of plugins from the given entrypoint path.
Note
The refresh(entrypoint_path=None)
method accepts a keyword argument for specifying the entrypoint path for plugins. If no path has been given, it uses the one specified at the initialization.
It also clears the plugins list before retrieving them again from the given entrypoint path.
4. The main window plugin
4.1 The interface
Ok, now let's create the main window interface of the editor:
plugeditor/plugins/interfaces.py from plugboard.plugin import IPlugin import gtk class IMainWindowPlugin(IPlugin): def get_window(): """ Returns the main window widget """ get_window.return_type = gtk.Window
Creating an interface for the main window plugin let the application to be very flexible for switching from an implementation to another.
In the code above, we declare a class deriving from IPlugin
which contains common methods and informations regarding plugins. The get_window.return_type = gtk.Window
directive is just an information for developers who want to create an IMainWindowPlugin
implementation.
Note
If you don't know which declaring component of zope.interface the assignment to get_window.return_type
is, please read the zope.interface documentation. This is called tag, used to add extra informations over an attribute or method.
4.2 The implementation
Now, we need to implement the previous created IMainWindowPlugin
interface:
plugeditor/plugins/core.py from plugboard import plugin from zope.interface import implements import interfaces, gtk class MainWindowPlugin(plugin.Plugin): implements(interfaces.IMainWindowPlugin) def __init__(self, application): self.widget = gtk.Window() self.widget.set_title("PlugEditor") def load(self, context): self.widget.show_all() def get_widget(self): return self.widget def set_child(
The initialization just set the instance variable widget
value, while on loading the window will be shown.
Note
If you don't know how a plugin is loaded into the application, please read the PlugBoard architecture documentation
Once we done this, we should add this new plugin to the entrypoint, so that SetuptoolsPluginResource
can find it:
setup.py [...] entry_points=""" [plugeditor.plugins] MainWindowPlugin = plugeditor.plugins.core:MainWindowPlugin """, [...]
The first name MainWindowPlugin
is not important for the purposes of SetuptoolsPluginResource
, however it could be a good idea to keep the name always clear because of other IPluginResource
implementations for setuptools which could need it.
5. The context resource
The IContextResource
is used to load specific plugins into a specific context. Each context contain a list of plugins which must be listed in IPluginResource
. So, according to this concept, let's use the XMLContextPlugin
implementation as follows:
plugeditor/main.py from plugboard import application, plugin, context import user, os from xml.dom import minidom import gtk config_file = os.path.join(user.home, '.plugeditor') def get_config(): if not os.path.exists(config_file): file(config_file, 'w').write('<plugeditor />') try: return minidom.parse(config_file).documentElement except: pass def main(): app = application.Application() pr = plugin.SetuptoolsPluginResource(app, 'plugeditor.plugins') pr.refresh() config = get_config() if not config: print >> sys.stderr, "No valid configuration file as been found" cr = context.XMLContextResource(app, config) cr.refresh() cr['default'].load() gtk.main()
In the first lines of the code, we simply get the document element (plugeditor) of the XML config file, while in the main()
function we pass to the XMLContextResource
the got xml element.
The XML document should have the following structure:
~/.plugboard <plugeditor> <context name='default'> <plugin path='plugeditor.plugins.core.MainWindowPlugin' /> </context> </plugeditor>
Notice how we loaded the default context in the code. Logically, everything should be as much dynamic as possible with an appropriate UI to let the user define both contexts and plugins.
Note
XMLContextResource
initialization let you specify what names must be used for both elements and attributes; here's the implementation:
def __init__(self, application, dom_element=None, context_element='context', context_name='name', plugin_element='plugin', plugin_path='path')
In the XML config, we defined the previous created plugin to be inserted in the context default then calling the load
method will initialize, preload and load the requested plugin.
This kind of structure, allows plugins to have different settings for each context, then each plugin can be present in many contexts.
6. Run the example
If you followed the above instructions and everything has gone well, you can run the example then a window should appear on the screen:
(Privileged console) python setup.py develop
(Python interpreter) >>> from plugeditor import main >>> main.main()
Notice how we need to install PlugEditor in the Python path (trough the develop
command) because of the setuptools-based plugin. This behavior can change if you'll implement a different IPluginResource
for your application without using setuptools.
After we need to import modules and start manually the application; naturally this is done because we didn't provide any script for this task.
If you want to see loaded plugins/contexts just add print list(pr.get_plugins()), list(cr.get_contexts())
in main()
.
Top^ | All contents by deelan, some rights reserved | Page last modified Feb 26, 2006