/* $Id: e2_menu.c 853 2008-04-07 10:38:17Z tpgww $

Copyright (C) 2004-2008 tooar <tooar@gmx.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, contact the Free Software
Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

/**
@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 callback to destroy widget @a menu

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

@return
*/
void e2_menu_destroy_cb (GtkWidget *menu, gpointer data)
{
	//CHECKME is this not done automatically ?
	gtk_container_foreach (GTK_CONTAINER (menu), (GtkCallback)gtk_widget_destroy, NULL);
	gtk_widget_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 (GtkWidget *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 (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 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
*/
static GtkWidget *_e2_menu_add_tied_check (GtkWidget *menu, E2_OptionSet *set,
	GtkWidget *controller, gpointer func, 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,
		_e2_menu_check_change_value, menu_item);
#ifdef USE_GTK2_12TIPS
	gtk_widget_set_tooltip_text (
#else
	e2_widget_set_tooltip (NULL,
#endif
		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

@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 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 (GtkWidget *menu, gchar *label, gchar *icon,
	gchar *tip, void *func, gpointer data)
{
	GtkWidget *menu_item;
	if ((icon != NULL) && (e2_option_bool_get ("menu-show-icons")))
	{
		menu_item = gtk_image_menu_item_new_with_mnemonic
			(label != NULL ? label : "");
		GtkWidget *image = e2_widget_get_icon (icon,
			e2_option_int_get ("menu-isize") + 1);
		if (image != NULL)
			gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), image);
	}
	else
		menu_item = gtk_menu_item_new_with_mnemonic (label);

	//turn on markup usage for the menu item
	GList *children = gtk_container_get_children (GTK_CONTAINER (menu_item));
	GtkWidget *lab = children->data;	//this is a GtkAccelLabel;
	gtk_label_set_use_markup (GTK_LABEL (lab), TRUE);

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

	if (tip != NULL && *tip != '\0')
#ifdef USE_GTK2_12TIPS
		gtk_widget_set_tooltip_text (
#else
		e2_widget_set_tooltip (NULL,
#endif
			menu_item, tip);
	return menu_item;
}
/**
@brief add action item to @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
@param freefunc pointer to function that frees @a arg when the action is finished

@return the menu item widget
*/
GtkWidget *e2_menu_add_action (GtkWidget *menu, gchar *label, gchar *icon,
	gchar *tip, gchar *action_name, gchar *arg, gpointer freefunc)
{
	gchar *real_arg;
	E2_Action *action = e2_action_get_with_custom (action_name, arg, &real_arg);
	E2_ActionRuntime *a_rt = e2_action_pack_runtime (action, real_arg, freefunc);
	GtkWidget *menu_item = e2_menu_add (menu, label, icon, tip, e2_action_run,
		a_rt);
	g_object_set_data_full (G_OBJECT (menu_item), "free-callback-data",
		a_rt, (GDestroyNotify) e2_action_free_runtime);
	return menu_item;
}
/**
@brief add check item to @a menu

@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

@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 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_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 @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 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 NULL for the first of the pair, the menu item after the second
*/
GtkWidget *e2_menu_add_toggle (GtkWidget *menu, 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(111));	//_(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 *actionrt = e2_action_pack_runtime (action, hashkey, g_free);
	GtkWidget *item = e2_menu_add (menu, uselabel, useicon, usetip,
				e2_action_run, actionrt);
	g_object_set_data_full (G_OBJECT (item), "free-callback-data", actionrt,
		(GDestroyNotify) e2_action_free_runtime);

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

	return item;
}
/**
@brief popup 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_destroy_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_destroy_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 the 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)
	{
		gpointer func = 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 action pointer to data for the pseudo-action being processed
@param set pointer to bookmarks option data
@param menu widget to which the items will be added
@param iter pointer to iter to be used for interrogating the treemodel for @a set

@return
*/
void e2_menu_create_bookmarks_menu (E2_Action *action,
	E2_OptionSet *set, GtkWidget *menu, GtkTreeIter *iter)
{
	//find which pane to use for the path, when adding marks
	gpointer pane = NULL;	//defualt = use current pane
	if (strstr (action->name, _A(11)) != NULL)
		pane = (GINT_TO_POINTER (1));
	else if (strstr (action->name, _A(12)) != NULL)
		pane = (GINT_TO_POINTER (2));

	do
	{
		gchar *name, *icon, *_tip, *_path;
		//don't support variables in label
//		gchar *_name, *icon, *_tip, *_path;
		gtk_tree_model_get (set->ex.tree.model, iter, 0, &name, 1, &icon, 2, &_tip, 3, &_path, -1);
//		gchar *name = e2_utils_replace_vars (_name);
		gchar *tip = e2_utils_replace_vars (_tip);
		gchar *path = e2_utils_replace_vars (_path);
//		g_free (_name);
		g_free (_tip);
		g_free (_path);

		GtkWidget *item;
		gboolean child = gtk_tree_model_iter_has_child (set->ex.tree.model, iter);
		if (child)
		{
			//sub-menu items can't show tip, context menu etc
			GtkWidget *menu2 = gtk_menu_new ();
			GtkTreeIter iter2;
			gtk_tree_model_iter_children (set->ex.tree.model, &iter2, iter);
			e2_menu_create_bookmarks_menu (action, set, menu2, &iter2);	//recurse
			item = e2_menu_add (menu, name, icon, tip, NULL, NULL);
			gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu2);
			g_free (path);
		}
		else
		{
			item = e2_menu_add (menu, name, icon, tip, NULL, NULL);
			g_signal_connect_data (G_OBJECT (item), "button-press-event",
				G_CALLBACK (e2_bookmark_click_cb),
				gtk_tree_model_get_path (set->ex.tree.model, iter),
				(GClosureNotify) gtk_tree_path_free, 0);
			//gtk sillyness fix, if we don't use connect_after for the activate
			//signal of the menuitem, the button-press-event will never occur
			//path is cleaned when art is destroyed
			E2_ActionRuntime *art = e2_action_pack_runtime (action, path, g_free);
			g_signal_connect_after (item, "activate", G_CALLBACK (e2_action_run), art);
			g_object_set_data_full (G_OBJECT (item), "free-callback-data-bookmark", art,
				(GDestroyNotify) e2_action_free_runtime);
			g_object_set_data (G_OBJECT (item), "bookmark-path", path);
			//signal which pane (or default) to use when adding an item
			g_object_set_data (G_OBJECT (item), "bookmark-pane", pane);
		}
		g_free (name);
		g_free (icon);
		g_free (tip);
	} while (gtk_tree_model_iter_next (set->ex.tree.model, iter));
}
/**
@brief add items for all relevant menu-enabled plugins, to @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 is_selection TRUE if one or more items are selected in active pane

@return
*/
void e2_menu_create_plugins_menu (GtkWidget *menu, gboolean is_selection)
{
	gchar *prefix = (is_selection) ? NULL : g_strconcat (_A(5), ".", 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)))
		{
			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)
						e2_menu_add (menu2, (gchar *)pc->menu_name, (gchar *)pc->icon,
							(gchar *)pc->description, e2_plugins_do_action, pc->action);
				}
			}
			else //just 1 action in the plugin, or it's a child
				if (p->module != NULL)	//it's not a child
					e2_menu_add (menu, (gchar *)p->menu_name, (gchar *)p->icon,
						(gchar *)p->description, e2_plugins_do_action, p->action);
		}
	}
	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 (GtkWidget *item, gpointer data)
{
	//command may be slow, get this out of the way
//	gtk_widget_hide (item->parent);
	gchar *cmd = (gchar *) g_object_get_data (G_OBJECT (item), "custom-command");
#ifdef E2_COMMANDQ
	e2_command_run (cmd, E2_COMMAND_RANGE_DEFAULT, FALSE);
#else
	e2_command_run (cmd, E2_COMMAND_RANGE_DEFAULT);
#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;
	GtkWidget *menu_item;
	gchar *icon, *label, *tip, *command;
	do
	{
		gtk_tree_model_get (model, iter, 1, &icon, 2, &label, 3, &tip, 4, &command, -1);
		menu_item = e2_menu_add (menu, label, icon, tip, _e2_menu_custom_cb, NULL);
		if (gtk_tree_model_iter_children (model, &iter2, iter))
		{
			GtkWidget *menu2 = gtk_menu_new ();
			//recurse
			_e2_menu_add_custom_items (model, &iter2, menu2);
			gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu2);
			g_free (command);
		}
		else
			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;
	GtkWidget *menu;
	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))
	{
		menu = gtk_menu_new ();
		_e2_menu_add_custom_items (set->ex.tree.model, &iter2, menu);
	}
	else
		menu = NULL;
	return menu;
}
/**
@brief construct a menu of running child processes, with items which callback to @a func
This func is usable for button-menu and output context menu
We use toggle items (no mnemonics) to indicate which procesess are active
@param func void* callback function for handling menu item selection

@return the menu item widget, or NULL
*/
#define CHILDMENUWIDTH 30
GtkWidget *e2_menu_create_child_menu (E2_ChildMenuType type, gpointer func)
{
	gchar *label, *s;
	gboolean active, empty = TRUE;
	GtkWidget *menu = gtk_menu_new ();
	GtkWidget *item;
	GList *member;
	pthread_mutex_lock (&task_mutex);
	member = app.taskhistory;
	pthread_mutex_unlock (&task_mutex);
	while (member != NULL)
	{
		E2_TaskRuntime *rt = member->data;
		if (type == E2_CHILD_ALL ||
			(!rt->action &&	//for this menu, 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)))
			)
		{
			empty = FALSE;
			//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);

			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);
//				if (func != NULL)
//					g_signal_connect (G_OBJECT (item), "toggled",
//						G_CALLBACK (func), rt);
			}
			else
			{
				item = gtk_menu_item_new_with_label (label);
//				if (func != NULL)
//					g_signal_connect (G_OBJECT (item), "activate",
//						G_CALLBACK (func), rt);
			}
			if (func != NULL)
				g_signal_connect (G_OBJECT (item), "activate",
					G_CALLBACK (func), rt);
			gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
			gtk_widget_show_all (item);

			g_free (label);
		}
		pthread_mutex_lock (&task_mutex);
		member = member->next;
		pthread_mutex_unlock (&task_mutex);
	}
	if (empty)
	{
		if (type == E2_CHILD_OUTPUT)
		{
			//label = _("no matching output");
			gtk_widget_destroy (menu);
			menu = NULL;
		}
		else
		{
			label = _("no children");
			e2_menu_add (menu, label, NULL, NULL, func, NULL);
		}
	}

	if (menu != NULL)
		g_signal_connect (G_OBJECT (menu), "selection-done",
			G_CALLBACK (e2_menu_destroy_cb), NULL);

	return menu;
}
/**
@brief create filters menu for showing in 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_destroy_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 user_data UNUSED data specified when the item was constructed

@return
*/
static void _e2_menu_mount_cb (GtkWidget *item, gpointer user_data)
{
	gboolean newstate = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
//	gint result;
	gchar *elsewhere = NULL;
	gchar *point = g_object_get_data (G_OBJECT (item), "_mountpoint_");
#ifdef E2_HAL
	gboolean remove = e2_fs_mount_is_ejectable (point);	//check this before unmount
#endif
	//CHECKME synchronous umount command to mimimize risk of CWD error
	gchar *cmd = g_strdup_printf ("%s \"%s\"", (newstate) ? "mount" : "|umount", point);	//CHECKME _I(
	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);	//has temporary change CWD
	}
	else
//	result =
#ifdef E2_COMMANDQ
		e2_command_run (cmd, E2_COMMAND_RANGE_DEFAULT, FALSE);
#else
		e2_command_run (cmd, E2_COMMAND_RANGE_DEFAULT);
#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
	{
#ifdef E2_HAL
		if (remove)
		{
			cmd = g_strdup_printf ("eject \"%s\"", point);	//CHECKME _I(
# ifdef E2_COMMANDQ
			e2_command_run_at (cmd, elsewhere, E2_COMMAND_RANGE_DEFAULT, FALSE);
# else
			e2_command_run_at (cmd, elsewhere, E2_COMMAND_RANGE_DEFAULT);
# 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);
		}
	}

/*	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

@return the menu item widget
*/
static GtkWidget *_e2_menu_mount_add_check (GtkWidget *menu,  gchar *label,
	gboolean state, void *func)
{
	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);
	g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (func), NULL);
	gtk_widget_show (check);
	return check;
}
/**
@brief create and pop up a 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;
	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);
		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 (g_str_equal (point, (gchar *) node->data))
			{
				mounted = TRUE;
				break;
			}
		}
		if (!mounted)
		{
			item = _e2_menu_mount_add_check (menu,
				(gchar *) member->data, FALSE, _e2_menu_mount_cb);
			g_object_set_data_full (G_OBJECT (item), "_mountpoint_",
				g_strdup ((gchar *) member->data), g_free);
		}
	}

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

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

	if (g_str_equal (IS_A (from), "GtkButton"))
		gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
			(GtkMenuPositionFunc) e2_toolbar_set_menu_position, from, 1, 0);
	else
		//FIXME
		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_"E2IP".png|","_Mount","|","Mount tip","|","mount command $FTPDIR",NULL),
	g_strconcat ("\t|vfs_off_"E2IP".png|","_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);
}
