aboutsummaryrefslogtreecommitdiffstats
path: root/e-util/e-plugin-ui.c
diff options
context:
space:
mode:
Diffstat (limited to 'e-util/e-plugin-ui.c')
-rw-r--r--e-util/e-plugin-ui.c635
1 files changed, 635 insertions, 0 deletions
diff --git a/e-util/e-plugin-ui.c b/e-util/e-plugin-ui.c
new file mode 100644
index 0000000000..a8d0368e3e
--- /dev/null
+++ b/e-util/e-plugin-ui.c
@@ -0,0 +1,635 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-plugin-ui.h"
+
+#include <string.h>
+
+#define E_PLUGIN_UI_HOOK_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_PLUGIN_UI_HOOK, EPluginUIHookPrivate))
+
+#define E_PLUGIN_UI_DEFAULT_FUNC "e_plugin_ui_init"
+#define E_PLUGIN_UI_HOOK_CLASS_ID "org.gnome.evolution.ui:1.0"
+
+struct _EPluginUIHookPrivate {
+
+ /* Table of GtkUIManager ID's to UI definitions.
+ *
+ * For example:
+ *
+ * <hook class="org.gnome.evolution.ui:1.0">
+ * <ui-manager id="org.gnome.evolution.foo">
+ * ... UI definition ...
+ * </ui-manager>
+ * </hook>
+ *
+ * Results in:
+ *
+ * g_hash_table_insert (
+ * ui_definitions,
+ * "org.gnome.evolution.foo",
+ * "... UI definition ...");
+ *
+ * See http://library.gnome.org/devel/gtk/unstable/GtkUIManager.html
+ * for more information about UI definitions. Note: the <ui> tag is
+ * optional.
+ */
+ GHashTable *ui_definitions;
+
+ /* Table of GtkUIManager ID's to callback function names.
+ *
+ * This stores the optional "callback" attribute in the <ui-manager>
+ * element. If not specified, it defaults to "e_plugin_ui_init".
+ *
+ * This is useful when extending the UI of multiple GtkUIManager IDs
+ * from a single plugin.
+ *
+ * For example:
+ *
+ * <hook class="org.gnome.evolution.ui:1.0">
+ * <ui-manager id="org.gnome.evolution.foo" callback="init_foo">
+ * ...
+ * </ui-manager>
+ * <ui-manager id="org.gnome.evolution.bar" callback="init_bar">
+ * ...
+ * </ui-manager>
+ * </hook>
+ *
+ * Results in:
+ *
+ * g_hash_table_insert (
+ * callbacks, "org.gnome.evolution.foo", "init_foo");
+ *
+ * g_hash_table_insert (
+ * callbacks, "org.gnome.evolution.bar", "init_bar");
+ */
+ GHashTable *callbacks;
+
+ /* The registry is the heart of EPluginUI. It tracks GtkUIManager
+ * instances, GtkUIManager IDs, and UI merge IDs as a hash table of
+ * hash tables:
+ *
+ * GtkUIManager instance -> GtkUIManager ID -> UI Merge ID
+ *
+ * A GtkUIManager instance and ID form a unique key for looking up
+ * UI merge IDs. The reason both are needed is because the same
+ * GtkUIManager instance can be registered under multiple IDs.
+ *
+ * This is done primarily to support shell views, which share a
+ * common GtkUIManager instance for a particular shell window.
+ * Each shell view registers the same GtkUIManager instance under
+ * a unique ID:
+ *
+ * "org.gnome.evolution.mail" }
+ * "org.gnome.evolution.contacts" } aliases for a common
+ * "org.gnome.evolution.calendar" } GtkUIManager instance
+ * "org.gnome.evolution.memos" }
+ * "org.gnome.evolution.tasks" }
+ *
+ * Note: The shell window also registers the same GtkUIManager
+ * instance as "org.gnome.evolution.shell".
+ *
+ * This way, plugins that extend a shell view's UI will follow the
+ * merging and unmerging of the shell view automatically.
+ *
+ * The presence or absence of GtkUIManager IDs in the registry is
+ * significant. Presence of a (instance, ID) pair indicates that
+ * UI manager is active, absence indicates inactive. Furthermore,
+ * a non-zero merge ID for an active UI manager indicates the
+ * plugin is enabled. Zero indicates disabled.
+ *
+ * Here's a quick scenario to illustrate:
+ *
+ * Suppose we have a plugin that extends the mail shell view UI.
+ * Its EPlugin definition file has this section:
+ *
+ * <hook class="org.gnome.evolution.ui:1.0">
+ * <ui-manager id="org.gnome.evolution.mail">
+ * ... UI definition ...
+ * </ui-manager>
+ * </hook>
+ *
+ * The plugin is enabled and the active shell view is "mail".
+ * Let "ManagerA" denote the common GtkUIManager instance for
+ * this shell window. Here's what happens to the registry as
+ * the user performs various actions;
+ *
+ * - Initial State Merge ID
+ * V
+ * { "ManagerA", { "org.gnome.evolution.mail", 3 } }
+ *
+ * - User Disables the Plugin
+ *
+ * { "ManagerA", { "org.gnome.evolution.mail", 0 } }
+ *
+ * - User Enables the Plugin
+ *
+ * { "ManagerA", { "org.gnome.evolution.mail", 4 } }
+ *
+ * - User Switches to Calendar View
+ *
+ * { "ManagerA", { } }
+ *
+ * - User Disables the Plugin
+ *
+ * { "ManagerA", { } }
+ *
+ * - User Switches to Mail View
+ *
+ * { "ManagerA", { "org.gnome.evolution.mail", 0 } }
+ *
+ * - User Enables the Plugin
+ *
+ * { "ManagerA", { "org.gnome.evolution.mail", 5 } }
+ */
+ GHashTable *registry;
+};
+
+G_DEFINE_TYPE (
+ EPluginUIHook,
+ e_plugin_ui_hook,
+ E_TYPE_PLUGIN_HOOK)
+
+static void
+plugin_ui_hook_unregister_manager (EPluginUIHook *hook,
+ GtkUIManager *ui_manager)
+{
+ GHashTable *registry;
+
+ /* Note: Manager may already be finalized. */
+ registry = hook->priv->registry;
+ g_hash_table_remove (registry, ui_manager);
+}
+
+static void
+plugin_ui_hook_register_manager (EPluginUIHook *hook,
+ GtkUIManager *ui_manager,
+ const gchar *id,
+ gpointer user_data)
+{
+ EPlugin *plugin;
+ EPluginUIInitFunc func;
+ GHashTable *registry;
+ GHashTable *hash_table;
+ const gchar *func_name;
+
+ plugin = ((EPluginHook *) hook)->plugin;
+
+ hash_table = hook->priv->callbacks;
+ func_name = g_hash_table_lookup (hash_table, id);
+
+ if (func_name == NULL)
+ func_name = E_PLUGIN_UI_DEFAULT_FUNC;
+
+ func = e_plugin_get_symbol (plugin, func_name);
+
+ if (func == NULL) {
+ g_critical (
+ "Plugin \"%s\" is missing a function named %s()",
+ plugin->name, func_name);
+ return;
+ }
+
+ /* Pass the manager and user_data to the plugin's callback function.
+ * The plugin should install whatever GtkActions and GtkActionGroups
+ * are neccessary to implement the actions in its UI definition. */
+ if (!func (ui_manager, user_data))
+ return;
+
+ g_object_weak_ref (
+ G_OBJECT (ui_manager), (GWeakNotify)
+ plugin_ui_hook_unregister_manager, hook);
+
+ registry = hook->priv->registry;
+ hash_table = g_hash_table_lookup (registry, ui_manager);
+
+ if (hash_table == NULL) {
+ hash_table = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) NULL);
+ g_hash_table_insert (registry, ui_manager, hash_table);
+ }
+}
+
+static guint
+plugin_ui_hook_merge_ui (EPluginUIHook *hook,
+ GtkUIManager *ui_manager,
+ const gchar *id)
+{
+ GHashTable *hash_table;
+ const gchar *ui_definition;
+ guint merge_id;
+ GError *error = NULL;
+
+ hash_table = hook->priv->ui_definitions;
+ ui_definition = g_hash_table_lookup (hash_table, id);
+ g_return_val_if_fail (ui_definition != NULL, 0);
+
+ merge_id = gtk_ui_manager_add_ui_from_string (
+ ui_manager, ui_definition, -1, &error);
+
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+
+ return merge_id;
+}
+
+static void
+plugin_ui_enable_manager (EPluginUIHook *hook,
+ GtkUIManager *ui_manager,
+ const gchar *id)
+{
+ GHashTable *hash_table;
+ GHashTable *ui_definitions;
+ GList *keys;
+
+ hash_table = hook->priv->registry;
+ hash_table = g_hash_table_lookup (hash_table, ui_manager);
+
+ if (hash_table == NULL)
+ return;
+
+ if (id != NULL)
+ keys = g_list_prepend (NULL, (gpointer) id);
+ else
+ keys = g_hash_table_get_keys (hash_table);
+
+ ui_definitions = hook->priv->ui_definitions;
+
+ while (keys != NULL) {
+ guint merge_id;
+ gpointer data;
+
+ id = keys->data;
+ keys = g_list_delete_link (keys, keys);
+
+ if (g_hash_table_lookup (ui_definitions, id) == NULL)
+ continue;
+
+ data = g_hash_table_lookup (hash_table, id);
+ merge_id = GPOINTER_TO_UINT (data);
+
+ if (merge_id > 0)
+ continue;
+
+ if (((EPluginHook *) hook)->plugin->enabled)
+ merge_id = plugin_ui_hook_merge_ui (
+ hook, ui_manager, id);
+
+ /* Merge ID will be 0 on error, which is what we want. */
+ data = GUINT_TO_POINTER (merge_id);
+ g_hash_table_insert (hash_table, g_strdup (id), data);
+ }
+}
+
+static void
+plugin_ui_disable_manager (EPluginUIHook *hook,
+ GtkUIManager *ui_manager,
+ const gchar *id,
+ gboolean remove)
+{
+ GHashTable *hash_table;
+ GHashTable *ui_definitions;
+ GList *keys;
+
+ hash_table = hook->priv->registry;
+ hash_table = g_hash_table_lookup (hash_table, ui_manager);
+
+ if (hash_table == NULL)
+ return;
+
+ if (id != NULL)
+ keys = g_list_prepend (NULL, (gpointer) id);
+ else
+ keys = g_hash_table_get_keys (hash_table);
+
+ ui_definitions = hook->priv->ui_definitions;
+
+ while (keys != NULL) {
+ guint merge_id;
+ gpointer data;
+
+ id = keys->data;
+ keys = g_list_delete_link (keys, keys);
+
+ if (g_hash_table_lookup (ui_definitions, id) == NULL)
+ continue;
+
+ data = g_hash_table_lookup (hash_table, id);
+ merge_id = GPOINTER_TO_UINT (data);
+
+ /* Merge ID could be 0 if the plugin is disabled. */
+ if (merge_id > 0) {
+ gtk_ui_manager_remove_ui (ui_manager, merge_id);
+ gtk_ui_manager_ensure_update (ui_manager);
+ }
+
+ if (remove)
+ g_hash_table_remove (hash_table, id);
+ else
+ g_hash_table_insert (hash_table, g_strdup (id), NULL);
+ }
+}
+
+static void
+plugin_ui_enable_hook (EPluginUIHook *hook)
+{
+ GHashTable *hash_table;
+ GHashTableIter iter;
+ gpointer key;
+
+ /* Enable all GtkUIManagers for this hook. */
+
+ hash_table = hook->priv->registry;
+ g_hash_table_iter_init (&iter, hash_table);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL)) {
+ GtkUIManager *ui_manager = key;
+ plugin_ui_enable_manager (hook, ui_manager, NULL);
+ }
+}
+
+static void
+plugin_ui_disable_hook (EPluginUIHook *hook)
+{
+ GHashTable *hash_table;
+ GHashTableIter iter;
+ gpointer key;
+
+ /* Disable all GtkUIManagers for this hook. */
+
+ hash_table = hook->priv->registry;
+ g_hash_table_iter_init (&iter, hash_table);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL)) {
+ GtkUIManager *ui_manager = key;
+ plugin_ui_disable_manager (hook, ui_manager, NULL, FALSE);
+ }
+}
+
+static void
+plugin_ui_hook_finalize (GObject *object)
+{
+ EPluginUIHookPrivate *priv;
+ GHashTableIter iter;
+ gpointer ui_manager;
+
+ priv = E_PLUGIN_UI_HOOK_GET_PRIVATE (object);
+
+ /* Remove weak reference callbacks to GtkUIManagers. */
+ g_hash_table_iter_init (&iter, priv->registry);
+ while (g_hash_table_iter_next (&iter, &ui_manager, NULL))
+ g_object_weak_unref (
+ G_OBJECT (ui_manager), (GWeakNotify)
+ plugin_ui_hook_unregister_manager, object);
+
+ g_hash_table_destroy (priv->ui_definitions);
+ g_hash_table_destroy (priv->callbacks);
+ g_hash_table_destroy (priv->registry);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_plugin_ui_hook_parent_class)->dispose (object);
+}
+
+static gint
+plugin_ui_hook_construct (EPluginHook *hook,
+ EPlugin *plugin,
+ xmlNodePtr node)
+{
+ EPluginUIHookPrivate *priv;
+
+ priv = E_PLUGIN_UI_HOOK_GET_PRIVATE (hook);
+
+ /* XXX The EPlugin should be a property of EPluginHookClass.
+ * Then it could be passed directly to g_object_new() and
+ * we wouldn't have to chain up here. */
+
+ /* Chain up to parent's construct() method. */
+ E_PLUGIN_HOOK_CLASS (e_plugin_ui_hook_parent_class)->
+ construct (hook, plugin, node);
+
+ for (node = xmlFirstElementChild (node); node != NULL;
+ node = xmlNextElementSibling (node)) {
+
+ xmlNodePtr child;
+ xmlBufferPtr buffer;
+ GString *content;
+ const gchar *temp;
+ gchar *callback;
+ gchar *id;
+
+ if (strcmp ((gchar *) node->name, "ui-manager") != 0)
+ continue;
+
+ id = e_plugin_xml_prop (node, "id");
+ if (id == NULL) {
+ g_warning ("<ui-manager> requires 'id' property");
+ continue;
+ }
+
+ callback = e_plugin_xml_prop (node, "callback");
+ if (callback != NULL)
+ g_hash_table_insert (
+ priv->callbacks,
+ g_strdup (id), callback);
+
+ content = g_string_sized_new (1024);
+
+ /* Extract the XML content below <ui-manager> */
+ buffer = xmlBufferCreate ();
+ for (child = node->children; child != NULL; child = child->next) {
+ xmlNodeDump (buffer, node->doc, child, 2, 1);
+ temp = (const gchar *) xmlBufferContent (buffer);
+ g_string_append (content, temp);
+ }
+
+ g_hash_table_insert (
+ priv->ui_definitions,
+ id, g_string_free (content, FALSE));
+
+ xmlBufferFree (buffer);
+ }
+
+ return 0;
+}
+
+static void
+plugin_ui_hook_enable (EPluginHook *hook,
+ gint state)
+{
+ if (state)
+ plugin_ui_enable_hook (E_PLUGIN_UI_HOOK (hook));
+ else
+ plugin_ui_disable_hook (E_PLUGIN_UI_HOOK (hook));
+}
+
+static void
+e_plugin_ui_hook_class_init (EPluginUIHookClass *class)
+{
+ GObjectClass *object_class;
+ EPluginHookClass *plugin_hook_class;
+
+ g_type_class_add_private (class, sizeof (EPluginUIHookPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = plugin_ui_hook_finalize;
+
+ plugin_hook_class = E_PLUGIN_HOOK_CLASS (class);
+ plugin_hook_class->id = E_PLUGIN_UI_HOOK_CLASS_ID;
+ plugin_hook_class->construct = plugin_ui_hook_construct;
+ plugin_hook_class->enable = plugin_ui_hook_enable;
+}
+
+static void
+e_plugin_ui_hook_init (EPluginUIHook *hook)
+{
+ GHashTable *ui_definitions;
+ GHashTable *callbacks;
+ GHashTable *registry;
+
+ ui_definitions = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ callbacks = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ registry = g_hash_table_new_full (
+ g_direct_hash, g_direct_equal,
+ (GDestroyNotify) NULL,
+ (GDestroyNotify) g_hash_table_destroy);
+
+ hook->priv = E_PLUGIN_UI_HOOK_GET_PRIVATE (hook);
+ hook->priv->ui_definitions = ui_definitions;
+ hook->priv->callbacks = callbacks;
+ hook->priv->registry = registry;
+}
+
+void
+e_plugin_ui_register_manager (GtkUIManager *ui_manager,
+ const gchar *id,
+ gpointer user_data)
+{
+ GSList *plugin_list;
+
+ g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
+ g_return_if_fail (id != NULL);
+
+ /* Loop over all installed plugins. */
+ plugin_list = e_plugin_list_plugins ();
+ while (plugin_list != NULL) {
+ EPlugin *plugin = plugin_list->data;
+ GSList *iter;
+
+ plugin_list = g_slist_remove (plugin_list, plugin);
+
+ /* Look for hooks of type EPluginUIHook. */
+ for (iter = plugin->hooks; iter != NULL; iter = iter->next) {
+ EPluginUIHook *hook = iter->data;
+ GHashTable *hash_table;
+
+ if (!E_IS_PLUGIN_UI_HOOK (hook))
+ continue;
+
+ hash_table = hook->priv->ui_definitions;
+
+ /* Check if the hook has a UI definition
+ * for the GtkUIManager being registered. */
+ if (g_hash_table_lookup (hash_table, id) == NULL)
+ continue;
+
+ /* Register the manager with the hook. */
+ plugin_ui_hook_register_manager (
+ hook, ui_manager, id, user_data);
+ }
+
+ g_object_unref (plugin);
+ }
+}
+
+void
+e_plugin_ui_enable_manager (GtkUIManager *ui_manager,
+ const gchar *id)
+{
+ GSList *plugin_list;
+
+ g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
+ g_return_if_fail (id != NULL);
+
+ /* Loop over all installed plugins. */
+ plugin_list = e_plugin_list_plugins ();
+ while (plugin_list != NULL) {
+ EPlugin *plugin = plugin_list->data;
+ GSList *iter;
+
+ plugin_list = g_slist_remove (plugin_list, plugin);
+
+ /* Look for hooks of type EPluginUIHook. */
+ for (iter = plugin->hooks; iter != NULL; iter = iter->next) {
+ EPluginUIHook *hook = iter->data;
+
+ if (!E_IS_PLUGIN_UI_HOOK (hook))
+ continue;
+
+ plugin_ui_enable_manager (hook, ui_manager, id);
+ }
+
+ g_object_unref (plugin);
+ }
+}
+
+void
+e_plugin_ui_disable_manager (GtkUIManager *ui_manager,
+ const gchar *id)
+{
+ GSList *plugin_list;
+
+ g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
+ g_return_if_fail (id != NULL);
+
+ /* Loop over all installed plugins. */
+ plugin_list = e_plugin_list_plugins ();
+ while (plugin_list != NULL) {
+ EPlugin *plugin = plugin_list->data;
+ GSList *iter;
+
+ plugin_list = g_slist_remove (plugin_list, plugin);
+
+ /* Look for hooks of type EPluginUIHook. */
+ for (iter = plugin->hooks; iter != NULL; iter = iter->next) {
+ EPluginUIHook *hook = iter->data;
+
+ if (!E_IS_PLUGIN_UI_HOOK (hook))
+ continue;
+
+ plugin_ui_disable_manager (hook, ui_manager, id, TRUE);
+ }
+
+ g_object_unref (plugin);
+ }
+}