aboutsummaryrefslogtreecommitdiffstats
path: root/e-util/e-event.c
diff options
context:
space:
mode:
Diffstat (limited to 'e-util/e-event.c')
-rw-r--r--e-util/e-event.c528
1 files changed, 528 insertions, 0 deletions
diff --git a/e-util/e-event.c b/e-util/e-event.c
new file mode 100644
index 0000000000..276c55e990
--- /dev/null
+++ b/e-util/e-event.c
@@ -0,0 +1,528 @@
+/*
+ * 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/>
+ *
+ *
+ * Authors:
+ * Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+
+#include "e-event.h"
+
+#include <glib/gi18n.h>
+
+#define E_EVENT_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_EVENT, EEventPrivate))
+
+#define d(x)
+
+struct _event_node {
+ GSList *events;
+ gpointer data;
+ EEventItemsFunc freefunc;
+};
+
+struct _event_info {
+ struct _event_node *parent;
+ EEventItem *item;
+};
+
+struct _EEventPrivate {
+ GQueue events;
+ GSList *sorted; /* sorted list of struct _event_info's */
+};
+
+G_DEFINE_TYPE (
+ EEvent,
+ e_event,
+ G_TYPE_OBJECT)
+
+static void
+event_finalize (GObject *object)
+{
+ EEvent *event = (EEvent *) object;
+ EEventPrivate *p = event->priv;
+
+ if (event->target)
+ e_event_target_free (event, event->target);
+
+ g_free (event->id);
+
+ while (!g_queue_is_empty (&p->events)) {
+ struct _event_node *node;
+
+ node = g_queue_pop_head (&p->events);
+
+ if (node->freefunc != NULL)
+ node->freefunc (event, node->events, node->data);
+
+ g_free (node);
+ }
+
+ g_slist_foreach (p->sorted, (GFunc) g_free, NULL);
+ g_slist_free (p->sorted);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_event_parent_class)->finalize (object);
+}
+
+static void
+event_target_free (EEvent *event,
+ EEventTarget *target)
+{
+ g_free (target);
+ g_object_unref (event);
+}
+
+static void
+e_event_class_init (EEventClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EEventPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = event_finalize;
+
+ class->target_free = event_target_free;
+}
+
+static void
+e_event_init (EEvent *event)
+{
+ event->priv = E_EVENT_GET_PRIVATE (event);
+
+ g_queue_init (&event->priv->events);
+}
+
+/**
+ * e_event_construct:
+ * @event: An instantiated but uninitialised EEvent.
+ * @id: Event manager id.
+ *
+ * Construct the base event instance with standard parameters.
+ *
+ * Returns: the @event
+ **/
+EEvent *
+e_event_construct (EEvent *event,
+ const gchar *id)
+{
+ event->id = g_strdup (id);
+
+ return event;
+}
+
+/**
+ * e_event_add_items:
+ * @event: An initialised EEvent structure.
+ * @items: A list of EEventItems event listeners to register on this event manager.
+ * @freefunc: A function called when the @items list is no longer needed.
+ * @data: callback data for @freefunc and for item event handlers.
+ *
+ * Adds @items to the list of events listened to on the event manager @event.
+ *
+ * Return value: An opaque key which can later be passed to remove_items.
+ **/
+gpointer
+e_event_add_items (EEvent *event,
+ GSList *items,
+ EEventItemsFunc freefunc,
+ gpointer data)
+{
+ struct _event_node *node;
+
+ node = g_malloc (sizeof (*node));
+ node->events = items;
+ node->freefunc = freefunc;
+ node->data = data;
+
+ g_queue_push_tail (&event->priv->events, node);
+
+ if (event->priv->sorted) {
+ g_slist_foreach (event->priv->sorted, (GFunc) g_free, NULL);
+ g_slist_free (event->priv->sorted);
+ event->priv->sorted = NULL;
+ }
+
+ return (gpointer) node;
+}
+
+/**
+ * e_event_remove_items:
+ * @event: an #EEvent
+ * @handle: an opaque key returned by e_event_add_items()
+ *
+ * Remove items previously added. They MUST have been previously
+ * added, and may only be removed once.
+ **/
+void
+e_event_remove_items (EEvent *event,
+ gpointer handle)
+{
+ struct _event_node *node = handle;
+
+ g_queue_remove (&event->priv->events, node);
+
+ if (node->freefunc)
+ node->freefunc (event, node->events, node->data);
+ g_free (node);
+
+ if (event->priv->sorted) {
+ g_slist_foreach (event->priv->sorted, (GFunc) g_free, NULL);
+ g_slist_free (event->priv->sorted);
+ event->priv->sorted = NULL;
+ }
+}
+
+static gint
+ee_cmp (gconstpointer ap,
+ gconstpointer bp)
+{
+ gint a = ((struct _event_info **) ap)[0]->item->priority;
+ gint b = ((struct _event_info **) bp)[0]->item->priority;
+
+ if (a < b)
+ return 1;
+ else if (a > b)
+ return -1;
+ else
+ return 0;
+}
+
+/**
+ * e_event_emit:
+ * event: An initialised EEvent, potentially with registered event listeners.
+ * @id: Event name. This will be compared against EEventItem.id.
+ * @target: The target describing the event context. This will be
+ * implementation defined.
+ *
+ * Emit an event. @target will automatically be freed once its
+ * emission is complete.
+ **/
+void
+e_event_emit (EEvent *event,
+ const gchar *id,
+ EEventTarget *target)
+{
+ EEventPrivate *p = event->priv;
+ GSList *events;
+
+ d (printf ("emit event %s\n", id));
+
+ if (event->target != NULL) {
+ g_warning ("Event already in progress.\n");
+ return;
+ }
+
+ event->target = target;
+ events = p->sorted;
+ if (events == NULL) {
+ GList *link = g_queue_peek_head_link (&p->events);
+
+ while (link != NULL) {
+ struct _event_node *node = link->data;
+ GSList *l = node->events;
+
+ for (; l; l = g_slist_next (l)) {
+ struct _event_info *info;
+
+ info = g_malloc0 (sizeof (*info));
+ info->parent = node;
+ info->item = l->data;
+ events = g_slist_prepend (events, info);
+ }
+
+ link = g_list_next (link);
+ }
+
+ p->sorted = events = g_slist_sort (events, ee_cmp);
+ }
+
+ for (; events; events = g_slist_next (events)) {
+ struct _event_info *info = events->data;
+ EEventItem *item = info->item;
+
+ if (item->enable & target->mask)
+ continue;
+
+ if (strcmp (item->id, id) == 0) {
+ item->handle (event, item, info->parent->data);
+
+ if (item->type == E_EVENT_SINK)
+ break;
+ }
+ }
+
+ e_event_target_free (event, target);
+ event->target = NULL;
+}
+
+/**
+ * e_event_target_new:
+ * @event: An initialised EEvent instance.
+ * @type: type, up to implementor
+ * @size: The size of memory to allocate. This must be >= sizeof(EEventTarget).
+ *
+ * Allocate a new event target suitable for this class. It is up to
+ * the implementation to define the available target types and their
+ * structure.
+ **/
+gpointer
+e_event_target_new (EEvent *event,
+ gint type,
+ gsize size)
+{
+ EEventTarget *target;
+
+ if (size < sizeof (EEventTarget)) {
+ g_warning ("Size is less than the size of EEventTarget\n");
+ size = sizeof (EEventTarget);
+ }
+
+ target = g_malloc0 (size);
+ target->event = g_object_ref (event);
+ target->type = type;
+
+ return target;
+}
+
+/**
+ * e_event_target_free:
+ * @event: An initialised EEvent instance on which this target was allocated.
+ * @target: The target to free.
+ *
+ * Free a target. This invokes the virtual free method on the EEventClass.
+ **/
+void
+e_event_target_free (EEvent *event,
+ gpointer target)
+{
+ E_EVENT_GET_CLASS (event)->target_free (
+ event, (EEventTarget *) target);
+}
+
+/* ********************************************************************** */
+
+/* Event menu plugin handler */
+
+/*
+ * <e-plugin
+ * class="org.gnome.mail.plugin.event:1.0"
+ * id="org.gnome.mail.plugin.event.item:1.0"
+ * type="shlib"
+ * location="/opt/gnome2/lib/camel/1.0/libcamelimap.so"
+ * name="imap"
+ * description="IMAP4 and IMAP4v1 mail store">
+ * <hook class="org.gnome.mail.eventMenu:1.0"
+ * handler="HandleEvent">
+ * <menu id="any" target="select">
+ * <item
+ * type="item|toggle|radio|image|submenu|bar"
+ * active
+ * path="foo/bar"
+ * label="label"
+ * icon="foo"
+ * mask="select_one"
+ * activate="ep_view_emacs"/>
+ * </menu>
+ * </hook>
+ *
+ * <hook class="org.gnome.evolution.mail.events:1.0">
+ * <event id=".folder.changed"
+ * target=""
+ * priority="0"
+ * handle="gotevent"
+ * enable="new"
+ * />
+ * <event id=".message.read"
+ * priority="0"
+ * handle="gotevent"
+ * mask="new"
+ * />
+ * </hook>
+ *
+ */
+
+#define emph ((EEventHook *)eph)
+
+/* must have 1:1 correspondence with e-event types in order */
+static const EPluginHookTargetKey emph_item_types[] = {
+ { "pass", E_EVENT_PASS },
+ { "sink", E_EVENT_SINK },
+ { NULL }
+};
+
+G_DEFINE_TYPE (
+ EEventHook,
+ e_event_hook,
+ E_TYPE_PLUGIN_HOOK)
+
+static void
+emph_event_handle (EEvent *ee,
+ EEventItem *item,
+ gpointer data)
+{
+ EEventHook *hook = data;
+
+ /* FIXME We could/should just remove the items
+ * we added to the event handler. */
+ if (!hook->hook.plugin->enabled)
+ return;
+
+ e_plugin_invoke (
+ hook->hook.plugin, (gchar *) item->user_data, ee->target);
+}
+
+static void
+emph_free_item (EEventItem *item)
+{
+ g_free ((gchar *) item->id);
+ g_free (item->user_data);
+ g_free (item);
+}
+
+static void
+emph_free_items (EEvent *ee,
+ GSList *items,
+ gpointer data)
+{
+ /*EPluginHook *eph = data;*/
+
+ g_slist_foreach (items, (GFunc) emph_free_item, NULL);
+ g_slist_free (items);
+}
+
+static EEventItem *
+emph_construct_item (EPluginHook *eph,
+ xmlNodePtr root,
+ EEventHookClass *class)
+{
+ EEventItem *item;
+ EEventHookTargetMap *map;
+ gchar *tmp;
+
+ item = g_malloc0 (sizeof (*item));
+
+ tmp = (gchar *) xmlGetProp (root, (const guchar *)"target");
+ if (tmp == NULL)
+ goto error;
+ map = g_hash_table_lookup (class->target_map, tmp);
+ xmlFree (tmp);
+ if (map == NULL)
+ goto error;
+ item->target_type = map->id;
+ item->type = e_plugin_hook_id (root, emph_item_types, "type");
+ if (item->type == -1)
+ item->type = E_EVENT_PASS;
+ item->priority = e_plugin_xml_int (root, "priority", 0);
+ item->id = e_plugin_xml_prop (root, "id");
+ item->enable = e_plugin_hook_mask (root, map->mask_bits, "enable");
+ item->user_data = e_plugin_xml_prop (root, "handle");
+
+ if (item->user_data == NULL || item->id == NULL)
+ goto error;
+
+ item->handle = emph_event_handle;
+
+ return item;
+error:
+ emph_free_item (item);
+ return NULL;
+}
+
+static gint
+emph_construct (EPluginHook *eph,
+ EPlugin *ep,
+ xmlNodePtr root)
+{
+ xmlNodePtr node;
+ EEventHookClass *class;
+ GSList *items = NULL;
+
+ d (printf ("loading event hook\n"));
+
+ if (((EPluginHookClass *) e_event_hook_parent_class)->
+ construct (eph, ep, root) == -1)
+ return -1;
+
+ class = E_EVENT_HOOK_GET_CLASS (eph);
+ g_return_val_if_fail (class->event != NULL, -1);
+
+ node = root->children;
+ while (node) {
+ if (strcmp ((gchar *) node->name, "event") == 0) {
+ EEventItem *item;
+
+ item = emph_construct_item (eph, node, class);
+ if (item)
+ items = g_slist_prepend (items, item);
+ }
+ node = node->next;
+ }
+
+ eph->plugin = ep;
+
+ if (items)
+ e_event_add_items (class->event, items, emph_free_items, eph);
+
+ return 0;
+}
+
+static void
+e_event_hook_class_init (EEventHookClass *class)
+{
+ EPluginHookClass *plugin_hook_class;
+
+ plugin_hook_class = E_PLUGIN_HOOK_CLASS (class);
+ plugin_hook_class->id = "org.gnome.evolution.event:1.0";
+ plugin_hook_class->construct = emph_construct;
+
+ class->target_map = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+static void
+e_event_hook_init (EEventHook *hook)
+{
+}
+
+/**
+ * e_event_hook_class_add_target_map:
+ * @hook_class: The derived EEventHook class.
+ * @map: A map used to describe a single EEventTarget type for this class.
+ *
+ * Add a target map to a concrete derived class of EEvent. The target
+ * map enumerates a single target type and th eenable mask bit names,
+ * so that the type can be loaded automatically by the base EEvent class.
+ **/
+void
+e_event_hook_class_add_target_map (EEventHookClass *hook_class,
+ const EEventHookTargetMap *map)
+{
+ g_hash_table_insert (
+ hook_class->target_map,
+ (gpointer) map->type, (gpointer) map);
+}