GNOME Bugzilla – Bug 525241
Extend EPlugin to support GtkUIManager
Last modified: 2013-09-13 00:58:47 UTC
My 'mbarnes_composer' branch [1] is just about ready to merge into trunk. The only outstanding issue I'm aware of is that the EPlugin framework needs to be extended to support GtkUIManager interface definitions in addition to BonoboUI interface definitions. Consequently, once the merge is complete, all composer plugins will be broken until this issue is resolved. I'm still learning the in's and out's of the plugin framework, but from what I gather so far I think we just need a new object class roughly similar to EMenu. Since GtkUIManager is capable of managing menus, toolbars, /AND/ popups, perhaps EInterface would be a suitable name? Timeline: I don't think we can realistically expect to resolve this in time for 2.23.1, but I'd still like to get the composer branch merged before 2.23.1 so it gets as much user testing as possible. I propose we ship 2.23.1 with composer plugins broken, and then get the EPlugin framework extended and fix the plugin regressions in time for 2.23.2 (or 2.23.3 at the latest). Setting the severity to 'critical' because of the time constraints. [1] http://www.go-evolution.org/New_Composer
Here's the EPlugin extension I mentioned in this week's meeting. The extension is called "EPluginUI" and is intended to eventually replace the BonoboUI-based EMenu and EPopup classes. The extension consists of a new hook class called EPluginUIHook and a function to register GtkUIManager objects with EPluginUI. I'm hoping this will prove sufficiently powerful so as to avoid the need for subclassing. I'm also avoiding using "targets" and "flags" (as EPlugin defines them) because I believe that, if designed properly, a GtkUIManager's action groups should provide sufficient context to plugins. That said, I've only actually converted one fairly simple plugin to EPluginUI, so we'll see how it holds up as we convert the rest. Best way to describe how this works is to walk you through the changes to the Face plugin. We start with the XML specification in org-gnome-face.eplug: <hook class="org.gnome.evolution.ui:1.0"> <ui-manager id="org.gnome.evolution.composer"> <menubar name='main-menu'> <menu action='insert-menu'> <placeholder name="insert-menu-top"> <menuitem action="face"/> </placeholder> </menu> </menubar> </ui-manager> </hook> The EPluginUIHook class identifies itself as "org.gnome.evolution.ui:1.0". Here we're saying we want to provide a UI definition for any GtkUIManager instances named "org.gnome.evolution.composer". Then what follows is the plugin's UI definition as defined in [1]. (The <ui> tags are optional.) Meanwhile in the composer code, new EMsgComposer instances register themselves with EPluginUI as part of their initialization: e_plugin_ui_register_manager ( "org.gnome.evolution.composer", manager, composer); EPluginUI does all the bookkeeping. It sees that the Face plugin has a UI definition for this GtkUIManager instance, and if the plugin is enabled it immediately merges the UI. It will continue to track the GtkUIManager instance until it's finalized, and take care of unmerging and re-merging Face's UI definition when the user enables or disables the plugin. Also notice that it passes the "composer" (EMsgComposer instance) as a "user data" value. Back to the Face plugin, here we see some new initialization code: static void action_face_cb (GtkAction *action, EMsgComposer *composer) { /* plugin logic goes here */ } static GtkActionEntry entries[] = { { "face", /* action_name */ NULL, /* stock_id */ N_("_Face"), /* label */ NULL, /* accelerator */ NULL, /* tooltip */ G_CALLBACK (action_face_cb) } }; gboolean e_plugin_ui_init (GtkUIManager *manager, EMsgComposer *composer) { GtkhtmlEditor *editor; editor = GTKHTML_EDITOR (composer); /* Add actions to the "composer" action group. */ gtk_action_group_add_actions ( gtkhtml_editor_get_action_group (editor, "composer"), entries, G_N_ELEMENTS (entries), composer); return TRUE; } e_plugin_ui_init() is a special function that gets invoked each time a new GtkUIManager instance we're interested in registers itself with EPluginUI. Notice we get the composer instance that was passed as "user data" to e_plugin_ui_register_manager(). The plugin is responsible for implementing the actions mentioned in its UI definition. The Face plugin adds a menu item that triggers the "face" action, so we define such an action and add it to the "composer" action group. Then we return TRUE to indicate initialization was successful. Defining the right action groups is really important. Plugins can create their own action groups to add to the UI manager if they want, but most of the time you'll want to add actions to predefined action groups in whatever widget or data structure you get passed. The composer, for example, has an action group called "html" which contains all the actions that are only sensitive in HTML mode. If we were writing a plugin that added some new capability to HTML mode, adding our actions to the "html" action group would cause them to be automatically desensitized in text mode. Get the idea? [1] http://library.gnome.org/devel/gtk/unstable/GtkUIManager.html
Created attachment 109797 [details] [review] EPluginUI + Face plugin I had to extend the EPluginClass slightly by adding a get_symbol() method, because the EPluginLibFunc function signature was inadequate for the e_plugin_ui_init() function. e_plugin_get_symbol() just looks up a symbol name from the plugin and returns it as a void pointer. It returns NULL if the EPlugin subclass does not implement the get_symbol() method. But EPluginLib does.
Created attachment 109799 [details] [review] Revised patch Fixes an error in the eplug file.
Sankar and I agreed to commit this with the understanding that we can revert or revise it if there are issues with the design. Resolving as FIXED but will reopen if further discussion is needed. Committed to trunk (revision 35485).