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
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)
21 #include "e-plugin-ui.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.
38 * <hook class="org.gnome.evolution.ui:1.0">
39 * <ui-manager id="org.gnome.evolution.foo">
40 * ... UI definition ...
46 * g_hash_table_insert (
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
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.
67 * <hook class="org.gnome.evolution.ui:1.0">
68 * <ui-manager id="org.gnome.evolution.foo" callback="init_foo">
71 * <ui-manager id="org.gnome.evolution.bar" callback="init_bar">
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
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
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 ...
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
;
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
);
183 plugin_ui_hook_register_manager (EPluginUIHook
*hook
,
184 GtkUIManager
*ui_manager
,
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
);
206 "Plugin \"%s\" is missing a function named %s()",
207 plugin
->name
, func_name
);
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
))
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
);
234 plugin_ui_hook_merge_ui (EPluginUIHook
*hook
,
235 GtkUIManager
*ui_manager
,
238 GHashTable
*hash_table
;
239 const gchar
*ui_definition
;
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
);
251 g_warning ("%s", error
->message
);
252 g_error_free (error
);
259 plugin_ui_enable_manager (EPluginUIHook
*hook
,
260 GtkUIManager
*ui_manager
,
263 GHashTable
*hash_table
;
264 GHashTable
*ui_definitions
;
267 hash_table
= hook
->priv
->registry
;
268 hash_table
= g_hash_table_lookup (hash_table
, ui_manager
);
270 if (hash_table
== NULL
)
274 keys
= g_list_prepend (NULL
, (gpointer
) id
);
276 keys
= g_hash_table_get_keys (hash_table
);
278 ui_definitions
= hook
->priv
->ui_definitions
;
280 while (keys
!= NULL
) {
285 keys
= g_list_delete_link (keys
, keys
);
287 if (g_hash_table_lookup (ui_definitions
, id
) == NULL
)
290 data
= g_hash_table_lookup (hash_table
, id
);
291 merge_id
= GPOINTER_TO_UINT (data
);
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
);
307 plugin_ui_disable_manager (EPluginUIHook
*hook
,
308 GtkUIManager
*ui_manager
,
312 GHashTable
*hash_table
;
313 GHashTable
*ui_definitions
;
316 hash_table
= hook
->priv
->registry
;
317 hash_table
= g_hash_table_lookup (hash_table
, ui_manager
);
319 if (hash_table
== NULL
)
323 keys
= g_list_prepend (NULL
, (gpointer
) id
);
325 keys
= g_hash_table_get_keys (hash_table
);
327 ui_definitions
= hook
->priv
->ui_definitions
;
329 while (keys
!= NULL
) {
334 keys
= g_list_delete_link (keys
, keys
);
336 if (g_hash_table_lookup (ui_definitions
, id
) == NULL
)
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. */
344 gtk_ui_manager_remove_ui (ui_manager
, merge_id
);
345 gtk_ui_manager_ensure_update (ui_manager
);
349 g_hash_table_remove (hash_table
, id
);
351 g_hash_table_insert (hash_table
, g_strdup (id
), NULL
);
356 plugin_ui_enable_hook (EPluginUIHook
*hook
)
358 GHashTable
*hash_table
;
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
);
374 plugin_ui_disable_hook (EPluginUIHook
*hook
)
376 GHashTable
*hash_table
;
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
);
392 plugin_ui_hook_finalize (GObject
*object
)
394 EPluginUIHookPrivate
*priv
;
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
);
416 plugin_ui_hook_construct (EPluginHook
*hook
,
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
)) {
442 if (strcmp ((gchar
*) node
->name
, "ui-manager") != 0)
445 id
= e_plugin_xml_prop (node
, "id");
447 g_warning ("<ui-manager> requires 'id' property");
451 callback
= e_plugin_xml_prop (node
, "callback");
452 if (callback
!= NULL
)
453 g_hash_table_insert (
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
);
478 plugin_ui_hook_enable (EPluginHook
*hook
,
482 plugin_ui_enable_hook (E_PLUGIN_UI_HOOK (hook
));
484 plugin_ui_disable_hook (E_PLUGIN_UI_HOOK (hook
));
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
;
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
;
533 e_plugin_ui_register_manager (GtkUIManager
*ui_manager
,
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
;
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
))
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
)
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
);
575 e_plugin_ui_enable_manager (GtkUIManager
*ui_manager
,
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
;
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
))
598 plugin_ui_enable_manager (hook
, ui_manager
, id
);
601 g_object_unref (plugin
);
606 e_plugin_ui_disable_manager (GtkUIManager
*ui_manager
,
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
;
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
))
629 plugin_ui_disable_manager (hook
, ui_manager
, id
, TRUE
);
632 g_object_unref (plugin
);