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.c438
1 files changed, 298 insertions, 140 deletions
diff --git a/e-util/e-plugin-ui.c b/e-util/e-plugin-ui.c
index 6af3290bb6..4f948fbb2f 100644
--- a/e-util/e-plugin-ui.c
+++ b/e-util/e-plugin-ui.c
@@ -25,7 +25,6 @@
#define E_PLUGIN_UI_INIT_FUNC "e_plugin_ui_init"
#define E_PLUGIN_UI_HOOK_CLASS_ID "org.gnome.evolution.ui:1.0"
-#define E_PLUGIN_UI_MANAGER_ID_KEY "e-plugin-ui-manager-id"
struct _EPluginUIHookPrivate {
@@ -33,9 +32,11 @@ struct _EPluginUIHookPrivate {
*
* For example:
*
- * <ui-manager id="org.gnome.evolution.sample">
- * ... UI definition ...
- * </ui-manager>
+ * <hook class="org.gnome.evolution.ui:1.0">
+ * <ui-manager id="org.gnome.evolution.sample">
+ * ... UI definition ...
+ * </ui-manager>
+ * </hook>
*
* Results in:
*
@@ -49,197 +50,310 @@ struct _EPluginUIHookPrivate {
* optional.
*/
GHashTable *ui_definitions;
+
+ /* 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 and 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;
};
-/* The registry is a hash table of hash tables. It maps
- *
- * EPluginUIHook instance --> GtkUIManager instance --> UI merge id
- *
- * GtkUIManager instances are automatically removed when finalized.
- */
-static GHashTable *registry;
static gpointer parent_class;
static void
-plugin_ui_registry_remove (EPluginUIHook *hook,
- GtkUIManager *ui_manager)
+plugin_ui_hook_unregister_manager (EPluginUIHook *hook,
+ GtkUIManager *ui_manager)
{
- GHashTable *hash_table;
+ GHashTable *registry;
/* Note: Manager may already be finalized. */
-
- hash_table = g_hash_table_lookup (registry, hook);
- g_return_if_fail (hash_table != NULL);
-
- g_hash_table_remove (hash_table, ui_manager);
- if (g_hash_table_size (hash_table) == 0)
- g_hash_table_remove (registry, hook);
+ registry = hook->priv->registry;
+ g_hash_table_remove (registry, ui_manager);
}
static void
-plugin_ui_registry_insert (EPluginUIHook *hook,
- GtkUIManager *ui_manager,
- guint merge_id)
+plugin_ui_hook_register_manager (EPluginUIHook *hook,
+ GtkUIManager *ui_manager,
+ gpointer user_data)
{
+ EPlugin *plugin;
+ EPluginUIInitFunc func;
+ GHashTable *registry;
GHashTable *hash_table;
- hash_table = g_hash_table_lookup (registry, hook);
- if (hash_table == NULL) {
- hash_table = g_hash_table_new (g_direct_hash, g_direct_equal);
- g_hash_table_insert (registry, hook, hash_table);
- }
+ plugin = ((EPluginHook *) hook)->plugin;
+ func = e_plugin_get_symbol (plugin, E_PLUGIN_UI_INIT_FUNC);
+
+ /* Pass the manager and user_data to the plugin's e_plugin_ui_init()
+ * function (if it defined one). The plugin should install whatever
+ * GtkActions and GtkActionGroups are neccessary to implement the
+ * action names in its UI definition. */
+ if (func != NULL && !func (ui_manager, user_data))
+ return;
g_object_weak_ref (
G_OBJECT (ui_manager), (GWeakNotify)
- plugin_ui_registry_remove, hook);
+ plugin_ui_hook_unregister_manager, hook);
- g_hash_table_insert (
- hash_table, ui_manager, GUINT_TO_POINTER (merge_id));
+ 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);
+ }
}
-/* Helper for plugin_ui_hook_merge_ui() */
-static void
-plugin_ui_hook_merge_foreach (GtkUIManager *ui_manager,
- const gchar *ui_definition,
- GHashTable *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;
- /* Merge the UI definition into the manager. */
+ 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);
- gtk_ui_manager_ensure_update (ui_manager);
+
if (error != NULL) {
g_warning ("%s", error->message);
g_error_free (error);
}
- /* Merge ID will be 0 on error, which is what we want. */
- g_hash_table_insert (
- hash_table, ui_manager, GUINT_TO_POINTER (merge_id));
+ return merge_id;
}
static void
-plugin_ui_hook_merge_ui (EPluginUIHook *hook)
+plugin_ui_enable_manager (EPluginUIHook *hook,
+ GtkUIManager *ui_manager,
+ const gchar *id)
{
- GHashTable *old_merge_ids;
- GHashTable *new_merge_ids;
- GHashTable *intermediate;
+ GHashTable *hash_table;
+ GHashTable *ui_definitions;
GList *keys;
- old_merge_ids = g_hash_table_lookup (registry, hook);
- if (old_merge_ids == NULL)
- return;
+ hash_table = hook->priv->registry;
+ hash_table = g_hash_table_lookup (hash_table, ui_manager);
- /* The GtkUIManager instances and UI definitions live in separate
- * tables, so we need to build an intermediate table that we can
- * easily iterate over. */
- keys = g_hash_table_get_keys (old_merge_ids);
- intermediate = g_hash_table_new (g_direct_hash, g_direct_equal);
+ if (hash_table == NULL)
+ return;
- while (keys != NULL) {
- GtkUIManager *ui_manager = keys->data;
- gchar *ui_definition;
+ if (id != NULL)
+ keys = g_list_prepend (NULL, (gpointer) id);
+ else
+ keys = g_hash_table_get_keys (hash_table);
- ui_definition = g_hash_table_lookup (
- hook->priv->ui_definitions,
- e_plugin_ui_get_manager_id (ui_manager));
+ ui_definitions = hook->priv->ui_definitions;
- g_hash_table_insert (intermediate, ui_manager, ui_definition);
+ while (keys != NULL) {
+ guint merge_id;
+ gpointer data;
+ id = keys->data;
keys = g_list_delete_link (keys, keys);
- }
- new_merge_ids = g_hash_table_new (g_direct_hash, g_direct_equal);
+ if (g_hash_table_lookup (ui_definitions, id) == NULL)
+ continue;
+
+ data = g_hash_table_lookup (hash_table, id);
+ merge_id = GPOINTER_TO_UINT (data);
- g_hash_table_foreach (
- intermediate, (GHFunc)
- plugin_ui_hook_merge_foreach, new_merge_ids);
+ if (merge_id > 0)
+ continue;
- g_hash_table_insert (registry, hook, new_merge_ids);
+ if (((EPluginHook *) hook)->plugin->enabled)
+ merge_id = plugin_ui_hook_merge_ui (
+ hook, ui_manager, id);
- g_hash_table_destroy (intermediate);
+ /* 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);
+ }
}
-/* Helper for plugin_ui_hook_unmerge_ui() */
static void
-plugin_ui_hook_unmerge_foreach (GtkUIManager *ui_manager,
- gpointer value,
- GHashTable *hash_table)
+plugin_ui_disable_manager (EPluginUIHook *hook,
+ GtkUIManager *ui_manager,
+ const gchar *id,
+ gboolean remove)
{
- guint merge_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;
- merge_id = GPOINTER_TO_UINT (value);
- gtk_ui_manager_remove_ui (ui_manager, merge_id);
+ id = keys->data;
+ keys = g_list_delete_link (keys, keys);
+
+ if (g_hash_table_lookup (ui_definitions, id) == NULL)
+ continue;
- g_hash_table_insert (hash_table, ui_manager, GUINT_TO_POINTER (0));
+ 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);
+
+ if (remove)
+ g_hash_table_remove (hash_table, id);
+ else
+ g_hash_table_insert (hash_table, g_strdup (id), NULL);
+ }
}
static void
-plugin_ui_hook_unmerge_ui (EPluginUIHook *hook)
+plugin_ui_enable_hook (EPluginUIHook *hook)
{
- GHashTable *old_merge_ids;
- GHashTable *new_merge_ids;
-
- old_merge_ids = g_hash_table_lookup (registry, hook);
- if (old_merge_ids == NULL)
- return;
+ GHashTable *hash_table;
+ GHashTableIter iter;
+ gpointer key;
- new_merge_ids = g_hash_table_new (g_direct_hash, g_direct_equal);
+ /* Enable all GtkUIManagers for this hook. */
- g_hash_table_foreach (
- old_merge_ids, (GHFunc)
- plugin_ui_hook_unmerge_foreach, new_merge_ids);
+ hash_table = hook->priv->registry;
+ g_hash_table_iter_init (&iter, hash_table);
- g_hash_table_insert (registry, hook, new_merge_ids);
+ 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_hook_register_manager (EPluginUIHook *hook,
- GtkUIManager *ui_manager,
- const gchar *ui_definition,
- gpointer user_data)
+plugin_ui_disable_hook (EPluginUIHook *hook)
{
- EPlugin *plugin;
- EPluginUIInitFunc func;
- guint merge_id = 0;
+ GHashTable *hash_table;
+ GHashTableIter iter;
+ gpointer key;
- plugin = ((EPluginHook *) hook)->plugin;
- func = e_plugin_get_symbol (plugin, E_PLUGIN_UI_INIT_FUNC);
+ /* Disable all GtkUIManagers for this hook. */
- /* Pass the manager and user_data to the plugin's e_plugin_ui_init()
- * function (if it defined one). The plugin should install whatever
- * GtkActions and GtkActionGroups are neccessary to implement the
- * action names in its UI definition. */
- if (func != NULL && !func (ui_manager, user_data))
- return;
+ hash_table = hook->priv->registry;
+ g_hash_table_iter_init (&iter, hash_table);
- if (plugin->enabled) {
- GError *error = NULL;
-
- /* Merge the UI definition into the manager. */
- merge_id = gtk_ui_manager_add_ui_from_string (
- ui_manager, ui_definition, -1, &error);
- gtk_ui_manager_ensure_update (ui_manager);
- if (error != NULL) {
- g_warning ("%s", error->message);
- g_error_free (error);
- }
+ while (g_hash_table_iter_next (&iter, &key, NULL)) {
+ GtkUIManager *ui_manager = key;
+ plugin_ui_disable_manager (hook, ui_manager, NULL, FALSE);
}
-
- /* Save merge ID's for later use. */
- plugin_ui_registry_insert (hook, ui_manager, merge_id);
}
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->registry);
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (parent_class)->dispose (object);
@@ -306,9 +420,9 @@ plugin_ui_hook_enable (EPluginHook *hook,
gint state)
{
if (state)
- plugin_ui_hook_merge_ui (E_PLUGIN_UI_HOOK (hook));
+ plugin_ui_enable_hook (E_PLUGIN_UI_HOOK (hook));
else
- plugin_ui_hook_unmerge_ui (E_PLUGIN_UI_HOOK (hook));
+ plugin_ui_disable_hook (E_PLUGIN_UI_HOOK (hook));
}
static void
@@ -327,25 +441,24 @@ plugin_ui_hook_class_init (EPluginUIHookClass *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;
-
- registry = g_hash_table_new_full (
- g_direct_hash, g_direct_equal,
- (GDestroyNotify) NULL,
- (GDestroyNotify) g_hash_table_destroy);
}
static void
plugin_ui_hook_init (EPluginUIHook *hook)
{
GHashTable *ui_definitions;
+ GHashTable *registry;
ui_definitions = g_hash_table_new_full (
g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_free);
+ registry = g_hash_table_new (g_direct_hash, g_direct_equal);
+
hook->priv = E_PLUGIN_UI_HOOK_GET_PRIVATE (hook);
hook->priv->ui_definitions = ui_definitions;
+ hook->priv->registry = registry;
}
GType
@@ -375,17 +488,14 @@ e_plugin_ui_hook_get_type (void)
}
void
-e_plugin_ui_register_manager (const gchar *id,
- GtkUIManager *ui_manager,
+e_plugin_ui_register_manager (GtkUIManager *ui_manager,
+ const gchar *id,
gpointer user_data)
{
- const gchar *key = E_PLUGIN_UI_MANAGER_ID_KEY;
GSList *plugin_list;
- g_return_if_fail (id != NULL);
g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
-
- g_object_set_data (G_OBJECT (ui_manager), key, (gpointer) id);
+ g_return_if_fail (id != NULL);
/* Loop over all installed plugins. */
plugin_list = e_plugin_list_plugins ();
@@ -393,36 +503,84 @@ e_plugin_ui_register_manager (const gchar *id,
EPlugin *plugin = plugin_list->data;
GSList *iter;
+ plugin_list = g_slist_next (plugin_list);
+
/* Look for hooks of type EPluginUIHook. */
for (iter = plugin->hooks; iter != NULL; iter = iter->next) {
EPluginUIHook *hook = iter->data;
- const gchar *ui_definition;
+ 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. */
- ui_definition = g_hash_table_lookup (
- hook->priv->ui_definitions, id);
- if (ui_definition == NULL)
+ if (g_hash_table_lookup (hash_table, id) == NULL)
continue;
/* Register the manager with the hook. */
plugin_ui_hook_register_manager (
- hook, ui_manager, ui_definition, user_data);
+ hook, ui_manager, user_data);
}
+ }
+}
+
+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_next (plugin_list);
+
+ /* 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);
+ }
}
}
-const gchar *
-e_plugin_ui_get_manager_id (GtkUIManager *ui_manager)
+void
+e_plugin_ui_disable_manager (GtkUIManager *ui_manager,
+ const gchar *id)
{
- const gchar *key = E_PLUGIN_UI_MANAGER_ID_KEY;
+ 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;
- g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), NULL);
+ plugin_list = g_slist_next (plugin_list);
+
+ /* Look for hooks of type EPluginUIHook. */
+ for (iter = plugin->hooks; iter != NULL; iter = iter->next) {
+ EPluginUIHook *hook = iter->data;
- return g_object_get_data (G_OBJECT (ui_manager), key);
+ if (!E_IS_PLUGIN_UI_HOOK (hook))
+ continue;
+
+ plugin_ui_disable_manager (hook, ui_manager, id, TRUE);
+ }
+ }
}