The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * Copyright (c) 2003 by the gtk2-perl team (see the file AUTHORS)
 *
 * Licensed under the LGPL, see LICENSE file for more information.
 *
 * $Header: /cvsroot/gtk2-perl/gtk2-perl-xs/Gtk2/xs/GtkActionGroup.xs,v 1.7 2004/02/29 09:41:35 muppetman Exp $
 */

#include "gtk2perl.h"

/* helper for using gperl_signal_connect when you don't have the SV of
 * the instance... */
#define WRAPINSTANCE(object)	(sv_2mortal (newSVGObject (G_OBJECT (object))))


/* these macros expect there to exist an SV** named svp */

#define HFETCHPV(hv, key)	\
	(((svp = hv_fetch ((hv), (key), strlen ((key)), FALSE))	\
	  && SvOK (*svp))					\
	  ? SvPV_nolen (*svp)					\
	  : NULL)

#define HFETCHCV(hv, key)	\
	(((svp = hv_fetch ((hv), (key), strlen ((key)), FALSE))	\
	  && SvOK (*svp))					\
	  ? (gpointer)(*svp)					\
	  : NULL)

#define HFETCHIV(hv, key)	\
	(((svp = hv_fetch ((hv), (key), strlen ((key)), FALSE))	\
	  && SvOK (*svp))					\
	  ? SvIV (*svp)						\
	  : 0)

#define AFETCHPV(av, index)	\
	(((svp = av_fetch ((av), (index), FALSE)) && SvOK (*svp))	\
	 ? SvPV_nolen (*svp)						\
	 : NULL)

#define AFETCHCV(av, index)	\
	(((svp = av_fetch ((av), (index), FALSE)) && SvOK (*svp))	\
	 ? (gpointer)(*svp)						\
	 : NULL)

#define AFETCHIV(av, index)	\
	(((svp = av_fetch ((av), (index), FALSE)) && SvOK (*svp))	\
	 ? SvIV (*svp)							\
	 : 0)


/*
struct _GtkActionEntry 
{
  gchar     *name;
  gchar     *stock_id;
  gchar     *label;
  gchar     *accelerator;
  gchar     *tooltip;
  GCallback  callback;
};
*/

static void
read_action_entry_from_sv (SV * sv,
                           GtkActionEntry * action)
{
	SV ** svp;
	if (!sv || !SvOK (sv) || !SvROK (sv))
		croak ("invalid action entry");

	switch (SvTYPE (SvRV (sv))) {
	    case SVt_PVHV:
		{
		HV * hv = (HV*) SvRV (sv);
		action->name        = HFETCHPV (hv, "name");
		action->stock_id    = HFETCHPV (hv, "stock_id");
		action->label       = HFETCHPV (hv, "label");
		action->accelerator = HFETCHPV (hv, "accelerator");
		action->tooltip     = HFETCHPV (hv, "tooltip");
		action->callback    = HFETCHCV (hv, "callback");
		}
		break;
	    case SVt_PVAV:
		{
		AV * av = (AV*) SvRV (sv);
		action->name        = AFETCHPV (av, 0);
		action->stock_id    = AFETCHPV (av, 1);
		action->label       = AFETCHPV (av, 2);
		action->accelerator = AFETCHPV (av, 3);
		action->tooltip     = AFETCHPV (av, 4);
		action->callback    = AFETCHCV (av, 5);
		}
		break;
	    default:
		croak ("action entry must be a hash or an array");
	}
}


/*
struct _GtkToggleActionEntry 
{
  gchar     *name;
  gchar     *stock_id;
  gchar     *label;
  gchar     *accelerator;
  gchar     *tooltip;
  GCallback  callback;
  gboolean   is_active;
};
*/

static void
read_toggle_action_entry_from_sv (SV * sv,
                                  GtkToggleActionEntry * action)
{
	SV ** svp;
	if (!sv || !SvOK (sv) || !SvROK (sv))
		croak ("invalid toggle action entry");

	switch (SvTYPE (SvRV (sv))) {
	    case SVt_PVHV:
		{
		HV * hv = (HV*) SvRV (sv);
		action->name        = HFETCHPV (hv, "name");
		action->stock_id    = HFETCHPV (hv, "stock_id");
		action->label       = HFETCHPV (hv, "label");
		action->accelerator = HFETCHPV (hv, "accelerator");
		action->tooltip     = HFETCHPV (hv, "tooltip");
		action->callback    = HFETCHCV (hv, "callback");
		action->is_active   = HFETCHIV (hv, "is_active");
		}
		break;
	    case SVt_PVAV:
		{
		AV * av = (AV*) SvRV (sv);
		if (av_len (av) < 5)
			croak ("not enough items in array form of toggle action entry; expecting:\n"
			       "     [ name, stock_id, label, accelerator, tooltip, value]\n"
			       "  ");
		action->name        = AFETCHPV (av, 0);
		action->stock_id    = AFETCHPV (av, 1);
		action->label       = AFETCHPV (av, 2);
		action->accelerator = AFETCHPV (av, 3);
		action->tooltip     = AFETCHPV (av, 4);
		action->callback    = AFETCHCV (av, 5);
		action->is_active   = AFETCHIV (av, 6);
		}
		break;
	    default:
		croak ("action entry must be a hash or an array");
	}
}


/*
struct _GtkRadioActionEntry 
{
  gchar *name;
  gchar *stock_id;
  gchar *label;
  gchar *accelerator;
  gchar *tooltip;
  gint   value; 
};
*/

static void
read_radio_action_entry_from_sv (SV * sv,
                                 GtkRadioActionEntry * action)
{
	SV ** svp;
	if (!sv || !SvOK (sv) || !SvROK (sv))
		croak ("invalid radio action entry");

	switch (SvTYPE (SvRV (sv))) {
	    case SVt_PVHV:
		{
		HV * hv = (HV*) SvRV (sv);
		action->name        = HFETCHPV (hv, "name");
		action->stock_id    = HFETCHPV (hv, "stock_id");
		action->label       = HFETCHPV (hv, "label");
		action->accelerator = HFETCHPV (hv, "accelerator");
		action->tooltip     = HFETCHPV (hv, "tooltip");
		action->value       = HFETCHIV (hv, "value");
		}
		break;
	    case SVt_PVAV:
		{
		AV * av = (AV*) SvRV (sv);
		if (av_len (av) < 5)
			croak ("not enough items in array form of radio action entry; expecting:\n"
			       "     [ name, stock_id, label, accelerator, tooltip, value]\n"
			       "  ");
		action->name        = AFETCHPV (av, 0);
		action->stock_id    = AFETCHPV (av, 1);
		action->label       = AFETCHPV (av, 2);
		action->accelerator = AFETCHPV (av, 3);
		action->tooltip     = AFETCHPV (av, 4);
		action->value       = AFETCHIV (av, 5);
		}
		break;
	    default:
		croak ("action entry must be a hash or an array");
	}
}


MODULE = Gtk2::ActionGroup	PACKAGE = Gtk2::ActionGroup	PREFIX = gtk_action_group_

GtkActionGroup_noinc *gtk_action_group_new (class, const gchar *name);
    C_ARGS:
	name

const gchar *gtk_action_group_get_name (GtkActionGroup *action_group);

void gtk_action_group_set_sensitive (GtkActionGroup *action_group, gboolean sensitive);

gboolean gtk_action_group_get_sensitive (GtkActionGroup *action_group);

void gtk_action_group_set_visible (GtkActionGroup *action_group, gboolean sensitive);

gboolean gtk_action_group_get_visible (GtkActionGroup *action_group);

GtkAction *gtk_action_group_get_action (GtkActionGroup *action_group, const gchar *action_name);

void gtk_action_group_list_actions (GtkActionGroup *action_group);
    PREINIT:
	GList * actions, * i;
    PPCODE:
	actions = gtk_action_group_list_actions (action_group);
	for (i = actions ; i != NULL ; i = i->next)
		XPUSHs (sv_2mortal (newSVGtkAction (i->data)));
	g_list_free (actions);

void gtk_action_group_add_action (GtkActionGroup *action_group, GtkAction *action);

void gtk_action_group_add_action_with_accel (GtkActionGroup *action_group, GtkAction *action, const gchar_ornull *accelerator);

void gtk_action_group_remove_action (GtkActionGroup *action_group, GtkAction *action);

##void gtk_action_group_add_actions (GtkActionGroup *action_group, GtkActionEntry *entries, guint n_entries, gpointer user_data);
##void gtk_action_group_add_actions_full (GtkActionGroup *action_group, GtkActionEntry *entries, guint n_entries, gpointer user_data, GDestroyNotify destroy);
void
gtk_action_group_add_actions (action_group, action_entries, user_data=NULL)
	GtkActionGroup * action_group
	SV * action_entries
	SV * user_data
    PREINIT:
	AV * av;
	GtkActionEntry * entries;
	gint n_actions, i;
    CODE:
	if (!action_entries || !SvOK (action_entries) || !SvROK (action_entries)
	    || SvTYPE (SvRV (action_entries)) != SVt_PVAV)
		croak ("actions must be a reference to an array of action entries");
	av = (AV*) SvRV (action_entries);
	n_actions = av_len (av) + 1;
	if (n_actions < 1)
		croak ("action array is empty");
	entries = gperl_alloc_temp (sizeof (GtkActionEntry) * n_actions);
	for (i = 0 ; i < n_actions ; i++) {
		SV ** svp = av_fetch (av, i, 0);
		read_action_entry_from_sv (*svp, entries+i);
	}
	
	for (i = 0 ; i < n_actions ; i++) {
		GtkAction * action;
		gchar * accel_path;

		action = g_object_new (GTK_TYPE_ACTION,
		                       "name", entries[i].name,
		                       "label", entries[i].label,
		                       "tooltip", entries[i].tooltip,
		                       "stock_id", entries[i].stock_id,
		                       NULL);
		if (entries[i].callback)
			gperl_signal_connect (WRAPINSTANCE (action),
			                      "activate",
					      (SV*)(entries[i].callback),
					      user_data, 0);

		/* set the accel path for the menu item */
		accel_path = g_strconcat
				("<Actions>/",
				 gtk_action_group_get_name (action_group),
				 "/", entries[i].name, NULL);

		if (entries[i].accelerator) {
			guint accel_key = 0;
			GdkModifierType accel_mods;

			gtk_accelerator_parse (entries[i].accelerator,
			                       &accel_key, &accel_mods);
			if (accel_key)
				gtk_accel_map_add_entry (accel_path,
				                         accel_key,
				                         accel_mods);
		}

		gtk_action_set_accel_path (action, accel_path);
		g_free (accel_path);

		gtk_action_group_add_action (action_group, action);
		g_object_unref (action);
	}

##void gtk_action_group_add_toggle_actions (GtkActionGroup *action_group, GtkToggleActionEntry *entries, guint n_entries, gpointer user_data);
##void gtk_action_group_add_toggle_actions_full (GtkActionGroup *action_group, GtkToggleActionEntry *entries, guint n_entries, gpointer user_data, GDestroyNotify destroy);
void
gtk_action_group_add_toggle_actions (action_group, toggle_action_entries, user_data=NULL)
	GtkActionGroup * action_group
	SV * toggle_action_entries
	SV * user_data
    PREINIT:
	AV * av;
	GtkToggleActionEntry * entries;
	gint n_actions, i;
    CODE:
	if (!toggle_action_entries ||
	    !SvOK (toggle_action_entries) || !SvROK (toggle_action_entries) ||
	    SvTYPE (SvRV (toggle_action_entries)) != SVt_PVAV)
		croak ("entries must be a reference to an array of toggle action entries");
	av = (AV*) SvRV (toggle_action_entries);
	n_actions = av_len (av) + 1;
	if (n_actions < 1)
		croak ("toggle action array is empty");
	entries = gperl_alloc_temp (sizeof (GtkToggleActionEntry) * n_actions);
	for (i = 0 ; i < n_actions ; i++) {
		SV ** svp = av_fetch (av, i, 0);
		read_toggle_action_entry_from_sv (*svp, entries+i);
	}
	
	for (i = 0 ; i < n_actions ; i++) {
		GtkAction * action;
		gchar * accel_path;

		action = g_object_new (GTK_TYPE_TOGGLE_ACTION,
		                       "name", entries[i].name,
		                       "label", entries[i].label,
		                       "tooltip", entries[i].tooltip,
		                       "stock_id", entries[i].stock_id,
		                       NULL);
		gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
		                              entries[i].is_active);
		if (entries[i].callback)
			gperl_signal_connect (WRAPINSTANCE (action),
			                      "activate",
					      (SV*)(entries[i].callback),
					      user_data, 0);

		/* set the accel path for the menu item */
		accel_path = g_strconcat
				("<Actions>/",
				 gtk_action_group_get_name (action_group),
				 "/", entries[i].name, NULL);

		if (entries[i].accelerator) {
			guint accel_key = 0;
			GdkModifierType accel_mods;

			gtk_accelerator_parse (entries[i].accelerator,
			                       &accel_key, &accel_mods);
			if (accel_key)
				gtk_accel_map_add_entry (accel_path,
				                         accel_key,
				                         accel_mods);
		}

		gtk_action_set_accel_path (action, accel_path);
		g_free (accel_path);

		gtk_action_group_add_action (action_group, action);
		g_object_unref (action);
	}

##void gtk_action_group_add_radio_actions (GtkActionGroup *action_group, GtkRadioActionEntry *entries, guint n_entries, gint value, GCallback on_change, gpointer user_data);
##void gtk_action_group_add_radio_actions_full (GtkActionGroup *action_group, GtkRadioActionEntry *entries, guint n_entries, gint value, GCallback on_change, gpointer user_data, GDestroyNotify destroy);
void
gtk_action_group_add_radio_actions (action_group, radio_action_entries, value, on_change, user_data=NULL)
	GtkActionGroup * action_group
	SV * radio_action_entries
	gint value
	SV * on_change
	SV * user_data
    PREINIT:
	AV * av;
	GtkRadioActionEntry * entries;
	GtkAction * first_action = NULL;
	GSList * group = NULL;
	gint n_actions, i;
    CODE:
	if (!radio_action_entries ||
	    !SvOK (radio_action_entries) || !SvROK (radio_action_entries) ||
	    SvTYPE (SvRV (radio_action_entries)) != SVt_PVAV)
		croak ("radio_action_entries must be a reference to an array of action entries");
	av = (AV*) SvRV (radio_action_entries);
	n_actions = av_len (av) + 1;
	if (n_actions < 1)
		croak ("radio action array is empty");
	entries = gperl_alloc_temp (sizeof (GtkRadioActionEntry) * n_actions);
	for (i = 0 ; i < n_actions ; i++) {
		SV ** svp = av_fetch (av, i, 0);
		read_radio_action_entry_from_sv (*svp, entries+i);
	}
	
	for (i = 0 ; i < n_actions ; i++) {
		GtkAction * action;
		gchar * accel_path;

		action = g_object_new (GTK_TYPE_RADIO_ACTION,
		                       "name", entries[i].name,
		                       "label", entries[i].label,
		                       "tooltip", entries[i].tooltip,
		                       "stock_id", entries[i].stock_id,
		                       "value", entries[i].value,
		                       NULL);

		if (i == 0)
			first_action = action;
		gtk_radio_action_set_group (GTK_RADIO_ACTION (action), group);
		group = gtk_radio_action_get_group (GTK_RADIO_ACTION (action));
		if (value == entries[i].value)
			gtk_toggle_action_set_active
					(GTK_TOGGLE_ACTION (action), TRUE);

		/* set the accel path for the menu item */
		accel_path = g_strconcat
				("<Actions>/",
				 gtk_action_group_get_name (action_group),
				 "/", entries[i].name, NULL);

		if (entries[i].accelerator) {
			guint accel_key = 0;
			GdkModifierType accel_mods;

			gtk_accelerator_parse (entries[i].accelerator,
			                       &accel_key, &accel_mods);
			if (accel_key)
				gtk_accel_map_add_entry (accel_path,
				                         accel_key,
				                         accel_mods);
		}

		gtk_action_set_accel_path (action, accel_path);
		g_free (accel_path);

		gtk_action_group_add_action (action_group, action);
		g_object_unref (action);
	}

	if (on_change && SvOK (on_change))
		gperl_signal_connect (WRAPINSTANCE (first_action),
		                      "changed", on_change, user_data, 0);

## NOTE: we had to implement the group adding API in xs so that we can
##       properly destroy the user data and callbacks and such.  since we
##       reimplement, we can't get to the translation function, its data,
##       or the translation domain, which are held in the opaque private
##       data object of the action group.  not the end of the world, but
##       not great, either.  see #135740
##void gtk_action_group_set_translate_func (GtkActionGroup *action_group, GtkTranslateFunc func, gpointer data, GtkDestroyNotify notify);

##void gtk_action_group_set_translation_domain (GtkActionGroup *action_group, const gchar *domain);