/*
 * SwamiguiTreeMenu.c - Swami Tree right-click menu object
 *
 * Swami
 * Copyright (C) 1999-2014 Element Green <element@elementsofsound.org>
 *
 * 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; version 2
 * of the License only.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA or point your web browser to http://www.gnu.org.
 */
#include <stdio.h>
#include <string.h>
#include <gtk/gtk.h>
#include <libinstpatch/libinstpatch.h>

#include "SwamiguiItemMenu.h"
#include "SwamiguiRoot.h"
#include "i18n.h"

enum
{
    PROP_0,
    PROP_SELECTION,	/* the item selection list */
    PROP_RIGHT_CLICK,	/* the right click item */
    PROP_CREATOR		/* the widget creating the menu */
};

typedef struct
{
    char *action_id;		/* store the hash key, for convenience */
    SwamiguiItemMenuInfo *info;
    SwamiguiItemMenuHandler handler;
} ActionBag;

typedef struct
{
    GType type;		/* type to match */
    gboolean derived;	/* TRUE if derived types should match also */
} TypeMatch;

static void swamigui_item_menu_class_init(SwamiguiItemMenuClass *klass);
static void type_match_list_free(gpointer data);
static void swamigui_item_menu_set_property(GObject *obj, guint property_id,
        const GValue *value, GParamSpec *pspec);
static void swamigui_item_menu_get_property(GObject *obj, guint property_id,
        GValue *value, GParamSpec *pspec);

static void swamigui_item_menu_init(SwamiguiItemMenu *menu);
static void swamigui_item_menu_finalize(GObject *object);
static void swamigui_item_menu_cb_deactivate(SwamiguiItemMenu *menu, gpointer user_data);
static ActionBag *lookup_item_action_bag(const char *action_id);
static void container_foreach_remove(GtkWidget *widg, gpointer data);
static void make_action_list_GHFunc(gpointer key, gpointer value,
                                    gpointer user_data);
static void swamigui_item_menu_callback_activate(GtkMenuItem *mitem,
        gpointer user_data);
static void swamigui_item_menu_accel_activate_callback(gpointer user_data);


/* keyboard accelerator group for item menu actions */
GtkAccelGroup *swamigui_item_menu_accel_group;

/* hash of action ID string -> ActionBag */
G_LOCK_DEFINE_STATIC(menu_action_hash);
static GHashTable *menu_action_hash = NULL;

/* hash of action ID string -> GSList of TypeMatch (for including types) */
G_LOCK_DEFINE_STATIC(item_type_include_hash);
static GHashTable *item_type_include_hash = NULL;

/* hash of action ID string -> GSList of TypeMatch (for excluding types) */
G_LOCK_DEFINE_STATIC(item_type_exclude_hash);
static GHashTable *item_type_exclude_hash = NULL;


static GObjectClass *parent_class = NULL;

void
_swamigui_item_menu_init(void)
{
    /* create key accelerator group */
    swamigui_item_menu_accel_group = gtk_accel_group_new();

    /* create menu action hash */
    menu_action_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
                       NULL, (GDestroyNotify)g_free);

    /* create menu item type inclusion hash */
    item_type_include_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
                             NULL, type_match_list_free);

    /* create menu item type exclusion hash */
    item_type_exclude_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
                             NULL, type_match_list_free);
}

/* free menu table */
void
_swamigui_item_menu_deinit (void)
{
    g_hash_table_destroy(menu_action_hash);
    g_hash_table_destroy(item_type_include_hash);
    g_hash_table_destroy(item_type_exclude_hash);
    g_object_unref(swamigui_item_menu_accel_group);
}

GType
swamigui_item_menu_get_type(void)
{
    static GType obj_type = 0;

    if(!obj_type)
    {
        static const GTypeInfo obj_info =
        {
            sizeof(SwamiguiItemMenuClass), NULL, NULL,
            (GClassInitFunc) swamigui_item_menu_class_init, NULL, NULL,
            sizeof(SwamiguiItemMenu), 0,
            (GInstanceInitFunc) swamigui_item_menu_init,
        };

        obj_type = g_type_register_static(GTK_TYPE_MENU, "SwamiguiItemMenu",
                                          &obj_info, 0);
    }

    return (obj_type);
}

static void
swamigui_item_menu_class_init(SwamiguiItemMenuClass *klass)
{
    GObjectClass *obj_class = G_OBJECT_CLASS(klass);

    parent_class = g_type_class_peek_parent(klass);

    obj_class->finalize = swamigui_item_menu_finalize;
    obj_class->get_property = swamigui_item_menu_get_property;
    obj_class->set_property = swamigui_item_menu_set_property;


    g_object_class_install_property(obj_class, PROP_SELECTION,
                                    g_param_spec_object("selection", "selection",
                                            "selection", IPATCH_TYPE_LIST,
                                            G_PARAM_READWRITE));
    g_object_class_install_property(obj_class, PROP_RIGHT_CLICK,
                                    g_param_spec_object("right-click", "right-click",
                                            "right-click", G_TYPE_OBJECT,
                                            G_PARAM_READWRITE));
    g_object_class_install_property(obj_class, PROP_CREATOR,
                                    g_param_spec_object("creator", "creator", "creator",
                                            G_TYPE_OBJECT, G_PARAM_READWRITE));
}

/* free a GSList of TypeMatch structures */
static void
type_match_list_free(gpointer data)
{
    GSList *p = (GSList *)data;

    for(; p; p = g_slist_delete_link(p, p))
    {
        g_free(p->data);
    }
}

static void
swamigui_item_menu_set_property(GObject *obj, guint property_id,
                                const GValue *value, GParamSpec *pspec)
{
    SwamiguiItemMenu *menu = SWAMIGUI_ITEM_MENU(obj);
    GObject *object;

    switch(property_id)
    {
    case PROP_SELECTION:
        if(menu->selection)
        {
            g_object_unref(menu->selection);
        }

        object = g_value_get_object(value);
        g_return_if_fail(!object || IPATCH_IS_LIST(object));
        menu->selection = (IpatchList *)g_object_ref(object);
        break;

    case PROP_RIGHT_CLICK:
        if(menu->rclick)
        {
            g_object_unref(menu->rclick);
        }

        menu->rclick = g_value_dup_object(value);
        break;

    case PROP_CREATOR:
        if(menu->creator)
        {
            g_object_unref(menu->creator);
        }

        menu->creator = g_value_dup_object(value);
        break;

    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
        break;
    }
}

static void
swamigui_item_menu_get_property(GObject *obj, guint property_id,
                                GValue *value, GParamSpec *pspec)
{
    SwamiguiItemMenu *menu = SWAMIGUI_ITEM_MENU(obj);

    switch(property_id)
    {
    case PROP_SELECTION:
        g_value_set_object(value, (GObject *)(menu->selection));
        break;

    case PROP_RIGHT_CLICK:
        g_value_set_object(value, (GObject *)(menu->rclick));
        break;

    case PROP_CREATOR:
        g_value_set_object(value, (GObject *)(menu->creator));
        break;

    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
        break;
    }
}

static void
swamigui_item_menu_init(SwamiguiItemMenu *menu)
{
    gtk_menu_set_accel_group(GTK_MENU(menu), swamigui_item_menu_accel_group);

    g_signal_connect(menu, "deactivate", G_CALLBACK(swamigui_item_menu_cb_deactivate), NULL);
    g_object_ref_sink(menu);      // HACK: This is used as a popup menu which gets destroyed in deactivate signal handler
}

static void
swamigui_item_menu_finalize(GObject *object)
{
    SwamiguiItemMenu *menu = SWAMIGUI_ITEM_MENU(object);

    if(menu->selection)
    {
        g_object_unref(menu->selection);
    }

    if(menu->rclick)
    {
        g_object_unref(menu->rclick);
    }

    if(menu->creator)
    {
        g_object_unref(menu->creator);
    }

    menu->selection = NULL;
    menu->rclick = NULL;
    menu->creator = NULL;

    if(parent_class->finalize)
    {
        parent_class->finalize(object);
    }
}

// HACK: Popup menus appear to leak with gtk_menu_popup, so we destroy it here
static void
swamigui_item_menu_cb_deactivate(SwamiguiItemMenu *menu, gpointer user_data)
{
    g_object_unref(menu);
}

/**
 * swamigui_item_menu_new:
 *
 * Create a new Swami item menu.
 *
 * Returns: New Swami item menu object with a ref count of 1.
 */
SwamiguiItemMenu *
swamigui_item_menu_new(void)
{
    return (SWAMIGUI_ITEM_MENU(g_object_new(SWAMIGUI_TYPE_ITEM_MENU, NULL)));
}

/**
 * swamigui_item_menu_add:
 * @menu: GUI menu to add item to
 * @info: Info describing new menu item to add
 * @action_id: The action ID string that is adding the menu item
 *
 * Add a menu item to a GUI menu.
 *
 * Returns: The new GtkMenuItem that was added to the menu.
 */
GtkWidget *
swamigui_item_menu_add(SwamiguiItemMenu *menu,
                       const SwamiguiItemMenuInfo *info, const char *action_id)
{
    GtkWidget *mitem;
    guint key;
    GdkModifierType mods;
    char *accel_path;
    GList *list, *p;
    guint order;
    int index;

    g_return_val_if_fail(SWAMIGUI_IS_ITEM_MENU(menu), NULL);
    g_return_val_if_fail(info != NULL, NULL);

    if(info->icon)
    {
        GtkWidget *image;

        mitem = gtk_image_menu_item_new_with_mnemonic(_(info->label));

        image = gtk_image_new_from_stock(info->icon, GTK_ICON_SIZE_MENU);
        gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mitem), image);
    }
    else
    {
        mitem = gtk_menu_item_new_with_mnemonic(_(info->label));
    }

    gtk_widget_show(mitem);

    g_object_set_data(G_OBJECT(mitem), "_order",
                      GUINT_TO_POINTER(info->order));
    g_object_set_data(G_OBJECT(mitem), "_func", (gpointer)(info->func));

    /* connect menu item to callback function */
    if(info->func)
        g_signal_connect(mitem, "activate",
                         G_CALLBACK(swamigui_item_menu_callback_activate),
                         info->data);

    /* parse key accelerator and add it to menu item */
    if(info->accel)
    {
        gtk_accelerator_parse(info->accel, &key, &mods);

        accel_path = g_strdup_printf("<SwamiguiItemMenu>/%s", action_id);
        gtk_accel_map_add_entry(accel_path, key, mods);

        gtk_menu_item_set_accel_path(GTK_MENU_ITEM(mitem), accel_path);
        g_free(accel_path);
    }

    if(info->flags & SWAMIGUI_ITEM_MENU_INACTIVE)
    {
        gtk_widget_set_sensitive(GTK_WIDGET(mitem), FALSE);
    }

    list = gtk_container_get_children(GTK_CONTAINER(menu));

    /* find insert position from order info parameter */
    for(p = list, index = 0; p; p = p->next, index++)
    {
        order = GPOINTER_TO_INT(g_object_get_data(p->data, "_order"));

        if(info->order < order)
        {
            break;
        }
    }

    g_list_free(list);

    gtk_menu_shell_insert(GTK_MENU_SHELL(menu), mitem, index);

    return (mitem);
}

/**
 * swamigui_item_menu_add_registered_info:
 * @menu: GUI menu to add item to
 * @action_id: The action ID string that is adding the menu item
 *
 * Add a menu item to a GUI menu using the default info added when the
 * @action_id was registered.
 *
 * Returns: The new GtkMenuItem that was added to the menu.
 */
GtkWidget *
swamigui_item_menu_add_registered_info(SwamiguiItemMenu *menu,
                                       const char *action_id)
{
    ActionBag *found_action;

    g_return_val_if_fail(SWAMIGUI_IS_ITEM_MENU(menu), NULL);

    found_action = lookup_item_action_bag(action_id);
    g_return_val_if_fail(found_action != NULL, NULL);
    g_return_val_if_fail(found_action->info != NULL, NULL);

    return (swamigui_item_menu_add(menu, found_action->info, action_id));
}

/**
 * swamigui_item_menu_add_registered_info_inactive:
 * @menu: GUI menu to add item to
 * @action_id: The action ID string that is adding the menu item
 *
 * Add an inactive menu item to a GUI menu using the default info added when the
 * @action_id was registered.
 *
 * Returns: The new GtkMenuItem that was added to the menu.
 */
GtkWidget *
swamigui_item_menu_add_registered_info_inactive(SwamiguiItemMenu *menu,
        const char *action_id)
{
    ActionBag *found_action;
    SwamiguiItemMenuInfo info;

    g_return_val_if_fail(SWAMIGUI_IS_ITEM_MENU(menu), NULL);

    found_action = lookup_item_action_bag(action_id);
    g_return_val_if_fail(found_action != NULL, NULL);
    g_return_val_if_fail(found_action->info != NULL, NULL);

    memcpy(&info, found_action->info, sizeof(info));
    info.flags |= SWAMIGUI_ITEM_MENU_INACTIVE;

    return (swamigui_item_menu_add(menu, &info, action_id));
}

/* lookup a registered action by its ID */
static ActionBag *
lookup_item_action_bag(const char *action_id)
{
    ActionBag *bag;

    G_LOCK(menu_action_hash);
    bag = g_hash_table_lookup(menu_action_hash, (gpointer)action_id);
    G_UNLOCK(menu_action_hash);

    return (bag);
}

/**
 * swamigui_item_menu_generate:
 * @menu: GUI menu
 *
 * Generate a GUI menu by executing all registered item action handlers which
 * add items to the menu.  Any existing items are removed before generating the
 * new menu.
 */
void
swamigui_item_menu_generate(SwamiguiItemMenu *menu)
{
    GSList *actions = NULL, *p;

    g_return_if_fail(SWAMIGUI_IS_ITEM_MENU(menu));

    /* remove any existing items from the menu */
    gtk_container_foreach(GTK_CONTAINER(menu), container_foreach_remove, menu);

    /* make a list so we can unlock the hash (prevent recursive locks) */
    G_LOCK(menu_action_hash);
    g_hash_table_foreach(menu_action_hash, make_action_list_GHFunc, &actions);
    G_UNLOCK(menu_action_hash);

    for(p = actions; p; p = p->next)
    {
        ActionBag *bag = (ActionBag *)(p->data);

        /* if handler was supplied then execute it, otherwise create item using
        the item info set when action was registered (one or the other must be
         set). */
        if(bag->handler)
        {
            bag->handler(menu, bag->action_id);
        }
        else
        {
            swamigui_item_menu_add(menu, bag->info, bag->action_id);
        }
    }

    g_slist_free(actions);
}

/* turn hash into a list */
static void
make_action_list_GHFunc(gpointer key, gpointer value, gpointer user_data)
{
    GSList **listp = (GSList **)user_data;
    *listp = g_slist_prepend(*listp, value);
}

static void
container_foreach_remove(GtkWidget *widg, gpointer data)
{
    gtk_container_remove(GTK_CONTAINER(data), widg);
}

/* callback when a menu item is activated */
static void
swamigui_item_menu_callback_activate(GtkMenuItem *mitem, gpointer user_data)
{
    SwamiguiItemMenuCallback callback;
    IpatchList *selection;

    callback = g_object_get_data(G_OBJECT(mitem), "_func");
    g_return_if_fail(callback != NULL);

    /* ++ ref selection */
    g_object_get(swamigui_root, "selection", &selection, NULL);

    if(!selection)
    {
        return;
    }

    (*callback)(selection, user_data);
    g_object_unref(selection);	/* -- unref selection */
}

/**
 * swamigui_register_item_menu_action:
 * @action_id: Menu item action ID (example: "paste", "new", "copy", etc),
 *   should be a static string since it is used directly.
 * @info: Menu item info (optional if @handler specified).  Structure is
 *   used directly and so it should be static as well as the strings inside.
 * @handler: Function to call when generating a menu (may be %NULL in which
 *   case the default handler is used).  Handler function should determine
 *   whether the registered action is appropriate for the current item
 *   selection and right click item and add menu items as appropriate.
 *
 * Registers a menu action.
 */
void
swamigui_register_item_menu_action(char *action_id,
                                   SwamiguiItemMenuInfo *info,
                                   SwamiguiItemMenuHandler handler)
{
    ActionBag *bag;
    guint key;
    GdkModifierType mods;
    GClosure *closure;

    g_return_if_fail(action_id != NULL && strlen(action_id) > 0);
    g_return_if_fail(info != NULL || handler != NULL);

    bag = g_new(ActionBag, 1);
    bag->action_id = action_id;
    bag->info = info;
    bag->handler = handler;

    G_LOCK(menu_action_hash);
    g_hash_table_insert(menu_action_hash, (gpointer)action_id, bag);
    G_UNLOCK(menu_action_hash);

    if(info->accel)
    {
        /* parse the accelerator */
        gtk_accelerator_parse(info->accel, &key, &mods);

        if(key != 0)	/* valid accelerator? */
        {
            /* create closure for callback and add accelerator to accel group */
            closure = g_cclosure_new_swap
                      ((GCallback)swamigui_item_menu_accel_activate_callback,
                       info, NULL);
            gtk_accel_group_connect(swamigui_item_menu_accel_group, key, mods,
                                    GTK_ACCEL_VISIBLE, closure);
        }
    }
}

static void
swamigui_item_menu_accel_activate_callback(gpointer user_data)
{
    SwamiguiItemMenuInfo *info = (SwamiguiItemMenuInfo *)user_data;
    IpatchList *selection;

    g_return_if_fail(info->func != NULL);

    g_object_get(swamigui_root, "selection", &selection, NULL);
    info->func(selection, info->data);
    g_object_unref(selection);
}

/**
 * swamigui_lookup_item_menu_action:
 * @action_id: ID string of item menu action to lookup.
 * @info: Location to store pointer to registered info (or %NULL to ignore).
 * @handler: Location to store pointer to registered handler (or %NULL to
 *   ignore).
 *
 * Lookup item action information registered by @action_id.
 *
 * Returns: %TRUE if action found by @action_id, %FALSE if not found.
 */
gboolean
swamigui_lookup_item_menu_action(const char *action_id,
                                 SwamiguiItemMenuInfo **info,
                                 SwamiguiItemMenuHandler *handler)
{
    ActionBag *bag;

    if(info)
    {
        *info = NULL;
    }

    if(handler)
    {
        *handler = NULL;
    }

    g_return_val_if_fail(action_id != NULL, FALSE);

    bag = lookup_item_action_bag(action_id);

    if(!bag)
    {
        return (FALSE);
    }

    if(info)
    {
        *info = bag->info;
    }

    if(handler)
    {
        *handler = bag->handler;
    }

    return (TRUE);
}

/**
 * swamigui_register_item_menu_include_type:
 * @action_id: The registered action ID string
 * @type: The type to add
 * @derived: Set to %TRUE if derived types should match also
 *
 * Adds a selection item type for inclusion for the given registered item
 * action.
 */
void
swamigui_register_item_menu_include_type(const char *action_id, GType type,
        gboolean derived)
{
    TypeMatch *typematch;
    GSList *list;

    g_return_if_fail(action_id != NULL);
    g_return_if_fail(type != 0);

    typematch = g_new(TypeMatch, 1);
    typematch->type = type;
    typematch->derived = derived;

    G_LOCK(item_type_include_hash);

    list = g_hash_table_lookup(item_type_include_hash, action_id);

    if(!list)
    {
        list = g_slist_append(list, typematch);
        g_hash_table_insert(item_type_include_hash, (gpointer)action_id, list);
    }	// Assignment to keep warn_unused_result happy (append to non-empty list)
    else
    {
        list = g_slist_append(list, typematch);
    }

    G_UNLOCK(item_type_include_hash);
}

/**
 * swamigui_register_item_menu_exclude_type:
 * @action_id: The registered action ID string
 * @type: The type to add
 * @derived: Set to %TRUE if derived types should match also
 *
 * Adds a selection item type for exclusion for the given registered item
 * action.
 */
void
swamigui_register_item_menu_exclude_type(const char *action_id, GType type,
        gboolean derived)
{
    TypeMatch *typematch;
    GSList *list;

    g_return_if_fail(action_id != NULL);
    g_return_if_fail(type != 0);

    typematch = g_new(TypeMatch, 1);
    typematch->type = type;
    typematch->derived = derived;

    G_LOCK(item_type_exclude_hash);

    list = g_hash_table_lookup(item_type_exclude_hash, action_id);

    if(!list)
    {
        list = g_slist_append(list, typematch);
        g_hash_table_insert(item_type_exclude_hash, (gpointer)action_id, list);
    }	// Assignment to keep warn_unused_result happy (append to non-empty list)
    else
    {
        list = g_slist_append(list, typematch);
    }

    G_UNLOCK(item_type_exclude_hash);
}

/**
 * swamigui_test_item_menu_type:
 * @action_id: Menu item action ID string
 * @type: Type to test
 *
 * Tests if a given item selection @type is in the include list and not in
 * the exclude list for @action_id.
 *
 * Returns: %TRUE if item is in include list and not in exclude list
 *    for @action_id, %FALSE otherwise.
 */
gboolean
swamigui_test_item_menu_type(const char *action_id, GType type)
{
    return (swamigui_test_item_menu_include_type(action_id, type)
            && swamigui_test_item_menu_exclude_type(action_id, type));
}

/**
 * swamigui_test_item_menu_include_type:
 * @action_id: Menu item action ID string
 * @type: Type to test
 *
 * Tests if a given item selection @type is in the include list for @action_id.
 *
 * Returns: %TRUE if item is in include list for @action_id, %FALSE otherwise.
 */
gboolean
swamigui_test_item_menu_include_type(const char *action_id, GType type)
{
    TypeMatch *match;
    GSList *p;

    g_return_val_if_fail(action_id != NULL, FALSE);
    g_return_val_if_fail(type != 0, FALSE);

    G_LOCK(item_type_include_hash);

    p = g_hash_table_lookup(item_type_include_hash, action_id);

    for(; p; p = p->next)
    {
        match = (TypeMatch *)(p->data);

        if((match->derived && g_type_is_a(type, match->type))
                || (!match->derived && type == match->type))
        {
            break;
        }
    }

    G_UNLOCK(item_type_include_hash);

    return (p != NULL);
}

/**
 * swamigui_test_item_menu_exclude_type:
 * @action_id: Menu item action ID string
 * @type: Type to test
 *
 * Tests if a given item selection @type is not in the exclude list for
 * @action_id.
 *
 * Returns: %TRUE if item is not in exclude list for @action_id (should be
 *   included), %FALSE otherwise.
 */
gboolean
swamigui_test_item_menu_exclude_type(const char *action_id, GType type)
{
    TypeMatch *match;
    GSList *p;

    g_return_val_if_fail(action_id != NULL, FALSE);
    g_return_val_if_fail(type != 0, FALSE);

    G_LOCK(item_type_exclude_hash);

    p = g_hash_table_lookup(item_type_exclude_hash, action_id);

    for(; p; p = p->next)
    {
        match = (TypeMatch *)(p->data);

        if((match->derived && g_type_is_a(type, match->type))
                || (!match->derived && type == match->type))
        {
            break;
        }
    }

    G_UNLOCK(item_type_exclude_hash);

    return (p == NULL);
}

/**
 * swamigui_item_menu_get_selection_single:
 * @menu: GUI menu object
 *
 * Test if a menu object has a single selected item and return it if so.
 *
 * Returns: The single selected item object or %NULL if not a single selection.
 *   Returned object does NOT have a reference added and should be referenced
 *   if used outside of calling function.
 */
GObject *
swamigui_item_menu_get_selection_single(SwamiguiItemMenu *menu)
{
    g_return_val_if_fail(SWAMIGUI_IS_ITEM_MENU(menu), FALSE);

    if(menu->selection && menu->selection->items
            && !menu->selection->items->next)
    {
        return (menu->selection->items->data);
    }
    else
    {
        return (NULL);
    }
}

/**
 * swamigui_item_menu_get_selection:
 * @menu: GUI menu object
 *
 * Test if a menu object has any selected items and return the selection list
 * if so.
 *
 * Returns: Selected items or %NULL if no selection object.  No reference is
 * added for caller, so caller must reference if used outside of calling
 * context.
 */
IpatchList *
swamigui_item_menu_get_selection(SwamiguiItemMenu *menu)
{
    g_return_val_if_fail(SWAMIGUI_IS_ITEM_MENU(menu), NULL);

    if(menu->selection)
    {
        return (menu->selection);
    }

    return (NULL);
}

/**
 * swamigui_item_menu_handler_single:
 * @menu: The menu object
 * @action_id: The action ID
 *
 * A #SwamiguiItemMenuHandler type that can be used when registering a menu
 * action.  It will add a single menu item, using the info provided during
 * action registration, if a single item is selected and is of a type found
 * in the include type list and not found in exclude list.
 */
void
swamigui_item_menu_handler_single(SwamiguiItemMenu *menu,
                                  const char *action_id)
{
    ActionBag *bag;
    GObject *item;

    g_return_if_fail(SWAMIGUI_IS_ITEM_MENU(menu));
    g_return_if_fail(action_id != NULL);

    /* make sure there is only 1 item selected */
    item = swamigui_item_menu_get_selection_single(menu);

    if(!item)
    {
        return;
    }

    /* item type is not in include list or in exclude list? - then return */
    if(!swamigui_test_item_menu_type(action_id, G_OBJECT_TYPE(item)))
    {
        return;
    }

    bag = lookup_item_action_bag(action_id);   /* lookup the registered action */

    /* add a menu item from the registered info */
    swamigui_item_menu_add(menu, bag->info, action_id);
}

/**
 * swamigui_item_menu_handler_multi:
 * @menu: The menu object
 * @action_id: The action ID
 *
 * A #SwamiguiItemMenuHandler type that can be used when registering a menu
 * action.  It will add a single menu item, using the info provided during
 * action registration, if a single item is selected and is of a type found
 * in the include type list and not in exclude list or multiple items are
 * selected.
 */
void
swamigui_item_menu_handler_multi(SwamiguiItemMenu *menu, const char *action_id)
{
    ActionBag *bag;
    IpatchList *list;
    GList *items;

    g_return_if_fail(SWAMIGUI_IS_ITEM_MENU(menu));
    g_return_if_fail(action_id != NULL);

    /* make sure there is at least 1 item selected */
    list = swamigui_item_menu_get_selection(menu);

    if(!list || !list->items)
    {
        return;
    }

    items = list->items;

    /* if only 1 item is selected.. */
    if(!items->next)
    {
        GObject *item = G_OBJECT(items->data);

        /* item type is not in include list or in exclude list? - then return */
        if(!swamigui_test_item_menu_type(action_id, G_OBJECT_TYPE(item)))
        {
            return;
        }
    }

    bag = lookup_item_action_bag(action_id);   /* lookup the registered action */

    /* add a menu item from the registered info */
    swamigui_item_menu_add(menu, bag->info, action_id);
}

/**
 * swamigui_item_menu_handler_single_all:
 * @menu: The menu object
 * @action_id: The action ID
 *
 * A #SwamiguiItemMenuHandler type that can be used when registering a menu
 * action.  It will add a single menu item, using the info provided during
 * action registration, if there is a single item selected of any type.
 */
void
swamigui_item_menu_handler_single_all(SwamiguiItemMenu *menu,
                                      const char *action_id)
{
    ActionBag *bag;

    g_return_if_fail(SWAMIGUI_IS_ITEM_MENU(menu));
    g_return_if_fail(action_id != NULL);

    /* make sure there is only 1 item selected */
    if(!swamigui_item_menu_get_selection_single(menu))
    {
        return;
    }

    bag = lookup_item_action_bag(action_id);   /* lookup the registered action */

    /* add a menu item from the registered info */
    swamigui_item_menu_add(menu, bag->info, action_id);
}

/**
 * swamigui_item_menu_handler_multi_all:
 * @menu: The menu object
 * @action_id: The action ID
 *
 * A #SwamiguiItemMenuHandler type that can be used when registering a menu
 * action.  It will add a single menu item, using the info provided during
 * action registration, if there is at least one item selected of any type.
 */
void
swamigui_item_menu_handler_multi_all(SwamiguiItemMenu *menu,
                                     const char *action_id)
{
    ActionBag *bag;
    IpatchList *list;

    g_return_if_fail(SWAMIGUI_IS_ITEM_MENU(menu));
    g_return_if_fail(action_id != NULL);

    /* make sure there is at least 1 item selected */
    list = swamigui_item_menu_get_selection(menu);

    if(!list || !list->items)
    {
        return;
    }

    bag = lookup_item_action_bag(action_id);   /* lookup the registered action */

    /* add a menu item from the registered info */
    swamigui_item_menu_add(menu, bag->info, action_id);
}
