evolution-3.6.4/e-util/e-plugin-ui.c

No issues found

  1 /*
  2  * This program is free software; you can redistribute it and/or
  3  * modify it under the terms of the GNU Lesser General Public
  4  * License as published by the Free Software Foundation; either
  5  * version 2 of the License, or (at your option) version 3.
  6  *
  7  * This program is distributed in the hope that it will be useful,
  8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 10  * Lesser General Public License for more details.
 11  *
 12  * You should have received a copy of the GNU Lesser General Public
 13  * License along with the program; if not, see <http://www.gnu.org/licenses/>
 14  *
 15  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 16  */
 17 
 18 #ifdef HAVE_CONFIG_H
 19 #include <config.h>
 20 #endif
 21 
 22 #include "e-plugin-ui.h"
 23 
 24 #include "e-util.h"
 25 #include "e-ui-manager.h"
 26 
 27 #include <string.h>
 28 
 29 #define E_PLUGIN_UI_HOOK_GET_PRIVATE(obj) \
 30 	(G_TYPE_INSTANCE_GET_PRIVATE \
 31 	((obj), E_TYPE_PLUGIN_UI_HOOK, EPluginUIHookPrivate))
 32 
 33 #define E_PLUGIN_UI_DEFAULT_FUNC	"e_plugin_ui_init"
 34 #define E_PLUGIN_UI_HOOK_CLASS_ID	"org.gnome.evolution.ui:1.0"
 35 
 36 struct _EPluginUIHookPrivate {
 37 
 38 	/* Table of GtkUIManager ID's to UI definitions.
 39 	 *
 40 	 * For example:
 41 	 *
 42 	 *     <hook class="org.gnome.evolution.ui:1.0">
 43 	 *       <ui-manager id="org.gnome.evolution.foo">
 44 	 *               ... UI definition ...
 45 	 *       </ui-manager>
 46 	 *     </hook>
 47 	 *
 48 	 * Results in:
 49 	 *
 50 	 *     g_hash_table_insert (
 51 	 *             ui_definitions,
 52 	 *             "org.gnome.evolution.foo",
 53 	 *             "... UI definition ...");
 54 	 *
 55 	 * See http://library.gnome.org/devel/gtk/unstable/GtkUIManager.html
 56 	 * for more information about UI definitions.  Note: the <ui> tag is
 57 	 * optional.
 58 	 */
 59 	GHashTable *ui_definitions;
 60 
 61 	/* Table of GtkUIManager ID's to callback function names.
 62 	 *
 63 	 * This stores the optional "callback" attribute in the <ui-manager>
 64 	 * element.  If not specified, it defaults to "e_plugin_ui_init".
 65 	 *
 66 	 * This is useful when extending the UI of multiple GtkUIManager IDs
 67 	 * from a single plugin.
 68 	 *
 69 	 * For example:
 70 	 *
 71 	 *     <hook class="org.gnome.evolution.ui:1.0">
 72 	 *       <ui-manager id="org.gnome.evolution.foo" callback="init_foo">
 73 	 *         ...
 74 	 *       </ui-manager>
 75 	 *       <ui-manager id="org.gnome.evolution.bar" callback="init_bar">
 76 	 *         ...
 77 	 *       </ui-manager>
 78 	 *     </hook>
 79 	 *
 80 	 * Results in:
 81 	 *
 82 	 *     g_hash_table_insert (
 83 	 *             callbacks, "org.gnome.evolution.foo", "init_foo");
 84 	 *
 85 	 *     g_hash_table_insert (
 86 	 *             callbacks, "org.gnome.evolution.bar", "init_bar");
 87 	 */
 88 	GHashTable *callbacks;
 89 
 90 	/* The registry is the heart of EPluginUI.  It tracks GtkUIManager
 91 	 * instances, GtkUIManager IDs, and UI merge IDs as a hash table of
 92 	 * hash tables:
 93 	 *
 94 	 *     GtkUIManager instance -> GtkUIManager ID -> UI Merge ID
 95 	 *
 96 	 * A GtkUIManager instance and ID form a unique key for looking up
 97 	 * UI merge IDs.  The reason both are needed is because the same
 98 	 * GtkUIManager instance can be registered under multiple IDs.
 99 	 *
100 	 * This is done primarily to support shell views, which share a
101 	 * common GtkUIManager instance for a particular shell window.
102 	 * Each shell view registers the same GtkUIManager instance under
103 	 * a unique ID:
104 	 *
105 	 *     "org.gnome.evolution.mail"      }
106 	 *     "org.gnome.evolution.contacts"  }  aliases for a common
107 	 *     "org.gnome.evolution.calendar"  }  GtkUIManager instance
108 	 *     "org.gnome.evolution.memos"     }
109 	 *     "org.gnome.evolution.tasks"     }
110 	 *
111 	 * Note: The shell window also registers the same GtkUIManager
112 	 *       instance as "org.gnome.evolution.shell".
113 	 *
114 	 * This way, plugins that extend a shell view's UI will follow the
115 	 * merging and unmerging of the shell view automatically.
116 	 *
117 	 * The presence or absence of GtkUIManager IDs in the registry is
118 	 * significant.  Presence of a (instance, ID) pair indicates that
119 	 * UI manager is active, absence indicates inactive.  Furthermore,
120 	 * a non-zero merge ID for an active UI manager indicates the
121 	 * plugin is enabled.  Zero indicates disabled.
122 	 *
123 	 * Here's a quick scenario to illustrate:
124 	 *
125 	 * Suppose we have a plugin that extends the mail shell view UI.
126 	 * Its EPlugin definition file has this section:
127 	 *
128 	 *     <hook class="org.gnome.evolution.ui:1.0">
129 	 *       <ui-manager id="org.gnome.evolution.mail">
130 	 *               ... UI definition ...
131 	 *       </ui-manager>
132 	 *     </hook>
133 	 *
134 	 * The plugin is enabled and the active shell view is "mail".
135 	 * Let "ManagerA" denote the common GtkUIManager instance for
136 	 * this shell window.  Here's what happens to the registry as
137 	 * the user performs various actions;
138 	 *
139 	 *     - Initial State                            Merge ID
140 	 *                                                   V
141 	 *       { "ManagerA", { "org.gnome.evolution.mail", 3 } }
142 	 *
143 	 *     - User Disables the Plugin
144 	 *
145 	 *       { "ManagerA", { "org.gnome.evolution.mail", 0 } }
146 	 *
147 	 *     - User Enables the Plugin
148 	 *
149 	 *       { "ManagerA", { "org.gnome.evolution.mail", 4 } }
150 	 *
151 	 *     - User Switches to Calendar View
152 	 *
153 	 *       { "ManagerA", { } }
154 	 *
155 	 *     - User Disables the Plugin
156 	 *
157 	 *       { "ManagerA", { } }
158 	 *
159 	 *     - User Switches to Mail View
160 	 *
161 	 *       { "ManagerA", { "org.gnome.evolution.mail", 0 } }
162 	 *
163 	 *     - User Enables the Plugin
164 	 *
165 	 *       { "ManagerA", { "org.gnome.evolution.mail", 5 } }
166 	 */
167 	GHashTable *registry;
168 };
169 
170 G_DEFINE_TYPE (
171 	EPluginUIHook,
172 	e_plugin_ui_hook,
173 	E_TYPE_PLUGIN_HOOK)
174 
175 static void
176 plugin_ui_hook_unregister_manager (EPluginUIHook *hook,
177                                    GtkUIManager *ui_manager)
178 {
179 	GHashTable *registry;
180 
181 	/* Note: Manager may already be finalized. */
182 	registry = hook->priv->registry;
183 	g_hash_table_remove (registry, ui_manager);
184 }
185 
186 static void
187 plugin_ui_hook_register_manager (EPluginUIHook *hook,
188                                  GtkUIManager *ui_manager,
189                                  const gchar *id,
190                                  gpointer user_data)
191 {
192 	EPlugin *plugin;
193 	EPluginUIInitFunc func;
194 	GHashTable *registry;
195 	GHashTable *hash_table;
196 	const gchar *func_name;
197 
198 	plugin = ((EPluginHook *) hook)->plugin;
199 
200 	hash_table = hook->priv->callbacks;
201 	func_name = g_hash_table_lookup (hash_table, id);
202 
203 	if (func_name == NULL)
204 		func_name = E_PLUGIN_UI_DEFAULT_FUNC;
205 
206 	func = e_plugin_get_symbol (plugin, func_name);
207 
208 	if (func == NULL) {
209 		g_critical (
210 			"Plugin \"%s\" is missing a function named %s()",
211 			plugin->name, func_name);
212 		return;
213 	}
214 
215 	/* Pass the manager and user_data to the plugin's callback function.
216 	 * The plugin should install whatever GtkActions and GtkActionGroups
217 	 * are neccessary to implement the actions in its UI definition. */
218 	if (!func (ui_manager, user_data))
219 		return;
220 
221 	g_object_weak_ref (
222 		G_OBJECT (ui_manager), (GWeakNotify)
223 		plugin_ui_hook_unregister_manager, hook);
224 
225 	registry = hook->priv->registry;
226 	hash_table = g_hash_table_lookup (registry, ui_manager);
227 
228 	if (hash_table == NULL) {
229 		hash_table = g_hash_table_new_full (
230 			g_str_hash, g_str_equal,
231 			(GDestroyNotify) g_free,
232 			(GDestroyNotify) NULL);
233 		g_hash_table_insert (registry, ui_manager, hash_table);
234 	}
235 }
236 
237 static guint
238 plugin_ui_hook_merge_ui (EPluginUIHook *hook,
239                          GtkUIManager *ui_manager,
240                          const gchar *id)
241 {
242 	GHashTable *hash_table;
243 	const gchar *ui_definition;
244 	guint merge_id;
245 	GError *error = NULL;
246 
247 	hash_table = hook->priv->ui_definitions;
248 	ui_definition = g_hash_table_lookup (hash_table, id);
249 	g_return_val_if_fail (ui_definition != NULL, 0);
250 
251 	if (E_IS_UI_MANAGER (ui_manager))
252 		merge_id = e_ui_manager_add_ui_from_string (
253 			E_UI_MANAGER (ui_manager), ui_definition, &error);
254 	else
255 		merge_id = gtk_ui_manager_add_ui_from_string (
256 			ui_manager, ui_definition, -1, &error);
257 
258 	if (error != NULL) {
259 		g_warning ("%s", error->message);
260 		g_error_free (error);
261 	}
262 
263 	return merge_id;
264 }
265 
266 static void
267 plugin_ui_enable_manager (EPluginUIHook *hook,
268                           GtkUIManager *ui_manager,
269                           const gchar *id)
270 {
271 	GHashTable *hash_table;
272 	GHashTable *ui_definitions;
273 	GList *keys;
274 
275 	hash_table = hook->priv->registry;
276 	hash_table = g_hash_table_lookup (hash_table, ui_manager);
277 
278 	if (hash_table == NULL)
279 		return;
280 
281 	if (id != NULL)
282 		keys = g_list_prepend (NULL, (gpointer) id);
283 	else
284 		keys = g_hash_table_get_keys (hash_table);
285 
286 	ui_definitions = hook->priv->ui_definitions;
287 
288 	while (keys != NULL) {
289 		guint merge_id;
290 		gpointer data;
291 
292 		id = keys->data;
293 		keys = g_list_delete_link (keys, keys);
294 
295 		if (g_hash_table_lookup (ui_definitions, id) == NULL)
296 			continue;
297 
298 		data = g_hash_table_lookup (hash_table, id);
299 		merge_id = GPOINTER_TO_UINT (data);
300 
301 		if (merge_id > 0)
302 			continue;
303 
304 		if (((EPluginHook *) hook)->plugin->enabled)
305 			merge_id = plugin_ui_hook_merge_ui (
306 				hook, ui_manager, id);
307 
308 		/* Merge ID will be 0 on error, which is what we want. */
309 		data = GUINT_TO_POINTER (merge_id);
310 		g_hash_table_insert (hash_table, g_strdup (id), data);
311 	}
312 }
313 
314 static void
315 plugin_ui_disable_manager (EPluginUIHook *hook,
316                            GtkUIManager *ui_manager,
317                            const gchar *id,
318                            gboolean remove)
319 {
320 	GHashTable *hash_table;
321 	GHashTable *ui_definitions;
322 	GList *keys;
323 
324 	hash_table = hook->priv->registry;
325 	hash_table = g_hash_table_lookup (hash_table, ui_manager);
326 
327 	if (hash_table == NULL)
328 		return;
329 
330 	if (id != NULL)
331 		keys = g_list_prepend (NULL, (gpointer) id);
332 	else
333 		keys = g_hash_table_get_keys (hash_table);
334 
335 	ui_definitions = hook->priv->ui_definitions;
336 
337 	while (keys != NULL) {
338 		guint merge_id;
339 		gpointer data;
340 
341 		id = keys->data;
342 		keys = g_list_delete_link (keys, keys);
343 
344 		if (g_hash_table_lookup (ui_definitions, id) == NULL)
345 			continue;
346 
347 		data = g_hash_table_lookup (hash_table, id);
348 		merge_id = GPOINTER_TO_UINT (data);
349 
350 		/* Merge ID could be 0 if the plugin is disabled. */
351 		if (merge_id > 0) {
352 			gtk_ui_manager_remove_ui (ui_manager, merge_id);
353 			gtk_ui_manager_ensure_update (ui_manager);
354 		}
355 
356 		if (remove)
357 			g_hash_table_remove (hash_table, id);
358 		else
359 			g_hash_table_insert (hash_table, g_strdup (id), NULL);
360 	}
361 }
362 
363 static void
364 plugin_ui_enable_hook (EPluginUIHook *hook)
365 {
366 	GHashTable *hash_table;
367 	GHashTableIter iter;
368 	gpointer key;
369 
370 	/* Enable all GtkUIManagers for this hook. */
371 
372 	hash_table = hook->priv->registry;
373 	g_hash_table_iter_init (&iter, hash_table);
374 
375 	while (g_hash_table_iter_next (&iter, &key, NULL)) {
376 		GtkUIManager *ui_manager = key;
377 		plugin_ui_enable_manager (hook, ui_manager, NULL);
378 	}
379 }
380 
381 static void
382 plugin_ui_disable_hook (EPluginUIHook *hook)
383 {
384 	GHashTable *hash_table;
385 	GHashTableIter iter;
386 	gpointer key;
387 
388 	/* Disable all GtkUIManagers for this hook. */
389 
390 	hash_table = hook->priv->registry;
391 	g_hash_table_iter_init (&iter, hash_table);
392 
393 	while (g_hash_table_iter_next (&iter, &key, NULL)) {
394 		GtkUIManager *ui_manager = key;
395 		plugin_ui_disable_manager (hook, ui_manager, NULL, FALSE);
396 	}
397 }
398 
399 static void
400 plugin_ui_hook_finalize (GObject *object)
401 {
402 	EPluginUIHookPrivate *priv;
403 	GHashTableIter iter;
404 	gpointer ui_manager;
405 
406 	priv = E_PLUGIN_UI_HOOK_GET_PRIVATE (object);
407 
408 	/* Remove weak reference callbacks to GtkUIManagers. */
409 	g_hash_table_iter_init (&iter, priv->registry);
410 	while (g_hash_table_iter_next (&iter, &ui_manager, NULL))
411 		g_object_weak_unref (
412 			G_OBJECT (ui_manager), (GWeakNotify)
413 			plugin_ui_hook_unregister_manager, object);
414 
415 	g_hash_table_destroy (priv->ui_definitions);
416 	g_hash_table_destroy (priv->callbacks);
417 	g_hash_table_destroy (priv->registry);
418 
419 	/* Chain up to parent's dispose() method. */
420 	G_OBJECT_CLASS (e_plugin_ui_hook_parent_class)->dispose (object);
421 }
422 
423 static gint
424 plugin_ui_hook_construct (EPluginHook *hook,
425                           EPlugin *plugin,
426                           xmlNodePtr node)
427 {
428 	EPluginUIHookPrivate *priv;
429 
430 	priv = E_PLUGIN_UI_HOOK_GET_PRIVATE (hook);
431 
432 	/* XXX The EPlugin should be a property of EPluginHookClass.
433 	 *     Then it could be passed directly to g_object_new() and
434 	 *     we wouldn't have to chain up here. */
435 
436 	/* Chain up to parent's construct() method. */
437 	E_PLUGIN_HOOK_CLASS (e_plugin_ui_hook_parent_class)->
438 		construct (hook, plugin, node);
439 
440 	for (node = xmlFirstElementChild (node); node != NULL;
441 		node = xmlNextElementSibling (node)) {
442 
443 		xmlNodePtr child;
444 		xmlBufferPtr buffer;
445 		GString *content;
446 		const gchar *temp;
447 		gchar *callback;
448 		gchar *id;
449 
450 		if (strcmp ((gchar *) node->name, "ui-manager") != 0)
451 			continue;
452 
453 		id = e_plugin_xml_prop (node, "id");
454 		if (id == NULL) {
455 			g_warning ("<ui-manager> requires 'id' property");
456 			continue;
457 		}
458 
459 		callback = e_plugin_xml_prop (node, "callback");
460 		if (callback != NULL)
461 			g_hash_table_insert (
462 				priv->callbacks,
463 				g_strdup (id), callback);
464 
465 		content = g_string_sized_new (1024);
466 
467 		/* Extract the XML content below <ui-manager> */
468 		buffer = xmlBufferCreate ();
469 		for (child = node->children; child != NULL; child = child->next) {
470 			xmlNodeDump (buffer, node->doc, child, 2, 1);
471 			temp = (const gchar *) xmlBufferContent (buffer);
472 			g_string_append (content, temp);
473 		}
474 
475 		g_hash_table_insert (
476 			priv->ui_definitions,
477 			id, g_string_free (content, FALSE));
478 
479 		xmlBufferFree (buffer);
480 	}
481 
482 	return 0;
483 }
484 
485 static void
486 plugin_ui_hook_enable (EPluginHook *hook,
487                        gint state)
488 {
489 	if (state)
490 		plugin_ui_enable_hook (E_PLUGIN_UI_HOOK (hook));
491 	else
492 		plugin_ui_disable_hook (E_PLUGIN_UI_HOOK (hook));
493 }
494 
495 static void
496 e_plugin_ui_hook_class_init (EPluginUIHookClass *class)
497 {
498 	GObjectClass *object_class;
499 	EPluginHookClass *plugin_hook_class;
500 
501 	g_type_class_add_private (class, sizeof (EPluginUIHookPrivate));
502 
503 	object_class = G_OBJECT_CLASS (class);
504 	object_class->finalize = plugin_ui_hook_finalize;
505 
506 	plugin_hook_class = E_PLUGIN_HOOK_CLASS (class);
507 	plugin_hook_class->id = E_PLUGIN_UI_HOOK_CLASS_ID;
508 	plugin_hook_class->construct = plugin_ui_hook_construct;
509 	plugin_hook_class->enable = plugin_ui_hook_enable;
510 }
511 
512 static void
513 e_plugin_ui_hook_init (EPluginUIHook *hook)
514 {
515 	GHashTable *ui_definitions;
516 	GHashTable *callbacks;
517 	GHashTable *registry;
518 
519 	ui_definitions = g_hash_table_new_full (
520 		g_str_hash, g_str_equal,
521 		(GDestroyNotify) g_free,
522 		(GDestroyNotify) g_free);
523 
524 	callbacks = g_hash_table_new_full (
525 		g_str_hash, g_str_equal,
526 		(GDestroyNotify) g_free,
527 		(GDestroyNotify) g_free);
528 
529 	registry = g_hash_table_new_full (
530 		g_direct_hash, g_direct_equal,
531 		(GDestroyNotify) NULL,
532 		(GDestroyNotify) g_hash_table_destroy);
533 
534 	hook->priv = E_PLUGIN_UI_HOOK_GET_PRIVATE (hook);
535 	hook->priv->ui_definitions = ui_definitions;
536 	hook->priv->callbacks = callbacks;
537 	hook->priv->registry = registry;
538 }
539 
540 void
541 e_plugin_ui_register_manager (GtkUIManager *ui_manager,
542                               const gchar *id,
543                               gpointer user_data)
544 {
545 	GSList *plugin_list;
546 
547 	g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
548 	g_return_if_fail (id != NULL);
549 
550 	/* Loop over all installed plugins. */
551 	plugin_list = e_plugin_list_plugins ();
552 	while (plugin_list != NULL) {
553 		EPlugin *plugin = plugin_list->data;
554 		GSList *iter;
555 
556 		plugin_list = g_slist_remove (plugin_list, plugin);
557 
558 		/* Look for hooks of type EPluginUIHook. */
559 		for (iter = plugin->hooks; iter != NULL; iter = iter->next) {
560 			EPluginUIHook *hook = iter->data;
561 			GHashTable *hash_table;
562 
563 			if (!E_IS_PLUGIN_UI_HOOK (hook))
564 				continue;
565 
566 			hash_table = hook->priv->ui_definitions;
567 
568 			/* Check if the hook has a UI definition
569 			 * for the GtkUIManager being registered. */
570 			if (g_hash_table_lookup (hash_table, id) == NULL)
571 				continue;
572 
573 			/* Register the manager with the hook. */
574 			plugin_ui_hook_register_manager (
575 				hook, ui_manager, id, user_data);
576 		}
577 
578 		g_object_unref (plugin);
579 	}
580 }
581 
582 void
583 e_plugin_ui_enable_manager (GtkUIManager *ui_manager,
584                             const gchar *id)
585 {
586 	GSList *plugin_list;
587 
588 	g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
589 	g_return_if_fail (id != NULL);
590 
591 	/* Loop over all installed plugins. */
592 	plugin_list = e_plugin_list_plugins ();
593 	while (plugin_list != NULL) {
594 		EPlugin *plugin = plugin_list->data;
595 		GSList *iter;
596 
597 		plugin_list = g_slist_remove (plugin_list, plugin);
598 
599 		/* Look for hooks of type EPluginUIHook. */
600 		for (iter = plugin->hooks; iter != NULL; iter = iter->next) {
601 			EPluginUIHook *hook = iter->data;
602 
603 			if (!E_IS_PLUGIN_UI_HOOK (hook))
604 				continue;
605 
606 			plugin_ui_enable_manager (hook, ui_manager, id);
607 		}
608 
609 		g_object_unref (plugin);
610 	}
611 }
612 
613 void
614 e_plugin_ui_disable_manager (GtkUIManager *ui_manager,
615                              const gchar *id)
616 {
617 	GSList *plugin_list;
618 
619 	g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
620 	g_return_if_fail (id != NULL);
621 
622 	/* Loop over all installed plugins. */
623 	plugin_list = e_plugin_list_plugins ();
624 	while (plugin_list != NULL) {
625 		EPlugin *plugin = plugin_list->data;
626 		GSList *iter;
627 
628 		plugin_list = g_slist_remove (plugin_list, plugin);
629 
630 		/* Look for hooks of type EPluginUIHook. */
631 		for (iter = plugin->hooks; iter != NULL; iter = iter->next) {
632 			EPluginUIHook *hook = iter->data;
633 
634 			if (!E_IS_PLUGIN_UI_HOOK (hook))
635 				continue;
636 
637 			plugin_ui_disable_manager (hook, ui_manager, id, TRUE);
638 		}
639 
640 		g_object_unref (plugin);
641 	}
642 }