/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Authors: Michael Zucchi * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "e-menu.h" #include "e-icon-factory.h" #define d(x) struct _EMenuFactory { struct _EMenuFactory *next, *prev; char *menuid; EMenuFactoryFunc factory; void *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; void *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 char *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. **/ void * e_menu_add_items(EMenu *emp, GSList *items, GSList *uifiles, GSList *pixmaps, EMenuItemsFunc freefunc, void *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 (void *)node; } /** * e_menu_remove_items: * @emp: * @handle: * * Remove menu items previously added. **/ void e_menu_remove_items(EMenu *emp, void *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 char *path, Bonobo_UIComponent_EventType type, const char *state, void *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, void *data, const char *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, int act) { struct _EMenuPrivate *p = em->priv; struct _menu_node *mw; GSList *l; if (act) { GArray *verbs; int 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, void *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; int 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 char *menuid, EMenuFactoryFunc func, void *data) { struct _EMenuFactory *f = g_malloc0(sizeof(*f)); d(printf("%p adding factory '%s' to class '%s'\n", klass, menuid?menuid:"", 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. **/ void *e_menu_target_new(EMenu *ep, int type, size_t 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, void *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 */ /* */ static void *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, void *data) { EMenuHook *hook = data; d(printf("invoking plugin hook '%s' %p\n", (char *)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, int state, void *data) { EMenuHook *hook = data; /* FIXME: where does the toggle state go? */ d(printf("invoking plugin hook '%s' %p\n", (char *)item->user_data, em->target)); e_plugin_invoke(hook->hook.plugin, item->user_data, em->target); } static void emph_menu_factory(EMenu *emp, void *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); char *tmp; d(printf(" loading menu\n")); menu = g_malloc0(sizeof(*menu)); menu->hook = (EMenuHook *)eph; tmp = (char *)xmlGetProp(root, (const unsigned char *)"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((char *)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((char *)node->name, "ui")) { tmp = (char *)xmlGetProp(node, (const unsigned char *)"file"); if (tmp) { EMenuUIFile *ui = g_malloc0(sizeof(*ui)); ui->filename = g_strdup(tmp); xmlFree(tmp); #ifdef G_OS_WIN32 { char *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((char *)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 int 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((char *)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, (void *)map->type, (void *)map); }