1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, see <http://www.gnu.org/licenses/>.
13  *
14  *
15  * Authors:
16  *		Michael Zucchi <notzed@ximian.com>
17  *
18  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
19  *
20  */
21 
22 #include "evolution-config.h"
23 
24 #include <string.h>
25 #include <stdlib.h>
26 
27 #include <gtk/gtk.h>
28 
29 #include "e-event.h"
30 
31 #include <glib/gi18n.h>
32 
33 #define E_EVENT_GET_PRIVATE(obj) \
34 	(G_TYPE_INSTANCE_GET_PRIVATE \
35 	((obj), E_TYPE_EVENT, EEventPrivate))
36 
37 #define d(x)
38 
39 struct _event_node {
40 	GSList *events;
41 	gpointer data;
42 	EEventItemsFunc freefunc;
43 };
44 
45 struct _event_info {
46 	struct _event_node *parent;
47 	EEventItem *item;
48 };
49 
50 struct _EEventPrivate {
51 	GQueue events;
52 	GSList *sorted;		/* sorted list of struct _event_info's */
53 };
54 
G_DEFINE_TYPE(EEvent,e_event,G_TYPE_OBJECT)55 G_DEFINE_TYPE (
56 	EEvent,
57 	e_event,
58 	G_TYPE_OBJECT)
59 
60 static void
61 event_finalize (GObject *object)
62 {
63 	EEvent *event = (EEvent *) object;
64 	EEventPrivate *p = event->priv;
65 
66 	if (event->target)
67 		e_event_target_free (event, event->target);
68 
69 	g_free (event->id);
70 
71 	while (!g_queue_is_empty (&p->events)) {
72 		struct _event_node *node;
73 
74 		node = g_queue_pop_head (&p->events);
75 
76 		if (node->freefunc != NULL)
77 			node->freefunc (event, node->events, node->data);
78 
79 		g_free (node);
80 	}
81 
82 	g_slist_foreach (p->sorted, (GFunc) g_free, NULL);
83 	g_slist_free (p->sorted);
84 
85 	/* Chain up to parent's finalize() method. */
86 	G_OBJECT_CLASS (e_event_parent_class)->finalize (object);
87 }
88 
89 static void
event_target_free(EEvent * event,EEventTarget * target)90 event_target_free (EEvent *event,
91                    EEventTarget *target)
92 {
93 	g_free (target);
94 	g_object_unref (event);
95 }
96 
97 static void
e_event_class_init(EEventClass * class)98 e_event_class_init (EEventClass *class)
99 {
100 	GObjectClass *object_class;
101 
102 	g_type_class_add_private (class, sizeof (EEventPrivate));
103 
104 	object_class = G_OBJECT_CLASS (class);
105 	object_class->finalize = event_finalize;
106 
107 	class->target_free = event_target_free;
108 }
109 
110 static void
e_event_init(EEvent * event)111 e_event_init (EEvent *event)
112 {
113 	event->priv = E_EVENT_GET_PRIVATE (event);
114 
115 	g_queue_init (&event->priv->events);
116 }
117 
118 /**
119  * e_event_construct:
120  * @event: An instantiated but uninitialised EEvent.
121  * @id: Event manager id.
122  *
123  * Construct the base event instance with standard parameters.
124  *
125  * Returns: the @event
126  **/
127 EEvent *
e_event_construct(EEvent * event,const gchar * id)128 e_event_construct (EEvent *event,
129                    const gchar *id)
130 {
131 	event->id = g_strdup (id);
132 
133 	return event;
134 }
135 
136 /**
137  * e_event_add_items:
138  * @event: An initialised EEvent structure.
139  * @items: A list of EEventItems event listeners to register on this event manager.
140  * @freefunc: A function called when the @items list is no longer needed.
141  * @data: callback data for @freefunc and for item event handlers.
142  *
143  * Adds @items to the list of events listened to on the event manager @event.
144  *
145  * Return value: An opaque key which can later be passed to remove_items.
146  **/
147 gpointer
e_event_add_items(EEvent * event,GSList * items,EEventItemsFunc freefunc,gpointer data)148 e_event_add_items (EEvent *event,
149                    GSList *items,
150                    EEventItemsFunc freefunc,
151                    gpointer data)
152 {
153 	struct _event_node *node;
154 
155 	node = g_malloc (sizeof (*node));
156 	node->events = items;
157 	node->freefunc = freefunc;
158 	node->data = data;
159 
160 	g_queue_push_tail (&event->priv->events, node);
161 
162 	if (event->priv->sorted) {
163 		g_slist_foreach (event->priv->sorted, (GFunc) g_free, NULL);
164 		g_slist_free (event->priv->sorted);
165 		event->priv->sorted = NULL;
166 	}
167 
168 	return (gpointer) node;
169 }
170 
171 /**
172  * e_event_remove_items:
173  * @event: an #EEvent
174  * @handle: an opaque key returned by e_event_add_items()
175  *
176  * Remove items previously added.  They MUST have been previously
177  * added, and may only be removed once.
178  **/
179 void
e_event_remove_items(EEvent * event,gpointer handle)180 e_event_remove_items (EEvent *event,
181                       gpointer handle)
182 {
183 	struct _event_node *node = handle;
184 
185 	g_queue_remove (&event->priv->events, node);
186 
187 	if (node->freefunc)
188 		node->freefunc (event, node->events, node->data);
189 	g_free (node);
190 
191 	if (event->priv->sorted) {
192 		g_slist_foreach (event->priv->sorted, (GFunc) g_free, NULL);
193 		g_slist_free (event->priv->sorted);
194 		event->priv->sorted = NULL;
195 	}
196 }
197 
198 static gint
ee_cmp(gconstpointer ap,gconstpointer bp)199 ee_cmp (gconstpointer ap,
200         gconstpointer bp)
201 {
202 	gint a = ((struct _event_info **) ap)[0]->item->priority;
203 	gint b = ((struct _event_info **) bp)[0]->item->priority;
204 
205 	if (a < b)
206 		return 1;
207 	else if (a > b)
208 		return -1;
209 	else
210 		return 0;
211 }
212 
213 /**
214  * e_event_emit:
215  * event: An initialised EEvent, potentially with registered event listeners.
216  * @id: Event name.  This will be compared against EEventItem.id.
217  * @target: The target describing the event context.  This will be
218  * implementation defined.
219  *
220  * Emit an event.  @target will automatically be freed once its
221  * emission is complete.
222  **/
223 void
e_event_emit(EEvent * event,const gchar * id,EEventTarget * target)224 e_event_emit (EEvent *event,
225               const gchar *id,
226               EEventTarget *target)
227 {
228 	EEventPrivate *p = event->priv;
229 	GSList *events;
230 
231 	d (printf ("emit event %s\n", id));
232 
233 	if (event->target != NULL) {
234 		g_warning ("Event already in progress.\n");
235 		return;
236 	}
237 
238 	event->target = target;
239 	events = p->sorted;
240 	if (events == NULL) {
241 		GList *link = g_queue_peek_head_link (&p->events);
242 
243 		while (link != NULL) {
244 			struct _event_node *node = link->data;
245 			GSList *l = node->events;
246 
247 			for (; l; l = g_slist_next (l)) {
248 				struct _event_info *info;
249 
250 				info = g_malloc0 (sizeof (*info));
251 				info->parent = node;
252 				info->item = l->data;
253 				events = g_slist_prepend (events, info);
254 			}
255 
256 			link = g_list_next (link);
257 		}
258 
259 		p->sorted = events = g_slist_sort (events, ee_cmp);
260 	}
261 
262 	for (; events; events = g_slist_next (events)) {
263 		struct _event_info *info = events->data;
264 		EEventItem *item = info->item;
265 
266 		if (item->enable & target->mask)
267 			continue;
268 
269 		if (strcmp (item->id, id) == 0) {
270 			item->handle (event, item, info->parent->data);
271 
272 			if (item->type == E_EVENT_SINK)
273 				break;
274 		}
275 	}
276 
277 	e_event_target_free (event, target);
278 	event->target = NULL;
279 }
280 
281 /**
282  * e_event_target_new:
283  * @event: An initialised EEvent instance.
284  * @type: type, up to implementor
285  * @size: The size of memory to allocate.  This must be >= sizeof(EEventTarget).
286  *
287  * Allocate a new event target suitable for this class.  It is up to
288  * the implementation to define the available target types and their
289  * structure.
290  **/
291 gpointer
e_event_target_new(EEvent * event,gint type,gsize size)292 e_event_target_new (EEvent *event,
293                     gint type,
294                     gsize size)
295 {
296 	EEventTarget *target;
297 
298 	if (size < sizeof (EEventTarget)) {
299 		g_warning ("Size is less than the size of EEventTarget\n");
300 		size = sizeof (EEventTarget);
301 	}
302 
303 	target = g_malloc0 (size);
304 	target->event = g_object_ref (event);
305 	target->type = type;
306 
307 	return target;
308 }
309 
310 /**
311  * e_event_target_free:
312  * @event: An initialised EEvent instance on which this target was allocated.
313  * @target: The target to free.
314  *
315  * Free a target.  This invokes the virtual free method on the EEventClass.
316  **/
317 void
e_event_target_free(EEvent * event,gpointer target)318 e_event_target_free (EEvent *event,
319                      gpointer target)
320 {
321 	E_EVENT_GET_CLASS (event)->target_free (
322 		event, (EEventTarget *) target);
323 }
324 
325 /* ********************************************************************** */
326 
327 /* Event menu plugin handler */
328 
329 /*
330  * <e-plugin
331  *  class="org.gnome.mail.plugin.event:1.0"
332  *  id="org.gnome.mail.plugin.event.item:1.0"
333  *  type="shlib"
334  *  location="/opt/gnome2/lib/camel/1.0/libcamelimap.so"
335  *  name="imap"
336  *  description="IMAP4 and IMAP4v1 mail store">
337  *  <hook class="org.gnome.mail.eventMenu:1.0"
338  *      handler="HandleEvent">
339  *   <menu id="any" target="select">
340  *   <item
341  *    type="item|toggle|radio|image|submenu|bar"
342  *    active
343  *    path="foo/bar"
344  *    label="label"
345  *    icon="foo"
346  *    mask="select_one"
347  *    activate="ep_view_emacs"/>
348  *   </menu>
349  *  </hook>
350  *
351  * <hook class="org.gnome.evolution.mail.events:1.0">
352  * <event id=".folder.changed"
353  *  target=""
354  *  priority="0"
355  *  handle="gotevent"
356  *  enable="new"
357  *  />
358  * <event id=".message.read"
359  *  priority="0"
360  *  handle="gotevent"
361  *  mask="new"
362  *  />
363  * </hook>
364  *
365  */
366 
367 #define emph ((EEventHook *)eph)
368 
369 /* must have 1:1 correspondence with e-event types in order */
370 static const EPluginHookTargetKey emph_item_types[] = {
371 	{ "pass", E_EVENT_PASS },
372 	{ "sink", E_EVENT_SINK },
373 	{ NULL }
374 };
375 
G_DEFINE_TYPE(EEventHook,e_event_hook,E_TYPE_PLUGIN_HOOK)376 G_DEFINE_TYPE (
377 	EEventHook,
378 	e_event_hook,
379 	E_TYPE_PLUGIN_HOOK)
380 
381 static void
382 emph_event_handle (EEvent *ee,
383                    EEventItem *item,
384                    gpointer data)
385 {
386 	EEventHook *hook = data;
387 
388 	/* FIXME We could/should just remove the items
389 	 *       we added to the event handler. */
390 	if (!hook->hook.plugin->enabled)
391 		return;
392 
393 	e_plugin_invoke (
394 		hook->hook.plugin, (gchar *) item->user_data, ee->target);
395 }
396 
397 static void
emph_free_item(EEventItem * item)398 emph_free_item (EEventItem *item)
399 {
400 	g_free ((gchar *) item->id);
401 	g_free (item->user_data);
402 	g_free (item);
403 }
404 
405 static void
emph_free_items(EEvent * ee,GSList * items,gpointer data)406 emph_free_items (EEvent *ee,
407                  GSList *items,
408                  gpointer data)
409 {
410 	/*EPluginHook *eph = data;*/
411 
412 	g_slist_foreach (items, (GFunc) emph_free_item, NULL);
413 	g_slist_free (items);
414 }
415 
416 static EEventItem *
emph_construct_item(EPluginHook * eph,xmlNodePtr root,EEventHookClass * class)417 emph_construct_item (EPluginHook *eph,
418                      xmlNodePtr root,
419                      EEventHookClass *class)
420 {
421 	EEventItem *item;
422 	EEventHookTargetMap *map;
423 	gchar *tmp;
424 
425 	item = g_malloc0 (sizeof (*item));
426 
427 	tmp = (gchar *) xmlGetProp (root, (const guchar *)"target");
428 	if (tmp == NULL)
429 		goto error;
430 	map = g_hash_table_lookup (class->target_map, tmp);
431 	xmlFree (tmp);
432 	if (map == NULL)
433 		goto error;
434 	item->target_type = map->id;
435 	item->type = e_plugin_hook_id (root, emph_item_types, "type");
436 	if (item->type == E_EVENT_INVALID)
437 		item->type = E_EVENT_PASS;
438 	item->priority = e_plugin_xml_int (root, "priority", 0);
439 	item->id = e_plugin_xml_prop (root, "id");
440 	item->enable = e_plugin_hook_mask (root, map->mask_bits, "enable");
441 	item->user_data = e_plugin_xml_prop (root, "handle");
442 
443 	if (item->user_data == NULL || item->id == NULL)
444 		goto error;
445 
446 	item->handle = emph_event_handle;
447 
448 	return item;
449 error:
450 	emph_free_item (item);
451 	return NULL;
452 }
453 
454 static gint
emph_construct(EPluginHook * eph,EPlugin * ep,xmlNodePtr root)455 emph_construct (EPluginHook *eph,
456                 EPlugin *ep,
457                 xmlNodePtr root)
458 {
459 	xmlNodePtr node;
460 	EEventHookClass *class;
461 	GSList *items = NULL;
462 
463 	d (printf ("loading event hook\n"));
464 
465 	if (((EPluginHookClass *) e_event_hook_parent_class)->
466 		construct (eph, ep, root) == -1)
467 		return -1;
468 
469 	class = E_EVENT_HOOK_GET_CLASS (eph);
470 	g_return_val_if_fail (class->event != NULL, -1);
471 
472 	node = root->children;
473 	while (node) {
474 		if (strcmp ((gchar *) node->name, "event") == 0) {
475 			EEventItem *item;
476 
477 			item = emph_construct_item (eph, node, class);
478 			if (item)
479 				items = g_slist_prepend (items, item);
480 		}
481 		node = node->next;
482 	}
483 
484 	eph->plugin = ep;
485 
486 	if (items)
487 		e_event_add_items (class->event, items, emph_free_items, eph);
488 
489 	return 0;
490 }
491 
492 static void
e_event_hook_class_init(EEventHookClass * class)493 e_event_hook_class_init (EEventHookClass *class)
494 {
495 	EPluginHookClass *plugin_hook_class;
496 
497 	plugin_hook_class = E_PLUGIN_HOOK_CLASS (class);
498 	plugin_hook_class->id = "org.gnome.evolution.event:1.0";
499 	plugin_hook_class->construct = emph_construct;
500 
501 	class->target_map = g_hash_table_new (g_str_hash, g_str_equal);
502 }
503 
504 static void
e_event_hook_init(EEventHook * hook)505 e_event_hook_init (EEventHook *hook)
506 {
507 }
508 
509 /**
510  * e_event_hook_class_add_target_map:
511  * @hook_class: The derived EEventHook class.
512  * @map: A map used to describe a single EEventTarget type for this class.
513  *
514  * Add a target map to a concrete derived class of EEvent.  The target
515  * map enumerates a single target type and th eenable mask bit names,
516  * so that the type can be loaded automatically by the base EEvent class.
517  **/
518 void
e_event_hook_class_add_target_map(EEventHookClass * hook_class,const EEventHookTargetMap * map)519 e_event_hook_class_add_target_map (EEventHookClass *hook_class,
520                                    const EEventHookTargetMap *map)
521 {
522 	g_hash_table_insert (
523 		hook_class->target_map,
524 		(gpointer) map->type, (gpointer) map);
525 }
526