/*
* 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 */
};
static gpointer parent_class;
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 (parent_class)->finalize (object);
}
static void
event_target_free (EEvent *event,
EEventTarget *target)
{
g_free (target);
g_object_unref (event);
}
static void
event_class_init (EEventClass *class)
{
GObjectClass *object_class;
parent_class = g_type_class_peek_parent (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
event_init (EEvent *event)
{
event->priv = E_EVENT_GET_PRIVATE (event);
g_queue_init (&event->priv->events);
}
GType
e_event_get_type(void)
{
static GType type = 0;
if (G_UNLIKELY (type == 0)) {
static const GTypeInfo type_info = {
sizeof (EEventClass),
(GBaseInitFunc) NULL,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) event_class_init,
(GClassFinalizeFunc) NULL,
NULL, /* class_data */
sizeof (EEvent),
0, /* n_preallocs */
(GInstanceInitFunc) event_init,
NULL /* value_table */
};
type = g_type_register_static (
G_TYPE_OBJECT, "EEvent", &type_info, 0);
}
return type;
}
/**
* e_event_construct:
* @ep: An instantiated but uninitialised EEvent.
* @id: Event manager id.
*
* Construct the base event instance with standard parameters.
*
* Return value: Returns @ep.
**/
EEvent *e_event_construct(EEvent *ep, const gchar *id)
{
ep->id = g_strdup(id);
return ep;
}
/**
* 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:
* @handle:
*
* 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:
* @ee: 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;
d(printf("event '%s' mask %08x target %08x\n", item->id, item->enable, target->mask));
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:
* @ep: 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:
* @ep: An initialised EEvent instance on which this target was allocated.
* @o: The target to free.
*
* Free a target. This invokes the virtual free method on the EEventClass.
**/
void
e_event_target_free (EEvent *event,
gpointer object)
{
EEventTarget *target = object;
E_EVENT_GET_CLASS (event)->target_free (event, 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>
</extension>
<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>
*/
static gpointer emph_parent_class;
#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 }
};
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;
g_return_val_if_fail(((EEventHookClass *)G_OBJECT_GET_CLASS(eph))->event != NULL, -1);
d(printf("loading event hook\n"));
if (((EPluginHookClass *)emph_parent_class)->construct(eph, ep, root) == -1)
return -1;
class = (EEventHookClass *)G_OBJECT_GET_CLASS(eph);
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
emph_class_init (EEventHookClass *class)
{
EPluginHookClass *plugin_hook_class;
emph_parent_class = g_type_class_peek_parent (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);
}
/**
* e_event_hook_get_type:
*
* Standard GObject function to get the EEvent object type. Used to
* subclass EEventHook.
*
* Return value: The type of the event hook class.
**/
GType
e_event_hook_get_type(void)
{
static GType type = 0;
if (G_UNLIKELY (type == 0)) {
static const GTypeInfo type_info = {
sizeof (EEventHookClass),
(GBaseInitFunc) NULL,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) emph_class_init,
(GClassFinalizeFunc) NULL,
NULL, /* class_data */
sizeof (EEventHook),
0, /* n_preallocs */
(GInstanceInitFunc) NULL,
NULL /* value_table */
};
type = g_type_register_static (
E_TYPE_PLUGIN_HOOK, "EEventHook", &type_info, 0);
}
return type;
}
/**
* e_event_hook_class_add_target_map:
* @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 *class, const EEventHookTargetMap *map)
{
g_hash_table_insert(class->target_map, (gpointer)map->type, (gpointer)map);
}