/*
 * 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 <glib.h>

#include <glib/gi18n.h>
#include <bonobo/bonobo-ui-util.h>

#include <libedataserver/e-data-server-util.h>

#include "e-menu.h"
#include "e-icon-factory.h"

#define d(x)

struct _EMenuFactory {
	struct _EMenuFactory *next, *prev;

	gchar *menuid;
	EMenuFactoryFunc factory;
	gpointer factory_data;
};

struct _item_node {
	struct _item_node *next;

	EMenuItem *item;
	struct _menu_node *menu;
};

struct _menu_node {
	struct _menu_node *next, *prev;

	EMenu *parent;

	GSList *items;
	GSList *uis;
	GSList *pixmaps;

	EMenuItemsFunc freefunc;
	gpointer data;

	/* a copy of items wrapped in an item_node, for bonobo
	 * callback mapping */
	struct _item_node *menu;
};

struct _EMenuPrivate {
	EDList menus;
};

static GObjectClass *em_parent;

static void
em_init(GObject *o)
{
	EMenu *emp = (EMenu *)o;
	struct _EMenuPrivate *p;

	p = emp->priv = g_malloc0(sizeof(struct _EMenuPrivate));

	e_dlist_init(&p->menus);
}

static void
em_finalise(GObject *o)
{
	EMenu *em = (EMenu *)o;
	struct _EMenuPrivate *p = em->priv;
	struct _menu_node *mnode;

	if (em->target)
		e_menu_target_free(em, em->target);
	g_free(em->menuid);

	while ((mnode = (struct _menu_node *)e_dlist_remhead(&p->menus))) {
		struct _item_node *inode;

		if (mnode->freefunc)
			mnode->freefunc(em, mnode->items, mnode->uis, mnode->pixmaps, mnode->data);

		inode = mnode->menu;
		while (inode) {
			struct _item_node *nnode = inode->next;

			g_free(inode);
			inode = nnode;
		}

		g_free(mnode);
	}

	g_free(p);

	((GObjectClass *)em_parent)->finalize(o);
}

static void
em_target_free(EMenu *ep, EMenuTarget *t)
{
	g_free(t);
	/* look funny but t has a reference to us */
	g_object_unref(ep);
}

static void
em_class_init(GObjectClass *klass)
{
	d(printf("EMenu class init %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type)));

	klass->finalize = em_finalise;
	((EMenuClass *)klass)->target_free = em_target_free;
}

static void
em_base_init(GObjectClass *klass)
{
	/* each class instance must have its own list, it isn't inherited */
	d(printf("%p: list init\n", klass));
	e_dlist_init(&((EMenuClass *)klass)->factories);
}

/**
 * e_menu_get_type:
 *
 * Standard GObject type function.  Used to subclass this type only.
 *
 * Return value: The EMenu object type.
 **/
GType
e_menu_get_type(void)
{
	static GType type = 0;

	if (type == 0) {
		static const GTypeInfo info = {
			sizeof(EMenuClass),
			(GBaseInitFunc)em_base_init, NULL,
			(GClassInitFunc)em_class_init,
			NULL, NULL,
			sizeof(EMenu), 0,
			(GInstanceInitFunc)em_init
		};
		em_parent = g_type_class_ref(G_TYPE_OBJECT);
		type = g_type_register_static(G_TYPE_OBJECT, "EMenu", &info, 0);
	}

	return type;
}

/**
 * e_menu_construct:
 * @em: An instantiated but uninitislied EPopup.
 * @menuid: The unique identifier for this menu.
 *
 * Construct the base menu instance based on the parameters.
 *
 * Return value: Returns @em.
 **/
EMenu *e_menu_construct(EMenu *em, const gchar *menuid)
{
	struct _EMenuFactory *f;
	EMenuClass *klass;

	d(printf("constructing menu '%s'\n", menuid));

	klass = (EMenuClass *)G_OBJECT_GET_CLASS(em);

	d(printf("   class is %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type)));

	em->menuid = g_strdup(menuid);

	/* setup the menu itself based on factories */
	f = (struct _EMenuFactory *)klass->factories.head;
	if (f->next == NULL) {
		d(printf("%p no factories registered on menu\n", klass));
	}

	while (f->next) {
		if (f->menuid == NULL
		    || !strcmp(f->menuid, em->menuid)) {
			d(printf("  calling factory\n"));
			f->factory(em, f->factory_data);
		}
		f = f->next;
	}

	return em;
}

/**
 * e_menu_add_items:
 * @emp: An initialised EMenu.
 * @items: A list of EMenuItems or derived structures defining a group
 * of menu items for this menu.
 * @uifiles: A list of EMenuUIFile objects describing all ui files
 * associated with the items.
 * @pixmaps: A list of EMenuPixmap objects describing all pixmaps
 * associated with the menus.
 * @freefunc: If supplied, called when the menu items are no longer needed.
 * @data: user-data passed to @freefunc and activate callbacks.
 *
 * Add new EMenuItems to the menu's.  This may be called any number of
 * times before the menu is first activated to hook onto any of the
 * menu items defined for that view.
 *
 * Return value: A handle that can be passed to remove_items as required.
 **/
gpointer
e_menu_add_items(EMenu *emp, GSList *items, GSList *uifiles, GSList *pixmaps, EMenuItemsFunc freefunc, gpointer data)
{
	struct _menu_node *node;
	GSList *l;

	node = g_malloc0(sizeof(*node));
	node->parent = emp;
	node->items = items;
	node->uis = uifiles;
	node->pixmaps = pixmaps;
	node->freefunc = freefunc;
	node->data = data;

	for (l=items;l;l=g_slist_next(l)) {
		struct _item_node *inode = g_malloc0(sizeof(*inode));
		EMenuItem *item = l->data;

		inode->item = item;
		inode->menu = node;
		inode->next = node->menu;
		node->menu = inode;
	}

	for (l=pixmaps;l;l=g_slist_next(l)) {
		EMenuPixmap *pixmap = l->data;

		if (pixmap->pixmap == NULL) {
			GdkPixbuf *pixbuf;

			pixbuf = e_icon_factory_get_icon(pixmap->name, pixmap->size);
			if (pixbuf == NULL) {
				g_warning("Unable to load icon '%s'", pixmap->name);
			} else {
				pixmap->pixmap = bonobo_ui_util_pixbuf_to_xml(pixbuf);
				g_object_unref(pixbuf);
			}
		}
	}

	e_dlist_addtail(&emp->priv->menus, (EDListNode *)node);

	/* FIXME: add the menu's to a running menu if it is there? */

	return (gpointer)node;
}

/**
 * e_menu_remove_items:
 * @emp:
 * @handle:
 *
 * Remove menu items previously added.
 **/
void
e_menu_remove_items(EMenu *emp, gpointer handle)
{
	struct _menu_node *node = handle;
	struct _item_node *inode;
	GSList *l;

	e_dlist_remove((EDListNode *)node);

	if (emp->uic) {
		for (l = node->items;l;l=g_slist_next(l)) {
			EMenuItem *item = l->data;

			bonobo_ui_component_remove_verb(emp->uic, item->verb);
		}
	}

	if (node->freefunc)
		node->freefunc(emp, node->items, node->uis, node->pixmaps, node->data);

	inode = node->menu;
	while (inode) {
		struct _item_node *nnode = inode->next;

		g_free(inode);
		inode = nnode;
	}

	g_free(node);
}

static void
em_activate_toggle(BonoboUIComponent *component, const gchar *path, Bonobo_UIComponent_EventType type, const gchar *state, gpointer data)
{
	struct _item_node *inode = data;

	if (type != Bonobo_UIComponent_STATE_CHANGED)
		return;

	((EMenuToggleActivateFunc)inode->item->activate)(inode->menu->parent, inode->item, state[0] != '0', inode->menu->data);
}

static void
em_activate(BonoboUIComponent *uic, gpointer data, const gchar *cname)
{
	struct _item_node *inode = data;

	((EMenuActivateFunc)inode->item->activate)(inode->menu->parent, inode->item, inode->menu->data);
}

/**
 * e_menu_activate:
 * @em: An initialised EMenu.
 * @uic: The BonoboUI component for this views menu's.
 * @act: If %TRUE, then the control is being activated.
 *
 * This is called by the owner of the component, control, or view to
 * pass on the activate or deactivate control signals.  If the view is
 * being activated then the callbacks and menu items are setup,
 * otherwise they are removed.
 *
 * This should always be called in the strict sequence of activate, then
 * deactivate, repeated any number of times.
 **/
void e_menu_activate(EMenu *em, struct _BonoboUIComponent *uic, gint act)
{
	struct _EMenuPrivate *p = em->priv;
	struct _menu_node *mw;
	GSList *l;

	if (act) {
		GArray *verbs;
		gint i;

		em->uic = uic;

		verbs = g_array_new(TRUE, FALSE, sizeof(BonoboUIVerb));
		for (mw = (struct _menu_node *)p->menus.head;mw->next;mw=mw->next) {
			struct _item_node *inode;

			for (l = mw->uis; l; l = g_slist_next(l)) {
				EMenuUIFile *ui = l->data;

				bonobo_ui_util_set_ui(uic, ui->appdir, ui->filename, ui->appname, NULL);
			}

			for (l = mw->pixmaps; l; l = g_slist_next(l)) {
				EMenuPixmap *pm = l->data;

				if (pm->pixmap)
					bonobo_ui_component_set_prop(uic, pm->command, "pixmap", pm->pixmap, NULL);
			}

			for (inode = mw->menu; inode; inode=inode->next) {
				EMenuItem *item = inode->item;
				BonoboUIVerb *verb;

				d(printf("adding menu verb '%s'\n", item->verb));

				switch (item->type & E_MENU_TYPE_MASK) {
				case E_MENU_ITEM:
					i = verbs->len;
					verbs = g_array_set_size(verbs, i+1);
					verb = &((BonoboUIVerb *)verbs->data)[i];

					verb->cname = item->verb;
					verb->cb = em_activate;
					verb->user_data = inode;
					break;
				case E_MENU_TOGGLE:
					bonobo_ui_component_set_prop(uic, item->path, "state", item->type & E_MENU_ACTIVE?"1":"0", NULL);
					bonobo_ui_component_add_listener(uic, item->verb, em_activate_toggle, inode);
					break;
				}
			}
		}

		if (verbs->len)
			bonobo_ui_component_add_verb_list(uic, (BonoboUIVerb *)verbs->data);

		g_array_free(verbs, TRUE);
	} else {
		for (mw = (struct _menu_node *)p->menus.head;mw->next;mw=mw->next) {
			for (l = mw->items;l;l=g_slist_next(l)) {
				EMenuItem *item = l->data;

				bonobo_ui_component_remove_verb(uic, item->verb);
			}
		}

		em->uic = NULL;
	}
}

/**
 * e_menu_update_target:
 * @em: An initialised EMenu.
 * @tp: Target, after this call the menu owns the target.
 *
 * Change the target for the menu.  Once the target is changed, the
 * sensitivity state of the menu items managed by @em is re-evaluated
 * and the physical menu's updated to reflect it.
 *
 * This is used by the owner of the menu and view to update the menu
 * system based on user input or changed system state.
 **/
void e_menu_update_target(EMenu *em, gpointer tp)
{
	struct _EMenuPrivate *p = em->priv;
	EMenuTarget *t = tp;
	guint32 mask = ~0;
	struct _menu_node *mw;
	GSList *l;

	if (em->target && em->target != t)
		e_menu_target_free(em, em->target);

	/* if we unset the target, should we disable/hide all the menu items? */
	em->target = t;
	if (t == NULL)
		return;

	mask = t->mask;

	/* canna do any more capt'n */
	if (em->uic == NULL)
		return;

	for (mw = (struct _menu_node *)p->menus.head;mw->next;mw=mw->next) {
		for (l = mw->items;l;l=g_slist_next(l)) {
			EMenuItem *item = l->data;
			gint state;

			d(printf("checking item '%s' mask %08x against target %08x\n", item->verb, item->enable, mask));

			state = (item->enable & mask) == 0;
			bonobo_ui_component_set_prop(em->uic, item->path, "sensitive", state?"1":"0", NULL);
			/* visible? */
		}
	}
}

/* ********************************************************************** */

/**
 * e_menu_class_add_factory:
 * @klass: An EMenuClass type to which this factory applies.
 * @menuid: The identifier of the menu for this factory, or NULL to be
 * called on all menus.
 * @func: An EMenuFactoryFunc callback.
 * @data: Callback data for @func.
 *
 * Add a menu factory which will be called when the menu @menuid is
 * created.  The factory is free to add new items as it wishes to the
 * menu provided in the callback.
 *
 * TODO: Make the menuid a pattern?
 *
 * Return value: A handle to the factory.
 **/
EMenuFactory *
e_menu_class_add_factory(EMenuClass *klass, const gchar *menuid, EMenuFactoryFunc func, gpointer data)
{
	struct _EMenuFactory *f = g_malloc0(sizeof(*f));

	d(printf("%p adding factory '%s' to class '%s'\n", klass, menuid?menuid:"<all menus>", g_type_name(((GObjectClass *)klass)->g_type_class.g_type)));

	f->menuid = g_strdup(menuid);
	f->factory = func;
	f->factory_data = data;
	e_dlist_addtail(&klass->factories, (EDListNode *)f);

	/* setup the menu itself based on factories */
	{
		struct _EMenuFactory *j;

		j = (struct _EMenuFactory *)klass->factories.head;
		if (j->next == NULL) {
			d(printf("%p no factories registered on menu???\n", klass));
		}
	}

	return f;
}

/**
 * e_menu_class_remove_factory:
 * @klass: Class on which the factory was originally added.
 * @f: Factory handle.
 *
 * Remove a popup factory.  This must only be called once, and must
 * only be called using a valid factory handle @f.  After this call,
 * @f is undefined.
 **/
void
e_menu_class_remove_factory(EMenuClass *klass, EMenuFactory *f)
{
	e_dlist_remove((EDListNode *)f);
	g_free(f->menuid);
	g_free(f);
}

/**
 * e_menu_target_new:
 * @ep: An EMenu to which this target applies.
 * @type: Target type, up to implementation.
 * @size: Size of memory to allocate.  Must be >= sizeof(EMenuTarget).
 *
 * Allocate a new menu target suitable for this class.  @size is used
 * to specify the actual target size, which may vary depending on the
 * implementing class.
 **/
gpointer e_menu_target_new(EMenu *ep, gint type, gsize size)
{
	EMenuTarget *t;

	if (size < sizeof(EMenuTarget)) {
		g_warning ("size less than size of EMenuTarget\n");
		size = sizeof (EMenuTarget);
	}

	t = g_malloc0(size);
	t->menu = ep;
	g_object_ref(ep);
	t->type = type;

	return t;
}

/**
 * e_menu_target_free:
 * @ep: EMenu on which the target was allocated.
 * @o: Tareget to free.
 *
 * Free a target.
 **/
void
e_menu_target_free(EMenu *ep, gpointer o)
{
	EMenuTarget *t = o;

	((EMenuClass *)G_OBJECT_GET_CLASS(ep))->target_free(ep, t);
}

/* ********************************************************************** */

/* Main menu plugin handler */

/* NB: This has significant overlap with EPopupHook */

/*
<e-plugin
  class="org.gnome.mail.plugin.popup:1.0"
  id="org.gnome.mail.plugin.popup.item:1.0"
  type="shlib"
  location="/opt/gnome2/lib/camel/1.0/libcamelimap.so"
  name="imap"
  description="Main menu plugin">
  <hook class="org.gnome.evolution.bonobomenu:1.0">
  <menu id="any" target="select" view="org.gnome.mail">
   <ui file="ui file1"/>
   <ui file="ui file2"/>
   <pixmap command="command" pixmap="stockname" size="menu|button|small_toolbar|large_toolbar|dnd|dialog"/>
   <item
    type="item|toggle"
    verb="verb"
    enable="select_one"
    visible="select_one"
    activate="doactivate"/>
   </menu>
  </hook>
  </extension>

*/

static gpointer emph_parent_class;
#define emph ((EMenuHook *)eph)

/* must have 1:1 correspondence with e-menu types in order */
static const EPluginHookTargetKey emph_item_types[] = {
	{ "item", E_MENU_ITEM },
	{ "toggle", E_MENU_TOGGLE },
	{ "radio", E_MENU_RADIO },
	{ NULL }
};

/* 1:1 with e-icon-factory sizes */
static const EPluginHookTargetKey emph_pixmap_sizes[] = {
	{ "menu", 0 },
	{ "button", 1},
	{ "small_toolbar", 2},
	{ "large_toolbar", 3},
	{ "dnd", 4},
	{ "dialog", 5},
	{ NULL }
};

static void
emph_menu_activate(EMenu *em, EMenuItem *item, gpointer data)
{
	EMenuHook *hook = data;

	d(printf("invoking plugin hook '%s' %p\n", (gchar *)item->user_data, em->target));

	e_plugin_invoke(hook->hook.plugin, item->user_data, em->target);
}

static void
emph_menu_toggle_activate(EMenu *em, EMenuItem *item, gint state, gpointer data)
{
	EMenuHook *hook = data;

	/* FIXME: where does the toggle state go? */
	d(printf("invoking plugin hook '%s' %p\n", (gchar *)item->user_data, em->target));

	e_plugin_invoke(hook->hook.plugin, item->user_data, em->target);
}

static void
emph_menu_factory(EMenu *emp, gpointer data)
{
	struct _EMenuHookMenu *menu = data;

	d(printf("menu factory, adding %d items\n", g_slist_length(menu->items)));

	if (menu->items)
		e_menu_add_items(emp, menu->items, menu->uis, menu->pixmaps, NULL, menu->hook);
}

static void
emph_free_item(struct _EMenuItem *item)
{
	g_free(item->path);
	g_free(item->verb);
	g_free(item->user_data);
	g_free(item);
}

static void
emph_free_ui(struct _EMenuUIFile *ui)
{
	g_free(ui->appdir);
	g_free(ui->appname);
	g_free(ui->filename);
}

static void
emph_free_pixmap(struct _EMenuPixmap *pixmap)
{
	g_free(pixmap->command);
	g_free(pixmap->name);
	g_free(pixmap->pixmap);
	g_free(pixmap);
}

static void
emph_free_menu(struct _EMenuHookMenu *menu)
{
	g_slist_foreach(menu->items, (GFunc)emph_free_item, NULL);
	g_slist_free(menu->items);
	g_slist_foreach(menu->uis, (GFunc)emph_free_ui, NULL);
	g_slist_free(menu->uis);
	g_slist_foreach(menu->pixmaps, (GFunc)emph_free_pixmap, NULL);
	g_slist_free(menu->pixmaps);

	g_free(menu->id);
	g_free(menu);
}

static struct _EMenuItem *
emph_construct_item(EPluginHook *eph, EMenuHookMenu *menu, xmlNodePtr root, EMenuHookTargetMap *map)
{
	struct _EMenuItem *item;

	d(printf("  loading menu item\n"));
	item = g_malloc0(sizeof(*item));
	item->type = e_plugin_hook_id(root, emph_item_types, "type");
	item->path = e_plugin_xml_prop(root, "path");
	item->verb = e_plugin_xml_prop(root, "verb");
	item->visible = e_plugin_hook_mask(root, map->mask_bits, "visible");
	item->enable = e_plugin_hook_mask(root, map->mask_bits, "enable");
	item->user_data = e_plugin_xml_prop(root, "activate");
	if ((item->type & E_MENU_TYPE_MASK) == E_MENU_TOGGLE)
		item->activate = G_CALLBACK(emph_menu_toggle_activate);
	else
		item->activate = G_CALLBACK(emph_menu_activate);

	if (item->type == -1 || item->user_data == NULL)
		goto error;

	d(printf("   path=%s\n", item->path));
	d(printf("   verb=%s\n", item->verb));

	return item;
error:
	d(printf("error!\n"));
	emph_free_item(item);
	return NULL;
}

static struct _EMenuPixmap *
emph_construct_pixmap(EPluginHook *eph, EMenuHookMenu *menu, xmlNodePtr root)
{
	struct _EMenuPixmap *pixmap;

	d(printf("  loading menu pixmap\n"));
	pixmap = g_malloc0(sizeof(*pixmap));
	pixmap->command = e_plugin_xml_prop(root, "command");
	pixmap->name = e_plugin_xml_prop(root, "pixmap");
	pixmap->size = e_plugin_hook_id(root, emph_pixmap_sizes, "size");

	if (pixmap->command == NULL || pixmap->name == NULL || pixmap->size == -1)
		goto error;

	return pixmap;
error:
	d(printf("error!\n"));
	emph_free_pixmap(pixmap);
	return NULL;
}

static struct _EMenuHookMenu *
emph_construct_menu(EPluginHook *eph, xmlNodePtr root)
{
	struct _EMenuHookMenu *menu;
	xmlNodePtr node;
	EMenuHookTargetMap *map;
	EMenuHookClass *klass = (EMenuHookClass *)G_OBJECT_GET_CLASS(eph);
	gchar *tmp;

	d(printf(" loading menu\n"));
	menu = g_malloc0(sizeof(*menu));
	menu->hook = (EMenuHook *)eph;

	tmp = (gchar *)xmlGetProp(root, (const guchar *)"target");
	if (tmp == NULL)
		goto error;
	map = g_hash_table_lookup(klass->target_map, tmp);
	xmlFree(tmp);
	if (map == NULL)
		goto error;

	menu->target_type = map->id;
	menu->id = e_plugin_xml_prop(root, "id");
	if (menu->id == NULL) {
		g_warning("Plugin '%s' missing 'id' field in menu for '%s'\n", eph->plugin->name,
			  ((EPluginHookClass *)G_OBJECT_GET_CLASS(eph))->id);
		goto error;
	}
	node = root->children;
	while (node) {
		if (0 == strcmp((gchar *)node->name, "item")) {
			struct _EMenuItem *item;

			item = emph_construct_item(eph, menu, node, map);
			if (item)
				menu->items = g_slist_append(menu->items, item);
		} else if (0 == strcmp((gchar *)node->name, "ui")) {
			tmp = (gchar *)xmlGetProp(node, (const guchar *)"file");
			if (tmp) {
				EMenuUIFile *ui = g_malloc0(sizeof(*ui));

				ui->filename = g_strdup(tmp);
				xmlFree(tmp);
#ifdef G_OS_WIN32
				{
					gchar *mapped_location =
						e_util_replace_prefix (EVOLUTION_PREFIX,
								       e_util_get_prefix (),
								       ui->filename);
					g_free (ui->filename);
					ui->filename = mapped_location;
				}
#endif
				ui->appdir = g_strdup(g_get_tmp_dir());
				ui->appname = g_strdup("Evolution");
				menu->uis = g_slist_append(menu->uis, ui);
			}
		} else if (0 == strcmp((gchar *)node->name, "pixmap")) {
			struct _EMenuPixmap *pixmap;

			pixmap = emph_construct_pixmap(eph, menu, node);
			if (pixmap)
				menu->pixmaps = g_slist_append(menu->pixmaps, pixmap);
		}
		node = node->next;
	}

	return menu;
error:
	d(printf("error loading menu hook\n"));
	emph_free_menu(menu);
	return NULL;
}

static gint
emph_construct(EPluginHook *eph, EPlugin *ep, xmlNodePtr root)
{
	xmlNodePtr node;
	EMenuClass *klass;

	d(printf("loading menu hook\n"));

	if (!ep->enabled)
		return 0;

	if (((EPluginHookClass *)emph_parent_class)->construct(eph, ep, root) == -1)
		return -1;

	klass = ((EMenuHookClass *)G_OBJECT_GET_CLASS(eph))->menu_class;

	node = root->children;
	while (node) {
		if (strcmp((gchar *)node->name, "menu") == 0) {
			struct _EMenuHookMenu *menu;

			menu = emph_construct_menu(eph, node);
			if (menu) {
				d(printf(" plugin adding factory %p\n", klass));
				e_menu_class_add_factory(klass, menu->id, emph_menu_factory, menu);
				emph->menus = g_slist_append(emph->menus, menu);
			}
		}

		node = node->next;
	}

	eph->plugin = ep;

	return 0;
}

static void
emph_finalise(GObject *o)
{
	EPluginHook *eph = (EPluginHook *)o;

	g_slist_foreach(emph->menus, (GFunc)emph_free_menu, NULL);
	g_slist_free(emph->menus);

	((GObjectClass *)emph_parent_class)->finalize(o);
}

static void
emph_class_init(EPluginHookClass *klass)
{
	d(printf("EMenuHook class init %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type)));

	((GObjectClass *)klass)->finalize = emph_finalise;
	klass->construct = emph_construct;

	/* this is actually an abstract implementation but list it anyway */
	klass->id = "org.gnome.evolution.bonobomenu:1.0";

	((EMenuHookClass *)klass)->target_map = g_hash_table_new(g_str_hash, g_str_equal);
	((EMenuHookClass *)klass)->menu_class = g_type_class_ref(e_menu_get_type());
}

/**
 * e_menu_hook_get_type:
 *
 * Standard GObject function to get the object type.  Used to subclass
 * EMenuHook.
 *
 * Return value: The type of the menu hook class.
 **/
GType
e_menu_hook_get_type(void)
{
	static GType type = 0;

	if (!type) {
		static const GTypeInfo info = {
			sizeof(EMenuHookClass), NULL, NULL, (GClassInitFunc) emph_class_init, NULL, NULL,
			sizeof(EMenuHook), 0, (GInstanceInitFunc) NULL,
		};

		emph_parent_class = g_type_class_ref(e_plugin_hook_get_type());
		type = g_type_register_static(e_plugin_hook_get_type(), "EMenuHook", &info, 0);
	}

	return type;
}

/**
 * e_menu_hook_class_add_target_map:
 * @klass: The derived EMenuHook class.
 * @map: A map used to describe a single EMenuTarget for this class.
 *
 * Adds a target map to a concrete derived class of EMenu.  The target
 * map enumerates a single target type, and the enable mask bit names,
 * so that the type can be loaded automatically by the EMenu class.
 **/
void e_menu_hook_class_add_target_map(EMenuHookClass *klass, const EMenuHookTargetMap *map)
{
	g_hash_table_insert(klass->target_map, (gpointer)map->type, (gpointer)map);
}
