/*
* 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
*
*
* Authors:
* Michael Zucchi
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#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;
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:"", 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 */
/*