Updated Spanish translation
[evolution.git] / e-util / e-plugin-ui.c
blobe875b5aef4b268f5eb9369c8edeffc717dc162ec
1 /*
2 * This program is free software; you can redistribute it and/or modify it
3 * under the terms of the GNU Lesser General Public License as published by
4 * the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful, but
7 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
9 * for more details.
11 * You should have received a copy of the GNU Lesser General Public License
12 * along with this program; if not, see <http://www.gnu.org/licenses/>.
14 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
17 #ifdef HAVE_CONFIG_H
18 #include <config.h>
19 #endif
21 #include "e-plugin-ui.h"
23 #include <string.h>
25 #define E_PLUGIN_UI_HOOK_GET_PRIVATE(obj) \
26 (G_TYPE_INSTANCE_GET_PRIVATE \
27 ((obj), E_TYPE_PLUGIN_UI_HOOK, EPluginUIHookPrivate))
29 #define E_PLUGIN_UI_DEFAULT_FUNC "e_plugin_ui_init"
30 #define E_PLUGIN_UI_HOOK_CLASS_ID "org.gnome.evolution.ui:1.0"
32 struct _EPluginUIHookPrivate {
34 /* Table of GtkUIManager ID's to UI definitions.
36 * For example:
38 * <hook class="org.gnome.evolution.ui:1.0">
39 * <ui-manager id="org.gnome.evolution.foo">
40 * ... UI definition ...
41 * </ui-manager>
42 * </hook>
44 * Results in:
46 * g_hash_table_insert (
47 * ui_definitions,
48 * "org.gnome.evolution.foo",
49 * "... UI definition ...");
51 * See http://library.gnome.org/devel/gtk/unstable/GtkUIManager.html
52 * for more information about UI definitions. Note: the <ui> tag is
53 * optional.
55 GHashTable *ui_definitions;
57 /* Table of GtkUIManager ID's to callback function names.
59 * This stores the optional "callback" attribute in the <ui-manager>
60 * element. If not specified, it defaults to "e_plugin_ui_init".
62 * This is useful when extending the UI of multiple GtkUIManager IDs
63 * from a single plugin.
65 * For example:
67 * <hook class="org.gnome.evolution.ui:1.0">
68 * <ui-manager id="org.gnome.evolution.foo" callback="init_foo">
69 * ...
70 * </ui-manager>
71 * <ui-manager id="org.gnome.evolution.bar" callback="init_bar">
72 * ...
73 * </ui-manager>
74 * </hook>
76 * Results in:
78 * g_hash_table_insert (
79 * callbacks, "org.gnome.evolution.foo", "init_foo");
81 * g_hash_table_insert (
82 * callbacks, "org.gnome.evolution.bar", "init_bar");
84 GHashTable *callbacks;
86 /* The registry is the heart of EPluginUI. It tracks GtkUIManager
87 * instances, GtkUIManager IDs, and UI merge IDs as a hash table of
88 * hash tables:
90 * GtkUIManager instance -> GtkUIManager ID -> UI Merge ID
92 * A GtkUIManager instance and ID form a unique key for looking up
93 * UI merge IDs. The reason both are needed is because the same
94 * GtkUIManager instance can be registered under multiple IDs.
96 * This is done primarily to support shell views, which share a
97 * common GtkUIManager instance for a particular shell window.
98 * Each shell view registers the same GtkUIManager instance under
99 * a unique ID:
101 * "org.gnome.evolution.mail" }
102 * "org.gnome.evolution.contacts" } aliases for a common
103 * "org.gnome.evolution.calendar" } GtkUIManager instance
104 * "org.gnome.evolution.memos" }
105 * "org.gnome.evolution.tasks" }
107 * Note: The shell window also registers the same GtkUIManager
108 * instance as "org.gnome.evolution.shell".
110 * This way, plugins that extend a shell view's UI will follow the
111 * merging and unmerging of the shell view automatically.
113 * The presence or absence of GtkUIManager IDs in the registry is
114 * significant. Presence of a (instance, ID) pair indicates that
115 * UI manager is active, absence indicates inactive. Furthermore,
116 * a non-zero merge ID for an active UI manager indicates the
117 * plugin is enabled. Zero indicates disabled.
119 * Here's a quick scenario to illustrate:
121 * Suppose we have a plugin that extends the mail shell view UI.
122 * Its EPlugin definition file has this section:
124 * <hook class="org.gnome.evolution.ui:1.0">
125 * <ui-manager id="org.gnome.evolution.mail">
126 * ... UI definition ...
127 * </ui-manager>
128 * </hook>
130 * The plugin is enabled and the active shell view is "mail".
131 * Let "ManagerA" denote the common GtkUIManager instance for
132 * this shell window. Here's what happens to the registry as
133 * the user performs various actions;
135 * - Initial State Merge ID
137 * { "ManagerA", { "org.gnome.evolution.mail", 3 } }
139 * - User Disables the Plugin
141 * { "ManagerA", { "org.gnome.evolution.mail", 0 } }
143 * - User Enables the Plugin
145 * { "ManagerA", { "org.gnome.evolution.mail", 4 } }
147 * - User Switches to Calendar View
149 * { "ManagerA", { } }
151 * - User Disables the Plugin
153 * { "ManagerA", { } }
155 * - User Switches to Mail View
157 * { "ManagerA", { "org.gnome.evolution.mail", 0 } }
159 * - User Enables the Plugin
161 * { "ManagerA", { "org.gnome.evolution.mail", 5 } }
163 GHashTable *registry;
166 G_DEFINE_TYPE (
167 EPluginUIHook,
168 e_plugin_ui_hook,
169 E_TYPE_PLUGIN_HOOK)
171 static void
172 plugin_ui_hook_unregister_manager (EPluginUIHook *hook,
173 GtkUIManager *ui_manager)
175 GHashTable *registry;
177 /* Note: Manager may already be finalized. */
178 registry = hook->priv->registry;
179 g_hash_table_remove (registry, ui_manager);
182 static void
183 plugin_ui_hook_register_manager (EPluginUIHook *hook,
184 GtkUIManager *ui_manager,
185 const gchar *id,
186 gpointer user_data)
188 EPlugin *plugin;
189 EPluginUIInitFunc func;
190 GHashTable *registry;
191 GHashTable *hash_table;
192 const gchar *func_name;
194 plugin = ((EPluginHook *) hook)->plugin;
196 hash_table = hook->priv->callbacks;
197 func_name = g_hash_table_lookup (hash_table, id);
199 if (func_name == NULL)
200 func_name = E_PLUGIN_UI_DEFAULT_FUNC;
202 func = e_plugin_get_symbol (plugin, func_name);
204 if (func == NULL) {
205 g_critical (
206 "Plugin \"%s\" is missing a function named %s()",
207 plugin->name, func_name);
208 return;
211 /* Pass the manager and user_data to the plugin's callback function.
212 * The plugin should install whatever GtkActions and GtkActionGroups
213 * are neccessary to implement the actions in its UI definition. */
214 if (!func (ui_manager, user_data))
215 return;
217 g_object_weak_ref (
218 G_OBJECT (ui_manager), (GWeakNotify)
219 plugin_ui_hook_unregister_manager, hook);
221 registry = hook->priv->registry;
222 hash_table = g_hash_table_lookup (registry, ui_manager);
224 if (hash_table == NULL) {
225 hash_table = g_hash_table_new_full (
226 g_str_hash, g_str_equal,
227 (GDestroyNotify) g_free,
228 (GDestroyNotify) NULL);
229 g_hash_table_insert (registry, ui_manager, hash_table);
233 static guint
234 plugin_ui_hook_merge_ui (EPluginUIHook *hook,
235 GtkUIManager *ui_manager,
236 const gchar *id)
238 GHashTable *hash_table;
239 const gchar *ui_definition;
240 guint merge_id;
241 GError *error = NULL;
243 hash_table = hook->priv->ui_definitions;
244 ui_definition = g_hash_table_lookup (hash_table, id);
245 g_return_val_if_fail (ui_definition != NULL, 0);
247 merge_id = gtk_ui_manager_add_ui_from_string (
248 ui_manager, ui_definition, -1, &error);
250 if (error != NULL) {
251 g_warning ("%s", error->message);
252 g_error_free (error);
255 return merge_id;
258 static void
259 plugin_ui_enable_manager (EPluginUIHook *hook,
260 GtkUIManager *ui_manager,
261 const gchar *id)
263 GHashTable *hash_table;
264 GHashTable *ui_definitions;
265 GList *keys;
267 hash_table = hook->priv->registry;
268 hash_table = g_hash_table_lookup (hash_table, ui_manager);
270 if (hash_table == NULL)
271 return;
273 if (id != NULL)
274 keys = g_list_prepend (NULL, (gpointer) id);
275 else
276 keys = g_hash_table_get_keys (hash_table);
278 ui_definitions = hook->priv->ui_definitions;
280 while (keys != NULL) {
281 guint merge_id;
282 gpointer data;
284 id = keys->data;
285 keys = g_list_delete_link (keys, keys);
287 if (g_hash_table_lookup (ui_definitions, id) == NULL)
288 continue;
290 data = g_hash_table_lookup (hash_table, id);
291 merge_id = GPOINTER_TO_UINT (data);
293 if (merge_id > 0)
294 continue;
296 if (((EPluginHook *) hook)->plugin->enabled)
297 merge_id = plugin_ui_hook_merge_ui (
298 hook, ui_manager, id);
300 /* Merge ID will be 0 on error, which is what we want. */
301 data = GUINT_TO_POINTER (merge_id);
302 g_hash_table_insert (hash_table, g_strdup (id), data);
306 static void
307 plugin_ui_disable_manager (EPluginUIHook *hook,
308 GtkUIManager *ui_manager,
309 const gchar *id,
310 gboolean remove)
312 GHashTable *hash_table;
313 GHashTable *ui_definitions;
314 GList *keys;
316 hash_table = hook->priv->registry;
317 hash_table = g_hash_table_lookup (hash_table, ui_manager);
319 if (hash_table == NULL)
320 return;
322 if (id != NULL)
323 keys = g_list_prepend (NULL, (gpointer) id);
324 else
325 keys = g_hash_table_get_keys (hash_table);
327 ui_definitions = hook->priv->ui_definitions;
329 while (keys != NULL) {
330 guint merge_id;
331 gpointer data;
333 id = keys->data;
334 keys = g_list_delete_link (keys, keys);
336 if (g_hash_table_lookup (ui_definitions, id) == NULL)
337 continue;
339 data = g_hash_table_lookup (hash_table, id);
340 merge_id = GPOINTER_TO_UINT (data);
342 /* Merge ID could be 0 if the plugin is disabled. */
343 if (merge_id > 0) {
344 gtk_ui_manager_remove_ui (ui_manager, merge_id);
345 gtk_ui_manager_ensure_update (ui_manager);
348 if (remove)
349 g_hash_table_remove (hash_table, id);
350 else
351 g_hash_table_insert (hash_table, g_strdup (id), NULL);
355 static void
356 plugin_ui_enable_hook (EPluginUIHook *hook)
358 GHashTable *hash_table;
359 GHashTableIter iter;
360 gpointer key;
362 /* Enable all GtkUIManagers for this hook. */
364 hash_table = hook->priv->registry;
365 g_hash_table_iter_init (&iter, hash_table);
367 while (g_hash_table_iter_next (&iter, &key, NULL)) {
368 GtkUIManager *ui_manager = key;
369 plugin_ui_enable_manager (hook, ui_manager, NULL);
373 static void
374 plugin_ui_disable_hook (EPluginUIHook *hook)
376 GHashTable *hash_table;
377 GHashTableIter iter;
378 gpointer key;
380 /* Disable all GtkUIManagers for this hook. */
382 hash_table = hook->priv->registry;
383 g_hash_table_iter_init (&iter, hash_table);
385 while (g_hash_table_iter_next (&iter, &key, NULL)) {
386 GtkUIManager *ui_manager = key;
387 plugin_ui_disable_manager (hook, ui_manager, NULL, FALSE);
391 static void
392 plugin_ui_hook_finalize (GObject *object)
394 EPluginUIHookPrivate *priv;
395 GHashTableIter iter;
396 gpointer ui_manager;
398 priv = E_PLUGIN_UI_HOOK_GET_PRIVATE (object);
400 /* Remove weak reference callbacks to GtkUIManagers. */
401 g_hash_table_iter_init (&iter, priv->registry);
402 while (g_hash_table_iter_next (&iter, &ui_manager, NULL))
403 g_object_weak_unref (
404 G_OBJECT (ui_manager), (GWeakNotify)
405 plugin_ui_hook_unregister_manager, object);
407 g_hash_table_destroy (priv->ui_definitions);
408 g_hash_table_destroy (priv->callbacks);
409 g_hash_table_destroy (priv->registry);
411 /* Chain up to parent's dispose() method. */
412 G_OBJECT_CLASS (e_plugin_ui_hook_parent_class)->dispose (object);
415 static gint
416 plugin_ui_hook_construct (EPluginHook *hook,
417 EPlugin *plugin,
418 xmlNodePtr node)
420 EPluginUIHookPrivate *priv;
422 priv = E_PLUGIN_UI_HOOK_GET_PRIVATE (hook);
424 /* XXX The EPlugin should be a property of EPluginHookClass.
425 * Then it could be passed directly to g_object_new() and
426 * we wouldn't have to chain up here. */
428 /* Chain up to parent's construct() method. */
429 E_PLUGIN_HOOK_CLASS (e_plugin_ui_hook_parent_class)->
430 construct (hook, plugin, node);
432 for (node = xmlFirstElementChild (node); node != NULL;
433 node = xmlNextElementSibling (node)) {
435 xmlNodePtr child;
436 xmlBufferPtr buffer;
437 GString *content;
438 const gchar *temp;
439 gchar *callback;
440 gchar *id;
442 if (strcmp ((gchar *) node->name, "ui-manager") != 0)
443 continue;
445 id = e_plugin_xml_prop (node, "id");
446 if (id == NULL) {
447 g_warning ("<ui-manager> requires 'id' property");
448 continue;
451 callback = e_plugin_xml_prop (node, "callback");
452 if (callback != NULL)
453 g_hash_table_insert (
454 priv->callbacks,
455 g_strdup (id), callback);
457 content = g_string_sized_new (1024);
459 /* Extract the XML content below <ui-manager> */
460 buffer = xmlBufferCreate ();
461 for (child = node->children; child != NULL; child = child->next) {
462 xmlNodeDump (buffer, node->doc, child, 2, 1);
463 temp = (const gchar *) xmlBufferContent (buffer);
464 g_string_append (content, temp);
467 g_hash_table_insert (
468 priv->ui_definitions,
469 id, g_string_free (content, FALSE));
471 xmlBufferFree (buffer);
474 return 0;
477 static void
478 plugin_ui_hook_enable (EPluginHook *hook,
479 gint state)
481 if (state)
482 plugin_ui_enable_hook (E_PLUGIN_UI_HOOK (hook));
483 else
484 plugin_ui_disable_hook (E_PLUGIN_UI_HOOK (hook));
487 static void
488 e_plugin_ui_hook_class_init (EPluginUIHookClass *class)
490 GObjectClass *object_class;
491 EPluginHookClass *plugin_hook_class;
493 g_type_class_add_private (class, sizeof (EPluginUIHookPrivate));
495 object_class = G_OBJECT_CLASS (class);
496 object_class->finalize = plugin_ui_hook_finalize;
498 plugin_hook_class = E_PLUGIN_HOOK_CLASS (class);
499 plugin_hook_class->id = E_PLUGIN_UI_HOOK_CLASS_ID;
500 plugin_hook_class->construct = plugin_ui_hook_construct;
501 plugin_hook_class->enable = plugin_ui_hook_enable;
504 static void
505 e_plugin_ui_hook_init (EPluginUIHook *hook)
507 GHashTable *ui_definitions;
508 GHashTable *callbacks;
509 GHashTable *registry;
511 ui_definitions = g_hash_table_new_full (
512 g_str_hash, g_str_equal,
513 (GDestroyNotify) g_free,
514 (GDestroyNotify) g_free);
516 callbacks = g_hash_table_new_full (
517 g_str_hash, g_str_equal,
518 (GDestroyNotify) g_free,
519 (GDestroyNotify) g_free);
521 registry = g_hash_table_new_full (
522 g_direct_hash, g_direct_equal,
523 (GDestroyNotify) NULL,
524 (GDestroyNotify) g_hash_table_destroy);
526 hook->priv = E_PLUGIN_UI_HOOK_GET_PRIVATE (hook);
527 hook->priv->ui_definitions = ui_definitions;
528 hook->priv->callbacks = callbacks;
529 hook->priv->registry = registry;
532 void
533 e_plugin_ui_register_manager (GtkUIManager *ui_manager,
534 const gchar *id,
535 gpointer user_data)
537 GSList *plugin_list;
539 g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
540 g_return_if_fail (id != NULL);
542 /* Loop over all installed plugins. */
543 plugin_list = e_plugin_list_plugins ();
544 while (plugin_list != NULL) {
545 EPlugin *plugin = plugin_list->data;
546 GSList *iter;
548 plugin_list = g_slist_remove (plugin_list, plugin);
550 /* Look for hooks of type EPluginUIHook. */
551 for (iter = plugin->hooks; iter != NULL; iter = iter->next) {
552 EPluginUIHook *hook = iter->data;
553 GHashTable *hash_table;
555 if (!E_IS_PLUGIN_UI_HOOK (hook))
556 continue;
558 hash_table = hook->priv->ui_definitions;
560 /* Check if the hook has a UI definition
561 * for the GtkUIManager being registered. */
562 if (g_hash_table_lookup (hash_table, id) == NULL)
563 continue;
565 /* Register the manager with the hook. */
566 plugin_ui_hook_register_manager (
567 hook, ui_manager, id, user_data);
570 g_object_unref (plugin);
574 void
575 e_plugin_ui_enable_manager (GtkUIManager *ui_manager,
576 const gchar *id)
578 GSList *plugin_list;
580 g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
581 g_return_if_fail (id != NULL);
583 /* Loop over all installed plugins. */
584 plugin_list = e_plugin_list_plugins ();
585 while (plugin_list != NULL) {
586 EPlugin *plugin = plugin_list->data;
587 GSList *iter;
589 plugin_list = g_slist_remove (plugin_list, plugin);
591 /* Look for hooks of type EPluginUIHook. */
592 for (iter = plugin->hooks; iter != NULL; iter = iter->next) {
593 EPluginUIHook *hook = iter->data;
595 if (!E_IS_PLUGIN_UI_HOOK (hook))
596 continue;
598 plugin_ui_enable_manager (hook, ui_manager, id);
601 g_object_unref (plugin);
605 void
606 e_plugin_ui_disable_manager (GtkUIManager *ui_manager,
607 const gchar *id)
609 GSList *plugin_list;
611 g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
612 g_return_if_fail (id != NULL);
614 /* Loop over all installed plugins. */
615 plugin_list = e_plugin_list_plugins ();
616 while (plugin_list != NULL) {
617 EPlugin *plugin = plugin_list->data;
618 GSList *iter;
620 plugin_list = g_slist_remove (plugin_list, plugin);
622 /* Look for hooks of type EPluginUIHook. */
623 for (iter = plugin->hooks; iter != NULL; iter = iter->next) {
624 EPluginUIHook *hook = iter->data;
626 if (!E_IS_PLUGIN_UI_HOOK (hook))
627 continue;
629 plugin_ui_disable_manager (hook, ui_manager, id, TRUE);
632 g_object_unref (plugin);