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