/* $Id: e2_menu.c 2064 2010-03-12 13:15:36Z tpgww $

Copyright (C) 2004-2010 tooar <tooar@emelfm2.net>

This file is part of emelFM2.
emelFM2 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 3, or (at your option)
any later version.

emelFM2 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 emelFM2; see the file GPL. If not, see http://www.gnu.org/licenses.
*/

/**
@file src/utils/e2_menu.c
@brief Menu utility functions

This file contains utility and helper functions for menus.
*/

#include "emelfm2.h"
#include <string.h>
#include <pthread.h>
#include "e2_menu.h"
#include "e2_dialog.h"
#include "e2_plugins.h"
#include "e2_task.h"
#ifdef E2_FS_MOUNTABLE
# include "e2_filelist.h"
#endif

extern pthread_mutex_t task_mutex;

  /*******************/
 /**** callbacks ****/
/*******************/

/**
@brief execute action corresponding to item selected from a destroyable menu
We do this outside the manu-item "activated" callback to avoid disrupting gtk's
menu-activation process
@param art pointer to action runtime data

@return FALSE to remove the source
*/
static gboolean _e2_menu_action_deferred_cb (E2_ActionRuntime *art)
{
	printd (DEBUG, "timer callback: _e2_menu_action_deferred_cb");
	gdk_threads_enter ();
	e2_action_run (app.main_window, art); //FIXME get better widget
	gdk_threads_leave ();
	e2_action_free_runtime (art);
	e2_utils_fake_event (); //CHECKME does this actually help?
	return FALSE;
}
/**
@brief setup to execute action corresponding to item selected from a destroyable menu
This is the "activate" signal callback for action-initiators in a menu that's to
be destroyed after usage.
To avoid disrupting gtk's menu-activation process, we do not run the action here.
Note: depending on the menu-destruction process and whether the action is
synchronous, menu_item may not persist until the action is finished running
i.e. possible race
@param menuitem the selected menu item widget
@param data pointer to action runtime data

@return
*/
void e2_menu_action_activated_cb (GtkMenuItem *menu_item, gpointer data)
{
	printd (DEBUG, "e2_menu_action_activated_cb");
	//don't want this action-data to be destroyed with the menu
	E2_ActionRuntime *art = (E2_ActionRuntime *)g_object_steal_data
		(G_OBJECT (menu_item), "e2-free-actruntime");
	//an idle-callback is no good !?
	g_timeout_add (100, (GSourceFunc)_e2_menu_action_deferred_cb, art);
}

/**
@brief like _e2_menu_action_deferred_cb, but for menus which are not destroyed
@param art pointer to action runtime data

@return FALSE to remove the source
*/
static gboolean _e2_menu_action_deferred_cb2 (E2_ActionRuntime *art)
{
	printd (DEBUG, "timer callback: _e2_menu_action_deferred_cb2");
	gdk_threads_enter ();
	e2_action_run (app.main_window, art); //FIXME get better widget
	gdk_threads_leave ();
	e2_utils_fake_event (); //CHECKME does this actually help?
	return FALSE;
}
/**
@brief like e2_menu_action_activated_cb, but for menus which are not destroyed
@param menuitem the selected menu item widget
@param data pointer to action runtime data
*/
void e2_menu_action_activated_cb2 (GtkMenuItem *menu_item, gpointer data)
{
	printd (DEBUG, "e2_menu_action_activated_cb2");
	//an idle-callback is no good !?
	g_timeout_add (100, (GSourceFunc)_e2_menu_action_deferred_cb2,
		(E2_ActionRuntime *)data);
}
/**
@brief timer callback to cleanup a redundant menu widget
Assumes menu is hidden so that BGL is irrelevant
@param menu the widget to be cleared
@return FALSE to stop the timer
*/
gboolean e2_menu_destroy (GtkWidget *menu)
{
	printd (DEBUG, "timer callback: e2_menu_destroy, menu: %x", menu);
	gtk_widget_destroy (menu);
	return FALSE;
}
/**
@brief "selection-done" signal callback for @a menu, initiates its destruction

@param menu the menu widget
@param data UNUSED data specified when the callback was connected

@return
*/
void e2_menu_selection_done_cb (GtkWidget *menu, gpointer data)
{
	printd (DEBUG, "e2_menu_selection_done_cb, menu: %x", menu);
	//must not destroy the menu immediately, gtk upstream has not necessarily finished with it
#ifdef USE_GLIB2_14
	g_timeout_add_seconds (10,
#else
	g_timeout_add (10000,
#endif
		(GSourceFunc)e2_menu_destroy, menu);
}
/**
@brief callback to

@param menu_item the activated menu-item widget
@param widget widget specified when the callback was connected

@return
*/
void e2_menu_control_cb (GtkMenuItem *menu_item, gpointer widget)
{
	e2_option_connect (widget, gtk_check_menu_item_get_active
		(GTK_CHECK_MENU_ITEM (menu_item)));
}
/**
@brief update set value when tied check menu item is activated
@param menu_item the item that was activated
@param set pointer to data for optionset tied to @a menu_item
@return
*/
static void _e2_menu_check_value_changed_cb (GtkWidget *menu_item, E2_OptionSet *set)
{
	GtkWidget *controller = g_object_get_data (G_OBJECT (menu_item),
		"e2-controller-widget");
	if (!GPOINTER_TO_INT (g_object_get_data (G_OBJECT (controller),
		"e2-controller-blocked")))
		e2_option_bool_set_direct (set, gtk_check_menu_item_get_active
			(GTK_CHECK_MENU_ITEM (menu_item)));
}
/**
@brief set @a menu_item state if it's not blocked and not already at the desired state
This is a hook-function callack upon change of set data associated with @a menu_item
@param state pointerised TRUE/FALSE, the value to apply to @a menu_item
@param menu_item the widget to change

@return TRUE always
*/
static gboolean _e2_menu_check_change_value_hook (gpointer state, GtkWidget *menu_item)
{
	GtkWidget *controller = g_object_get_data (G_OBJECT (menu_item),
		"e2-controller-widget");
	if (!GPOINTER_TO_INT (g_object_get_data (G_OBJECT (controller),
		"e2-controller-blocked")))
	{
		gboolean value = GPOINTER_TO_INT (state);
		gboolean current = gtk_check_menu_item_get_active
			(GTK_CHECK_MENU_ITEM (menu_item));
		if (value != current)
			gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), value);
	}
	return TRUE;
}
/**
@brief add tied check item to @a menu

@param menu menu widget
@param set pointer to data for set whose value is tied to the check button
@param controller widget which may have "e2-controller-blocked" data to prevent set value being updated when item is activated, can be NULL
@param func callback function for handling menu item toggle, or NULL
@param data pointer to data to send to the callback

@return the menu item widget
*/
static GtkWidget *_e2_menu_add_tied_check (GtkWidget *menu, E2_OptionSet *set,
	GtkWidget *controller, void (*func)(GtkCheckMenuItem*,gpointer), gpointer data)
{
	GtkWidget *menu_item = e2_menu_add_check (menu, set->desc,
		e2_option_bool_get_direct (set), func, data);
	g_object_set_data (G_OBJECT (menu_item), "e2-controller-widget", controller);
	//this will update the set value when the check menu item is activated
	g_signal_connect (G_OBJECT (menu_item), "toggled",
		G_CALLBACK (_e2_menu_check_value_changed_cb), set);
	//this will update the check menu item when the config data value changes
	e2_option_attach_value_changed_simple (set, menu_item,
		(HookFunc)_e2_menu_check_change_value_hook, menu_item);

	e2_widget_set_safetip (menu_item, set->tip);

	//conform to dependent set if any
	e2_widget_handle_depends (menu_item, set);

	return menu_item;
}

  /****************/
 /**** public ****/
/****************/

/**
@brief add item to @a menu
Args of @a func should be (GtkMenuItem*,some pointer)
@param menu menu widget
@param label menu item text, optionally with mnemonic
@param icon custom iconfile path, or stock-icon identifier, or NULL for no icon
@param tip tooltip string, or NULL
@param activate_cb callback function for handling menu item activation, or NULL
@param data pointer to data to send to @a activate_cb

@return the menu item widget
*/
GtkWidget *e2_menu_add (GtkWidget *menu, gchar *labeltext, gchar *icon,
	gchar *tip, void (*activate_cb)(), gpointer data)
{
	GtkWidget *menu_item;
	gint choice;
	if (icon == NULL)
		choice = 2;	//no icon
	else
		choice = e2_option_sel_get ("menu-show-icons");
	if (choice == 0)	//theme
	{
		//ensure that the "gtk-menu-images" property is registered
		static GtkWidget *dummy = NULL;
		if (dummy == NULL)
		{
			dummy = gtk_image_menu_item_new ();
			gtk_widget_destroy (dummy);
		}
		GtkSettings* defs = gtk_settings_get_default ();	//assume this always exists
		gboolean show;
		g_object_get (G_OBJECT (defs), "gtk-menu-images", &show, NULL);
		if (show)
			choice = 1;
	}
	if (choice == 1) //show icon
	{
		GtkWidget *image = e2_widget_get_icon (icon, GTK_ICON_SIZE_MENU);	//e2_option_int_get ("menu-isize") + 1);
		if (image != NULL)
		{
			menu_item = gtk_image_menu_item_new_with_mnemonic
				(labeltext != NULL ? labeltext : "");
			gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), image);
		}
		else
			menu_item = gtk_menu_item_new_with_mnemonic
				(labeltext != NULL ? labeltext : _("Missing _image"));
	}
	else
		menu_item = gtk_menu_item_new_with_mnemonic (labeltext);

	//turn on markup for the label
	GList *member, *children = gtk_container_get_children (GTK_CONTAINER (menu_item));
	for  (member = children; member != NULL; member = member->next)
	{
		GtkWidget *child = (GtkWidget *)member->data;
		if (GTK_IS_LABEL (child))
		{
			gtk_label_set_use_markup (GTK_LABEL (child), TRUE);
			break;
		}
	}
	g_list_free (children);

	if (activate_cb != G_CALLBACK(NULL))
		g_signal_connect (G_OBJECT (menu_item), "activate",
			G_CALLBACK (activate_cb), data);
	gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
	gtk_widget_show_all (menu_item);

	if (tip != NULL && *tip != '\0')
		e2_widget_set_safetip (menu_item, tip);
	return menu_item;
}
/**
@brief add action item to destroyable @a menu

@param menu menu widget
@param label menu item text, optionally with mnemonic
@param icon custom iconfile path, or stock-icon identifier
@param tip tooltip string
@param action_name identifier of the form "_A(n)._A(m)", or external command
@param arg argument(s) string for the action, or NULL

@return the menu item widget, or NULL
*/
GtkWidget *e2_menu_add_action (GtkWidget *menu, gchar *label, gchar *icon,
	gchar *tip, gchar *action_name, gchar *arg)
{
	gchar *real_arg;
	E2_Action *action = e2_action_get_with_custom (action_name, arg, &real_arg);
	E2_ActionRuntime *art = e2_action_pack_runtime (action, real_arg, g_free);
	if (art != NULL)
	{
		//note: callback relates to destroyable menu
		GtkWidget *item = e2_menu_add (menu, label, icon, tip,
			e2_menu_action_activated_cb, art);
		g_object_set_data_full (G_OBJECT(item), "e2-free-actruntime",
			art, (GDestroyNotify)e2_action_free_runtime);
		return item;
	}
	return NULL;
}
/**
@brief add check item to @a menu
Args of @a func should be (GtkCheckMenuItem*,some pointer)
@param menu menu widget
@param label menu item text, optionally with mnemonic
@param state initial state of the created item, T/F
@param func void* callback function for handling menu item selection, or NULL
@param data pointer to data to send to the callback

@return the menu item widget
*/
GtkWidget *e2_menu_add_check (GtkWidget *menu,  gchar *label, gboolean state,
	void (*func)(), gpointer data)
{
	GtkWidget *check = gtk_check_menu_item_new_with_mnemonic (label);
	if (menu != NULL)
		gtk_menu_shell_append (GTK_MENU_SHELL (menu), check);
	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (check), state);
	if (func != NULL)
		g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (func), data);
	gtk_widget_show (check);
	return check;
}
/**
@brief add radio item to @a menu
Args of @a func should be (GtkMenuItem*,some pointer)
@param menu menu widget, or NULL
@param group pointer to gslist of radio group
@param label menu item text, optionally with mnemonic
@param state initial state of the created item, T/F
@param func callback function for handling menu-item activation, or NULL
@param data pointer to data to send to the callback

@return the menu item widget
*/
GtkWidget *e2_menu_add_radio (GtkWidget *menu, GSList **group, gchar *label,
	gboolean state, void (*func)(), gpointer data)
{
	GtkWidget *radio = gtk_radio_menu_item_new_with_mnemonic (*group, label);
	*group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (radio));
	if (menu != NULL)
		gtk_menu_shell_append (GTK_MENU_SHELL (menu), radio);
	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (radio), state);
	if (func != NULL)
		g_signal_connect (G_OBJECT (radio), "activate", G_CALLBACK (func), data);
	gtk_widget_show (radio);
	return radio;
}
/**
@brief add separator to @a menu

@param menu menu widget, or NULL

@return the menu item widget
*/
GtkWidget *e2_menu_add_separator (GtkWidget *menu)
{
	GtkWidget *sep = gtk_separator_menu_item_new ();
	if (menu != NULL)
		gtk_menu_shell_append (GTK_MENU_SHELL (menu), sep);
	gtk_widget_show (sep);
	return sep;
}
/**
@brief add tear-off to @a menu

@param menu menu widget

@return the menu item widget
*/
GtkWidget *e2_menu_add_tear_off (GtkWidget *menu)
{
	GtkWidget *menu_item = gtk_tearoff_menu_item_new ();
	gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
	gtk_widget_show (menu_item);
	return menu_item;
}
/**
@brief add sub-menu to @a menu
Note - no use to add a tooltip for item, it does not show
@param menu menu widget
@param label string with the name to appear in the menu
@param icon icon-file name string

@return the sub-menu item widget
*/
GtkWidget *e2_menu_add_submenu (GtkWidget *menu, gchar *label, gchar *icon)
{
	GtkWidget *menu_item = e2_menu_add (menu, label, icon, NULL, NULL, NULL);
	GtkWidget *submenu = gtk_menu_new ();
	gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu);
	gtk_widget_show (submenu);
	return submenu;
}
/**
@brief create toggle menu item and add it to destroyable @a menu
This expects to be called twice in succession for each toggle-item.
None of the strings may be NULL
@param menu the menu to which the item will be added
@param label menu label string
@param destroy TRUE if @a menu will be destroyed after use
@param icon menu icon name
@param tip menu tip string
@param type string "toggle.on" or "toggle.off"
@param cmd toggle action, for creating a hash key

@return after the first of the pair, NULL: after the second, the menu item or NULL upon error
*/
GtkWidget *e2_menu_add_toggle (GtkWidget *menu, gboolean destroy,
	gchar *label, gchar *icon, gchar *tip, gchar *type, gchar *cmd)
{
	static gboolean first = TRUE;
	static gboolean firststate;
	static gchar *firstlabel;
	static gchar *firsticon;
	static gchar *firsttip;
	static gchar *firstcmd; //NEVER FREE THIS
	if (label == NULL) label = "";
	if (icon == NULL) icon = "";
	if (tip == NULL) tip = "";
	if (cmd == NULL) cmd = "";
	if (first)
	{	//process 1st of a pair
		first = FALSE;
		//park until we get the paired name, so we can make hash key
		firststate = g_str_has_suffix (type, _A(116));	//_(on
		firstlabel = g_strdup (label);
		firsticon = g_strdup (icon);
		firsttip = g_strdup (tip);
		firstcmd = g_strdup (cmd);
		return NULL;
	}
	//process 2nd of the pair
	first = TRUE;
	//hash table key is the joined action strings
	gchar *hashkey = g_strconcat (firstcmd, ".", cmd, NULL);
	//get toggle data, if any
	E2_ToggleData *data = g_hash_table_lookup (toggles_hash, hashkey);
	if (data == NULL)
	{
		data = ALLOCATE (E2_ToggleData);	//deallocation when # cleared
		CHECKALLOCATEDFATAL (data);
		g_hash_table_insert (toggles_hash, g_strdup (hashkey), data);
		data->current_state = firststate;
		data->boxes = NULL;
		if (firststate)
		{
			data->true_action = firstcmd;	//action command string
			data->false_action = g_strdup (cmd); //ditto
		}
		else
		{
			data->false_action = firstcmd;
			data->true_action = g_strdup (cmd);
		}
	}

	gchar *uselabel, *useicon, *usetip;
	if (data->current_state == firststate)
	{
		uselabel = firstlabel;
		useicon = firsticon;
		usetip = firsttip;
	}
	else
	{
		uselabel = label;
		useicon = icon;
		usetip = tip;
	}
	gchar *realarg;
	E2_Action *action = e2_action_get_with_custom (type, cmd, &realarg);
	E2_ActionRuntime *art = e2_action_pack_runtime (action, hashkey, g_free); //CHECKME realarg instead ?
	GtkWidget *item;
	if (art != NULL)
	{
		item = e2_menu_add (menu, uselabel, useicon, usetip,
		(destroy) ? e2_menu_action_activated_cb:e2_menu_action_activated_cb2,
		art);
		g_object_set_data_full (G_OBJECT(item), "e2-free-actruntime",
			art, (GDestroyNotify)e2_action_free_runtime);
	}
	else
		item = NULL;

	g_free (firstlabel);
	g_free (firsticon);
	g_free (firsttip);
	g_free (realarg);

	return item;
}
/**
@brief popup detroyable menu @a menu

@param menu menu widget
@param button event button
@param time event time

@return
*/
void e2_menu_popup (GtkWidget *menu, gint button, guint32 time)
{
	g_signal_connect (G_OBJECT (menu), "selection-done",
		G_CALLBACK (e2_menu_selection_done_cb), NULL);
	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
}
/* *
@brief

@param menu menu widget
@param x UNUSED
@param y UNUSED
@param button event button
@param time event time

@return
*/
/* UNUSED
void _e2_menu_popup (GtkWidget *menu, gint x, gint y, gint button, guint32 time)
{
	g_signal_connect (G_OBJECT (menu), "selection-done",
		G_CALLBACK (e2_menu_selection_done_cb), NULL);
	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
}
*/
/**
@brief add items for a series of config options to @a menu

@param controller "parent" widget for the created menu
@param menu menu widget to populate, or NULL to create a new one
@param set first of a NULL-terminated series of option data structs

@return @a menu or a new menu widget
*/
GtkWidget *e2_menu_create_options_menu (GtkWidget *controller, GtkWidget *menu,
	E2_OptionSet *set, ...)
{
	if (menu == NULL)
		menu = gtk_menu_new ();
	va_list args;
	va_start (args, set);
	while (set != NULL)
	{
		void (*func)(GtkCheckMenuItem*,gpointer) = va_arg (args, gpointer);
		gpointer data = va_arg (args, gpointer);
		switch (set->type)
		{
			case E2_OPTION_TYPE_BOOL:
				_e2_menu_add_tied_check (menu, set, controller, func, data);
				break;
			case E2_OPTION_TYPE_SEL:
				e2_option_sel_add_menu_widget (controller, menu, set, func, data);
				break;
			default:
				break;
		}
		set = va_arg (args, E2_OptionSet *);
	}
	va_end (args);

	return menu;
}
/**
@brief add items to bookmarks menu, recursively if children exist

@param menu widget to which the items will be added
@param onetime TRUE if @a menu is destroyed after use
@param action pointer to data for the pseudo-action being processed
@param panenum the pane whose path will be used when adding a mark (E2PANECUR, E2PANE1 or E2PANE2)
@param mdl pointer to bookmarks config data tree model
@param iter pointer to iter to be used for interrogating @a mdl

@return
*/
void e2_menu_add_bookmark_items (GtkWidget *menu, gboolean onetime,
	E2_Action *action, gint panenum, GtkTreeModel *mdl, GtkTreeIter *iter)
{
	do
	{
		gchar *name, *icon, *_tip, *_path;
		//don't support variables in label
//		gchar *_name, *icon, *_tip, *_path;
		gtk_tree_model_get (mdl, iter, 0, &name, 1, &icon, 2, &_tip, 3, &_path, -1);
//		gchar *name = e2_utils_replace_vars (_name, FALSE);
		gchar *tip = e2_utils_replace_vars (_tip, FALSE);
		gchar *path = e2_utils_replace_vars (_path, TRUE);
//		g_free (_name);
		g_free (_tip);
		g_free (_path);

		if (gtk_tree_model_iter_has_child (mdl, iter))
		{
			GtkTreeIter iter2;
			gtk_tree_model_iter_children (mdl, &iter2, iter);
			//sub-menu items can't show tip, context menu etc
			GtkWidget *submenu = e2_menu_add_submenu (menu, name, icon);
			e2_menu_add_bookmark_items (submenu, onetime, action, panenum, mdl, &iter2);	//recurse
			g_free (path);
		}
		else
		{
			GtkWidget *item = e2_menu_add (menu, name, icon, tip, NULL, NULL);
			g_signal_connect_data (G_OBJECT (item), "button-press-event",
				G_CALLBACK (e2_bookmark_button_press_cb),
				gtk_tree_model_get_path (mdl, iter),
				(GClosureNotify) gtk_tree_path_free, 0);
			void (*activatecb)() = (onetime) ?
				e2_menu_action_activated_cb : e2_menu_action_activated_cb2;
			E2_ActionRuntime *art = e2_action_pack_runtime (action,
				g_strdup_printf ("%d %s", panenum, path), g_free);
			//must connect_after to the activate signal, or else the
			//button-press-event will never occur
			g_signal_connect_after (G_OBJECT (item), "activate",
				G_CALLBACK (activatecb), art);
			g_object_set_data_full (G_OBJECT (item), "e2-free-actruntime", art,
				(GDestroyNotify) e2_action_free_runtime);
			//path to be cleaned when item is destroyed
			g_object_set_data_full (G_OBJECT (item), "bookmark-path", path, g_free);
			//signal which pane (or default) to use when adding a mark or opening a dir
			g_object_set_data (G_OBJECT (item), "bookmark-pane", GINT_TO_POINTER (panenum));
		}
		g_free (name);
		g_free (icon);
		g_free (tip);
	} while (gtk_tree_model_iter_next (mdl, iter));
}
/**
@brief add items for all relevant menu-enabled plugins, to destroyable @a menu
Assumes app.plugins is in menu-order, for "non-child" plugins at least, and
for any "parent" plugin, its embedded list of children is in sub-menu order
@param menu widget to which items are added
@param onetime TRUE if @a menu is destroyed after usage
@param is_selection TRUE if one or more items are selected in active pane

@return
*/
void e2_menu_create_plugins_menu (GtkWidget *menu, gboolean onetime, gboolean is_selection)
{
	gchar *prefix = (is_selection) ? NULL : g_strconcat (_A(6), ".", NULL);
	GList *tmp;
	for (tmp = app.plugins; tmp != NULL; tmp = tmp->next)
	{
		Plugin *p = tmp->data;
		if (p->show_in_menu && (is_selection || !g_str_has_prefix (p->action->name, prefix)))
		{
			E2_ActionRuntime *art;
			GtkWidget *item;
			//FIXME plugin action runtime data from ? ATM plugins expect no data,
			// tho' action->data may be relevant
//			gpointer pdata = NULL;
//			gpointer pclean = NULL;
			void (*activatecb)() = (onetime) ? e2_menu_action_activated_cb:e2_menu_action_activated_cb2;
			if (p->child_list != NULL)
			{
				//create item and child menu
				GtkWidget *menu2 = e2_menu_add_submenu (menu, (gchar *)p->menu_name,
					(gchar *)p->icon);
				GList *member;
				for (member = p->child_list; member != NULL; member = member->next)
				{
					Plugin *pc = (Plugin *)member->data;
					if (pc->show_in_menu)
					{
						art = e2_action_pack_runtime (pc->action, NULL, NULL);
						if (art != NULL)
						{
							item = e2_menu_add (menu2, (gchar *)pc->menu_name,
								(gchar *)pc->icon, (gchar *)pc->description, activatecb, art);
							g_object_set_data_full (G_OBJECT(item), "e2-free-actruntime",
								art, (GDestroyNotify)e2_action_free_runtime);
						}
					}
				}
			}
			else //just 1 action in the plugin, or it's a child
				if (p->module != NULL	//it's not a child
					&& p->show_in_menu)
			{
				art = e2_action_pack_runtime (p->action, NULL, NULL);
				if (art != NULL)
				{
					item = e2_menu_add (menu, (gchar *)p->menu_name, (gchar *)p->icon,
						(gchar *)p->description, activatecb, art);
					g_object_set_data_full (G_OBJECT(item), "e2-free-actruntime",
						art, (GDestroyNotify)e2_action_free_runtime);
				}
			}
		}
	}
	if (!is_selection)
		g_free (prefix);
}
/**
@brief process custom-menu item

This is the callback for 'activated' signal for the menu item

@param item the activated menu item
@param data UNUSED data specified when callback was connected

@return
*/
static void _e2_menu_custom_cb (GtkMenuItem *item, gpointer data)
{
	//command may be slow, get this out of the way
/*/	gtk_widget_hide (
#ifdef USE_GTK2_14
		get_widget_get_parent (item)
#else
		item->parent
#endif
	);
*/
	gchar *cmd = (gchar *) g_object_get_data (G_OBJECT (item), "custom-command");
	e2_command_run (cmd, E2_COMMAND_RANGE_DEFAULT, item
#ifdef E2_COMMANDQ
	, FALSE
#endif
	);
	//FIXME some sort of termination process ?
}
//static void _e2_menu_custom_finished_cb (GtkWidget *menu, gpointer data)
//{
//}
/**
@brief add items to a menu of custom commands

@param model config option tree model for custom menus
@param iter tree iter to use for interrogating @a model
@param menu the menu to which items are to be added

@return
*/
static void _e2_menu_add_custom_items (GtkTreeModel *model, GtkTreeIter *iter, GtkWidget *menu)
{
	GtkTreeIter iter2;
	gchar *icon, *label, *tip, *command;
	do
	{
		gtk_tree_model_get (model, iter, 1, &icon, 2, &label, 3, &tip, 4, &command, -1);
		if (gtk_tree_model_iter_children (model, &iter2, iter))
		{
			GtkWidget *submenu = e2_menu_add_submenu (menu, label, icon);
			_e2_menu_add_custom_items (model, &iter2, submenu);	//recurse
			g_free (command);
		}
		else
		{
			GtkWidget *menu_item = e2_menu_add (menu, label, icon, tip,
					_e2_menu_custom_cb, NULL);
			g_object_set_data_full (G_OBJECT (menu_item), "custom-command",
				command, (GDestroyNotify) g_free);
		}
		//cleanup except command
		g_free (icon);
		g_free (label);
		g_free (tip);
	} while (gtk_tree_model_iter_next (model, iter));
}
/**
@brief construct a menu of custom commands

@param name utf8 string, name of custom menu to use

@return the menu item widget, or NULL
*/
GtkWidget *e2_menu_create_custom_menu (gchar *name)
{
	GtkTreeIter iter, iter2;
	E2_OptionSet *set = e2_option_get ("custom-menus");
	if (e2_tree_find_iter_from_str (set->ex.tree.model, 0, name, &iter, TRUE)
		&& gtk_tree_model_iter_children (set->ex.tree.model, &iter2, &iter))
	{
		GtkWidget *menu = gtk_menu_new ();
		_e2_menu_add_custom_items (set->ex.tree.model, &iter2, menu);
		return menu;
	}
	return NULL;
}
/**
@brief construct but not pop up a destroyable menu of running child processes,
  with items which upon activation callback to @a func
Args for @a func should be (GtkMenuItem*, some pointer)
This func is usable for button-menu and output context menu
Uses check-items (no mnemonics) to indicate which procesess are active
@param type enumerator for which processes to gather
@param activate_cb callback function for handling menu item activation

The arguments for @a activate_cb must be (GtkMenuItem*,E2_TaskRuntime*).

@return the menu widget, or NULL
*/
#define CHILDMENUWIDTH 30
GtkWidget *e2_menu_create_child_menu (E2_ChildMenuType type, void (*activate_cb)())
{
	gchar *label, *s;
	gboolean active, empty = TRUE;
	GtkWidget *item, *menu = gtk_menu_new ();
	pthread_mutex_lock (&task_mutex);
	GList *member = app.taskhistory;
	pthread_mutex_unlock (&task_mutex);
	while (member != NULL)
	{
		E2_TaskRuntime *rt = member->data;
		if (type == E2_CHILD_ALL ||
			(type == E2_CHILD_ACTIVETASKS &&
			 (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED)
			) ||
			(!rt->action &&	//for these menus, we're not interested in actions
			 ( (type == E2_CHILD_ACTIVE &&
				(rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED))
			|| (type == E2_CHILD_OUTPUT &&
				gtk_text_buffer_get_mark (app.tab.buffer, rt->pidstr) != NULL) )
		    )
		   )
		{
			if (type == E2_CHILD_ACTIVETASKS && rt->action)
			{
				E2_ActionTaskData *atask = &rt->ex.action;
				gchar *srcdir = F_FILENAME_FROM_LOCALE (atask->currdir);
				gchar *shortdir = e2_utils_str_shorten (srcdir, 20, E2_DOTS_START);
				const gchar *subject;
				if (g_str_has_prefix (atask->action->name, _A(6)))
				{	//this was a "file-action"
					subject =
					(atask->rt_data == NULL || *((gchar *)atask->rt_data) == '\0') ?
						_("<selected items>") : (gchar *)atask->rt_data;
				}
				else
					subject = "";	//results in a redundant trailing space in the label
				label = g_strdup_printf ("%s %s %s", atask->action->name, shortdir, subject);
				F_FREE (srcdir, atask->currdir);
				g_free (shortdir);
			}
			else if (!rt->action)
			{
				//ellipsize the middle of longish commands
				s = e2_utils_str_shorten (rt->ex.command.command,
					CHILDMENUWIDTH - 7, E2_DOTS_MIDDLE);
				label = g_strconcat (rt->pidstr, ": ", s, NULL);
				g_free (s);
			}
			else
				label = NULL;

			if (label != NULL)
			{
				empty = FALSE;

				if (type == E2_CHILD_ALL)
				{
					item = gtk_check_menu_item_new_with_label (label);
					active = (rt != NULL
						&& (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED));
					gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), active);
				}
				else
				{
					item = gtk_menu_item_new_with_label (label);
				}
				if (activate_cb != G_CALLBACK (NULL))
					g_signal_connect (G_OBJECT (item), "activate",
						G_CALLBACK (activate_cb), rt);
				gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
				g_free (label);
			}
		}
		pthread_mutex_lock (&task_mutex);
		member = member->next;
		pthread_mutex_unlock (&task_mutex);
	}
	if (empty)
	{
		if (type == E2_CHILD_OUTPUT)
		{
			gtk_widget_destroy (menu);
			menu = NULL;
		}
		else
		{
			e2_menu_add (menu, _("no children"), NULL, NULL, activate_cb, NULL);
		}
	}

	if (menu != NULL)
	{
		gtk_widget_show_all (menu);
		g_signal_connect (G_OBJECT (menu), "selection-done",
			G_CALLBACK (e2_menu_selection_done_cb), NULL);
	}

	return menu;
}
/**
@brief create but not pop up a destroyable filters-menu for pane to which @a view belongs

@param view data struct for view to be changed

@return the menu widget
*/
GtkWidget *e2_menu_create_filter_menu (ViewInfo *view)
{
	GtkWidget *menu = gtk_menu_new ();
	view->check_name = e2_menu_add_check (menu, _("_Name filter"),
		view->name_filter.active, e2_name_filter_dialog_create_cb, view);
	view->check_size = e2_menu_add_check (menu, _("_Size filter"),
		view->size_filter.active, e2_size_filter_dialog_create_cb, view);
	view->check_date = e2_menu_add_check (menu, _("_Date filter"),
		view->date_filter.active, e2_date_filter_dialog_create_cb, view);
	e2_menu_add_separator (menu);
	view->check_dirs = e2_menu_add_check (menu, _("_Directories too"),
		view->filter_directories, e2_fileview_filter_dirs_cb, view);
	if (view->name_filter.active
		|| view->size_filter.active
		|| view->date_filter.active)
	{
		e2_menu_add_separator (menu);
		e2_menu_add (menu, _("_Remove all filters"), NULL, NULL,
			e2_fileview_remove_filters_cb, view);
	}

	g_signal_connect (G_OBJECT (menu), "selection-done",
		G_CALLBACK (e2_menu_selection_done_cb), NULL);

	return menu;
}
#ifdef E2_FS_MOUNTABLE
/**
@brief handle a selection from a mountpoints menu
The state of @a item when it arrives here is opposite to that
shown in the menu, when clicked
@param item the selected menu item
@param from the widget where the mounts menu was initiated

@return
*/
static void _e2_menu_mount_cb (GtkCheckMenuItem *item, gpointer from)
{
	gboolean newstate = gtk_check_menu_item_get_active (item);
//	gint result;
	gchar *elsewhere = NULL;
	gchar *point;
//#ifdef USE_GTK2_16
//	point = gtk_menu_item_get_label (GTK_MENU_ITEM (item));
//#else
	point = g_object_get_data (G_OBJECT (item), "_mountpoint_");
//#endif
#if defined (E2_HAL) || defined (E2_DEVKIT)
	gboolean remove = e2_fs_mount_is_ejectable (point);	//check this before unmount
#endif
	//CHECKME synchronous umount command to mimimize risk of CWD error
//tag E2_BADQUOTES
	gchar *qp = e2_utils_quote_string (point);
	gchar *cmd = g_strconcat ((newstate) ? E2_MOUNTCOMMAND : "|"E2_UNMOUNTCOMMAND,
		" ", qp, NULL);
	if (!newstate //doing an unmount
#ifdef E2_VFSTMP
	//FIXME dir when not mounted local
#else
		&& g_str_has_prefix (curr_view->dir, point))
#endif
	{	//if possible, change CWD out of mountpoint so we don't ourself block the unmount
		gchar *s = strrchr (point, G_DIR_SEPARATOR);	//assumes no trailer on mountpoint string
		if (s > point+1) //not doing root dir
			elsewhere = g_strndup (point, s - point);
		else //can't cd
			elsewhere = g_strdup (G_DIR_SEPARATOR_S);

		e2_fs_get_valid_path (&elsewhere, TRUE E2_ERR_NONE());
		//if (
			e2_fs_chdir (elsewhere E2_ERR_NONE()); //)
		//result =
		e2_command_run_at (cmd, elsewhere, E2_COMMAND_RANGE_DEFAULT, from	//has temporary change CWD
#ifdef E2_COMMANDQ
		, FALSE
#endif
		);
		//else
			//FIXME warn user about blockage
	}
	else
//	result =
		e2_command_run (cmd, E2_COMMAND_RANGE_DEFAULT, from
#ifdef E2_COMMANDQ
		, FALSE
#endif
		);
	g_free (cmd);

	if (newstate) //doing a mount
	{
#ifdef E2_FAM
		e2_filelist_request_refresh (curr_view->dir, FALSE);
		e2_filelist_request_refresh (other_view->dir, TRUE);
#else
		e2_filelist_check_dirty (GINT_TO_POINTER (1));
#endif
	}
	else	//doing an unmount
	{
#if defined (E2_HAL) || defined (E2_DEVKIT)
		if (remove)
		{
			cmd = g_strdup_printf ("eject %s", qp);
			e2_command_run_at (cmd, elsewhere, E2_COMMAND_RANGE_DEFAULT, from
# ifdef E2_COMMANDQ
			, FALSE
# endif
			);
			g_free (cmd);
		}
#endif
		if (elsewhere != NULL)
		{
//E2_VFSTMP may change space too
//			e2_pane_change_dir (curr_pane, point);	//not needed, with inofify at least
			g_free (elsewhere);
		}
	}
	g_free (qp);

/*	if (!(newstate || result == 0))
	{	//change back to where we started
		e2_pane_change_dir (curr_pane, olddir);
		g_free (olddir);
	}
*/
}
/**
@brief add check item to @a menu

Can't use e2_menu_add_check() because we don't want mnemonics

@param menu menu widget
@param label menu item text
@param state initial state of the created item, T/F
@param func void* callback function for handling menu item selection, or NULL
@param data data for callback

@return the menu item widget
*/
static GtkWidget *_e2_menu_mount_add_check (GtkWidget *menu,  gchar *label,
	gboolean state, void (*func)(GtkCheckMenuItem*,gpointer), gpointer data)
{
	GtkWidget *check = gtk_check_menu_item_new_with_label (label);
	gtk_menu_shell_append (GTK_MENU_SHELL (menu), check);
	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (check), state);
	if (func != NULL)
		g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (func), data);
	return check;
}

static GtkWidget *selectedmount = NULL;
/**
@brief "select" signal callback to log which item in mounts-menu is current
@param item the slected menu-item
@param user_data UNUSED
@return
*/
void _e2_menu_mount_select_cb (GtkItem *item, gpointer user_data)
{
	selectedmount = GTK_WIDGET(item);
}
/**
@brief key press signal callback to insert into the commandline the path of the selected item in @a menu
@param menu the focused menu-widget when the key was pressed
@param event pointer to event data
@param user_data UNUSED
@return
*/
static gboolean _e2_menu_mount_keypress_cb (GtkWidget *menu,
	GdkEventKey *event, gpointer user_data)
{
	printd (DEBUG, "_e2_menu_mount_keypress_cb");
	if (event->keyval == GDK_Insert)
	{
		const gchar *path;
//#ifdef USE_GTK2_16
//		path = gtk_menu_item_get_label (GTK_MENU_ITEM (selectedmount));
//#else
		path = g_object_get_data (G_OBJECT(selectedmount), "_mountpoint_");
//#endif
		e2_command_line_insert (path);
		return TRUE;
	}
	return FALSE;
}
/**
@brief create and pop up a destroyable mountpoints menu

@param from the button that was clicked to popup the menu
@param art action runtime data

@return TRUE if the action succeeded
*/
gboolean e2_menu_create_mounts_menu (gpointer from, E2_ActionRuntime *art)
{
	gchar *point;
	GList *mounts, *mountables, *member, *node;
	GtkWidget *item;

	if (GTK_IS_BUTTON (from))
	{
		art->state &= ~GDK_MOD2_MASK;
		if (!ACTION_CLICK(art))
			return FALSE;
	}

	GtkWidget *menu = gtk_menu_new ();

	mounts = e2_fs_mount_get_mounts_list ();
	for (member = mounts ; member != NULL; member = member->next)
	{
		item = _e2_menu_mount_add_check (menu, (gchar *) member->data, TRUE,
				_e2_menu_mount_cb, from);
		g_signal_connect (G_OBJECT (item), "select",
			G_CALLBACK (_e2_menu_mount_select_cb), NULL);
		g_object_set_data_full (G_OBJECT (item), "_mountpoint_",
			member->data, g_free);
	}

	mountables = e2_fs_mount_get_mountable_list ();
	for (member = mountables ; member != NULL; member = member->next)
	{
		point = (gchar *) member->data;
		gboolean mounted = FALSE;
		for (node = mounts; node != NULL; node = node->next)
		{
			if (!strcmp (point, (gchar *) node->data))
			{
				mounted = TRUE;
				break;
			}
		}
		if (mounted)
			g_free (member->data);
		else
		{
			item = _e2_menu_mount_add_check (menu, (gchar *) member->data,
					FALSE, _e2_menu_mount_cb, from);
			g_signal_connect (G_OBJECT (item), "select",
				G_CALLBACK (_e2_menu_mount_select_cb), NULL);
			g_object_set_data_full (G_OBJECT (item), "_mountpoint_",
				member->data, g_free);
		}
	}

	g_list_free (mounts);	//its data are cleared with the menu
	g_list_free (mountables); //ditto

	g_signal_connect (G_OBJECT (menu), "key-press-event",
		G_CALLBACK(_e2_menu_mount_keypress_cb), NULL);
	g_signal_connect (G_OBJECT (menu), "selection-done",
		G_CALLBACK (e2_menu_selection_done_cb), NULL);

	gtk_widget_show_all (menu);
	if (GTK_IS_BUTTON (from))
		gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
			(GtkMenuPositionFunc) e2_toolbar_set_menu_position, from, 1, 0);
	else
		gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 1, 0);

	return TRUE;
}
#endif	//def E2_FS_MOUNTABLE
/**
@brief install default tree options for custom menus
This function is called only if the default is missing from the config file
This default set data is for example purposes only, the user must provide real
data
@param set pointer to set data
@return
*/
static void _e2_menu_tree_defaults (E2_OptionSet *set)
{
	e2_option_tree_setup_defaults (set,
	g_strdup("custom-menus=<"),  //internal name
	g_strdup("fusemounts||||"),	//menu lookup name
	//these are examples only, no translation
	g_strconcat ("\t|vfs_on"E2ICONTB"|","_Mount","|","Mount tip","|","FTPDIR=/whatever;mount-command $FTPDIR",NULL),
	g_strconcat ("\t|vfs_off"E2ICONTB"|","_Unmount choices","||",NULL),
	g_strconcat ("\t\t||","_Unmount","|","Unount tip","|","fusermount -u $FTPDIR",NULL),
	g_strdup(">"),
	NULL);
}
/**
@brief setup tree option for custom menus

@return
*/
void e2_menu_custom_option_register (void)
{
	gchar *group_name = g_strconcat(_C(20) ,".",_C(9),NULL);  //_("interface.custom menus";
	E2_OptionSet *set = e2_option_tree_register ("custom-menus", group_name, _C(9), //no translation
		NULL, NULL, NULL, E2_OPTION_TREE_UP_DOWN | E2_OPTION_TREE_ADD_DEL,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP | E2_OPTION_FLAG_BUILDBARS);
	e2_option_tree_add_column (set, _("Menu"), E2_OPTION_TREE_TYPE_STR, 0, "",
		0, NULL, NULL);
	//since this a a new addition to config files, put the icon before the label
	e2_option_tree_add_column (set, _("Icon"), E2_OPTION_TREE_TYPE_ICON, 0, "",
		0, NULL, NULL);
	e2_option_tree_add_column (set, _("Label"), E2_OPTION_TREE_TYPE_STR, 0, "",
		0, NULL, NULL);
	e2_option_tree_add_column (set, _("Tooltip"), E2_OPTION_TREE_TYPE_STR, 0, "",
		0, NULL, NULL);
	e2_option_tree_add_column (set, _("Command"), E2_OPTION_TREE_TYPE_STR, 0, "",
		0, NULL,
		GINT_TO_POINTER (E2_ACTION_EXCLUDE_TOGGLE));
	e2_option_tree_create_store (set);

	e2_option_tree_prepare_defaults (set, _e2_menu_tree_defaults);
}
