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

Copyright (C) 2003-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/actions/e2_action.c
@brief action system

This file contains functions for the action system.
*/
/**
\page actions the action system

ToDo - description of how actions work

\section file_operations operations on selected items

ToDo
*/

#include "emelfm2.h"
#include <string.h>
#include "e2_action.h"
#include "e2_action_option.h"
#include "e2_task.h"
#include "e2_filelist.h"
#include "e2_plugins.h"
#include "e2_filetype.h"
#include "e2_dialog.h"
#include "e2_mkdir_dialog.h"
#include "e2_complete.h"

//static
GHashTable *actions_hash;
//static GPtrArray *actions_array;
GtkTreeStore *actions_store;	//for action-names in config dialog combo renderers

  /*******************/
 /***** actions *****/
/*******************/

/**
@brief process a toggle-button click
This expects as data the key of the relevant toggle data struct in the toggles hash
@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
static gboolean _e2_action_do_toggle (gpointer from, E2_ActionRuntime *art)
{
	gboolean newstate, retval;
	gchar *hash_name = (gchar *) art->data;
	E2_ToggleData *ex = g_hash_table_lookup (toggles_hash, hash_name);
	//check if action name is in the array of "self-managed" toggles
	gint i;
	for (i = 0; i < E2_TOGGLE_COUNT; i++)
	{
		if (!strcmp (toggles_array [i], ex->true_action)
			&& !strcmp (ex->true_action, ex->false_action))
				break;
	}
	if (i < E2_TOGGLE_COUNT)
		//this is a self-mangaged toggle
		newstate = !ex->current_state;
	else
		newstate = e2_toolbar_button_toggle_custom (hash_name);
	//get the operation corresponding to the un-toggled state
	gchar *cc = g_strdup ((newstate) ? ex->false_action : ex->true_action);
	if (e2_action_check (cc) != NULL)
	{
		gchar *gap = e2_utils_find_whitespace (cc);
		if (gap != NULL)
		{
			*gap = '\0';
			gap = e2_utils_pass_whitespace (gap+1);
		}
		retval = e2_action_run_simple_from (cc, gap, from);
	}
	else
	{
		gint res = e2_command_run (cc, E2_COMMAND_RANGE_DEFAULT, from
#ifdef E2_COMMANDQ
		, TRUE
#endif
		);
		retval = (res == 0);
	}
	g_free (cc);
	return retval;
}
/**
@brief process a constructed custom command
This expects data string of the form "<custom command> realcmd [realargs]"
@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if command returned 0
*/
static gboolean _e2_action_custom_command (gpointer from, E2_ActionRuntime *art)
{
	gchar *command = g_strdup ((gchar *)art->data);
	gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT, from
#ifdef E2_COMMANDQ
	, TRUE
#endif
	);
	g_free (command);
	return (res == 0);
}
/**
@brief initiate configuration change

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE
*/
static gboolean _e2_action_configure (gpointer from, E2_ActionRuntime *art)
{  //put this in config dialog file ?
//	gtk_widget_set_sensitive (app.main_window, FALSE);
	gchar *page = (gchar *)art->data;
	e2_config_dialog_create (page);
//	gtk_widget_set_sensitive (app.main_window, TRUE);
	return TRUE;
}
/**
@brief revert all configuration settings to defailt values

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE
*/
static gboolean _e2_action_configure_default (gpointer from, E2_ActionRuntime *art)
{
	//dump old data, don't re-read the config file, recreate screen
	e2_option_refresh (FALSE, TRUE);
	return TRUE;
}
#ifdef E2_VFS
/**
@brief dummy action for plugins which do not really belong on a plugins menu

@param from the button, menu item etc which was activated
@param art action runtime data

@return FALSE always
*/
gboolean e2_action_inaction (gpointer from, E2_ActionRuntime *art)
{
	return FALSE;
}
#endif

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

/**
@brief actions treestore sort-function
This sorts on column 1 of the store
@param model the GtkTreeModel for the store being sorted
@param a a GtkTreeIter in @a model
@param b another GtkTreeIter in @a model
@param userdata UNUSED data specified when the compare func was assigned

@return <0, 0, >0 if a sorts before b, with b, or after b, respectively
*/
static gint _e2_action_tree_compare_cb (GtkTreeModel *model,
	GtkTreeIter *a, GtkTreeIter *b, gpointer userdata)
{
	gint ret;
	gchar *name1, *name2;
	gtk_tree_model_get (model, a, 1, &name1, -1);
	gtk_tree_model_get (model, b, 1, &name2, -1);
	if (name1 == NULL || name2 == NULL)
	{
		if (name1 == NULL && name2 == NULL)
			return 0;
		if (name1 == NULL)
		{
			g_free (name2);
			return -1;
		}
		else
		{
			g_free (name1);
			return 1;
		}
	}
	else
	{
		ret = g_utf8_collate (name1,name2);
		g_free(name1);
		g_free(name2);
	}
	return ret;
}
/**
@brief actions treestore visibility-function
This determines which actions are visible in a filter model for the store,
based on data stored in column 2
@param model the filtermodel for the actions store
@param iter pointer to data for row in @a model to be evaluated
@param data pointerised flags specified when @a model was created

@return TRUE if the row is visible
*/
static gboolean _e2_action_visible_cb (GtkTreeModel *model,
	GtkTreeIter *iter, gpointer data)
{
	gboolean retval;
	E2_ACTION_EXCLUDE ex1 = GPOINTER_TO_INT (data);
	E2_ACTION_EXCLUDE ex2;
	gchar *name;	//, *child;
//	gboolean notfake, col5;
	gtk_tree_model_get (model, iter, 0, &name, //1, &child,
		2, &ex2, // 3, &notfake, 4, &col5,
		-1);
	retval = !(ex1 & ex2 & 0xffff);
	//special cases
	if ((ex1 & E2_ACTION_INCLUDE_FILES) && name != NULL)
		retval = (retval && g_str_has_prefix (name, _A(6)));
	g_free (name);
//	g_free (child);
	return retval;
}

static gboolean _e2_action_enter_menu_cb (GtkWidget *widget, GdkEventCrossing *event,
		E2HoverData *data)
{
	if (data->timer_id > 0)
	{
		g_source_remove (data->timer_id);
		data->timer_id = 0;
	}
	return FALSE;
}

/**
@brief timer callback to implement a hover callback
@brief data pointer to hover data
@return FALSE to cancel the timer
*/
static gboolean _e2_action_do_hover_cb (E2HoverData *data)
{
	//get rid of this ASAP in case of pending departure from hover-widget
	if (data->timer_id > 0)
	{
		g_source_remove (data->timer_id);
		data->timer_id = 0;
	}

	gdk_threads_enter ();
	(*data->callback) (data->hovered, data);

	if (GTK_IS_MENU (data->popup) &&
#ifdef USE_GTK2_18
		!gtk_widget_get_visible (data->popup)
#else
		!GTK_WIDGET_VISIBLE (data->popup)
#endif
		)
	{
		GtkWidget *popwin = gtk_widget_get_ancestor (data->popup, GTK_TYPE_WINDOW); //would be parent, actually
		gtk_window_set_transient_for (GTK_WINDOW (popwin), GTK_WINDOW (app.main_window));
		g_signal_connect (G_OBJECT (popwin), "enter-notify-event",
			G_CALLBACK (_e2_action_enter_menu_cb), data);
		g_signal_connect (G_OBJECT (popwin), "leave-notify-event",
		G_CALLBACK (e2_action_end_hover_cb), data);

		gtk_widget_show (data->popup); //must do this here, not later - dunno why

		GtkRequisition requisition;
		gtk_widget_size_request (data->popup, &requisition);
		gint x, y;
		gboolean push = TRUE;
		e2_toolbar_set_menu_position (GTK_MENU (data->popup), &x, &y, &push, data->hovered);
		gtk_widget_size_request (popwin, &requisition);
		gtk_widget_set_size_request (popwin, requisition.width, requisition.height);
		gtk_window_move (GTK_WINDOW (popwin), x, y); //this might be ignored by WM
		gtk_widget_show_all (popwin); //doesn't work, dunno why
		gtk_window_move (GTK_WINDOW (popwin), x, y); //fallback
	}
	gdk_threads_leave ();

	return FALSE;
}
/**
@brief timer callback to cleanup after a hover
@brief data pointer to hover data
@return FALSE to cancel the timer
*/
static gboolean _e2_action_clear_hover_cb (E2HoverData *data)
{
	if (GTK_IS_WIDGET (data->popup))
	{
		gdk_threads_enter ();
		gtk_widget_destroy (data->popup);
		gdk_threads_leave ();
	}
	data->popup = NULL;

	return FALSE;
}

static void _e2_action_abort_hover_cb (GtkButton *button, E2HoverData *data)
{
	printd (DEBUG, "in function _e2_action_abort_hover_cb");
	if (data->timer_id > 0)
	{
		g_source_remove (data->timer_id);
		data->timer_id = 0;
	}
	if (GTK_IS_WIDGET (data->popup))
		gtk_widget_destroy (data->popup);
	data->popup = NULL;
}

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

/**
@brief setup for a potential hover process
@param widget the entered widget (for now at least, only a toolbar button)
@param event pointer to event data
@para data pointer to hover data struct

This is a "enter-notify-event" signal callback for all toolbar buttons based on
a E2_ACTION_TYPE_HOVER action.

@return	FALSE always so the event is propagated
*/
gboolean e2_action_start_hover_cb (GtkWidget *widget, GdkEventCrossing *event,
		E2HoverData *data)
{
	printd (DEBUG, "in function e2_action_start_hover_cb");
	if (data->timer_id > 0)
	{
		//we're re-entering the buttton from a menu
		g_source_remove (data->timer_id);
		data->timer_id = 0;
	}
	if (data->popup == NULL)
	{
		//no popup if button is clicked
		g_signal_connect (G_OBJECT (widget), "clicked", //not "button_press-event", that's blocked
			G_CALLBACK (_e2_action_abort_hover_cb), data);
		//FIXME relate interval to gtk's tooltip popup time
		data->timer_id = g_timeout_add (1000, (GSourceFunc)_e2_action_do_hover_cb, data);
	}

	return FALSE;
}
/**
@brief abort an actual or potential hover process
@param widget the departed widget
@param event pointer to event data
@para data pointer to hover data struct

This is a "leave-notify-event" signal callback for all toolbar buttons based on
a E2_ACTION_TYPE_HOVER action, and for a popped-up menu widget.

@return	FALSE always so the event is propagated
*/
gboolean e2_action_end_hover_cb (GtkWidget *widget, GdkEventCrossing *event,
		E2HoverData *data)
{
	printd (DEBUG, "in function e2_action_end_hover_cb");
	if (data->timer_id > 0)
	{
		g_source_remove (data->timer_id);
		data->timer_id = 0;
	}
	if (GTK_IS_WIDGET (data->popup))
		//allow short interval to move pointer from button into the menu or vice-versa, before killing it
		data->timer_id = g_timeout_add (100, (GSourceFunc)_e2_action_clear_hover_cb, data);
	else
		data->popup = NULL;

	g_signal_handlers_disconnect_by_func ((gpointer)widget, _e2_action_abort_hover_cb, data);

	return FALSE;
}

/**
@brief option helper for action columns

This function is used to source a model for config-dialog cell renderers
in E2_OPTION_TREE_TYPE_SEL columns of tree-options

@param ex pointerised flags which specify categories of actions
 *		that should not be visible

@return GtkTreeFilterModel of all actions
*/
GtkTreeModel *e2_action_filter_store (gpointer ex)
{
	GtkTreeModel *filter = gtk_tree_model_filter_new (
		GTK_TREE_MODEL (actions_store), NULL);
	gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
		(GtkTreeModelFilterVisibleFunc) _e2_action_visible_cb, ex, NULL);
	return filter;
}

gint entry_level = 0;

/**
@brief register 'public' action names, and corresponding parameters, for later use as needed.
It replaces any previously-registered action with the same name.
Also adds the action name etc to the options store, as a convenience for the option system
Action names need to be freed (= action removed from the hash) when the session ends, if not before.
Potentially re-entrant.
As another convenience, the non-constant name string used to register the action is freed at exit.
@param newaction pointer to action data to be copied and heaped

@return registered action E2_Action
*/
E2_Action *e2_action_register (const E2_Action *newaction)
{
	//unregister any previously registered actions with that name
	//(for convenience)

	entry_level++; //keep note of whether re-entrant

	//now allocate the action structure and initialize it from the
	//function parameters
#ifdef USE_GLIB2_10
	E2_Action *action = (E2_Action *) g_slice_alloc (sizeof (E2_Action));
//	E2_Action *action = ALLOCATE (E2_Action);
#else
	E2_Action *action = ALLOCATE (E2_Action);
#endif
	CHECKALLOCATEDFATAL (action);
	//action name is freed if the action is removed from the actions_hash table
	//because the names are used as keys
	*action = *newaction;
	//add the action to the static hash table and pointer array register
	g_hash_table_replace (actions_hash, action->name, action);
//	g_ptr_array_add (actions_array, action);

	//to get structured dropdown lists, find and register the 'parent' action, if any
	gboolean registered_parent = FALSE;
	E2_Action *parent = NULL;
	gchar *search = g_strdup (newaction->name);
	gchar *child = strrchr (search, '.');	//always ascii '.', don't need g_utf8_strrchr()
	if (child != NULL)
	{ //also register a new parent, if need be
		*child = '\0';
		parent = e2_action_get (search);
		if (parent == NULL)
		{
			E2_Action found =
			{search, NULL, FALSE, E2_ACTION_TYPE_DUMMY, 0, NULL, NULL};
			//re-entry !!
			parent = e2_action_register (&found);
			registered_parent = TRUE;
		}
	}

	//now add the action to the treestore
	GtkTreeIter iter;
	GtkTreeIter iter_parent;
	GtkTreeIter *parentptr;
	//setup store
	//(sortable, with filter-model for selection-type cell renderers in config dialog trees)
	if (parent == NULL)
		parentptr = NULL;
	else
	{
		parentptr = &iter_parent;
		if (!e2_tree_ref_to_iter (actions_store, parent->ref, parentptr))
			parentptr = NULL;
	}
	//CHECKME only columns 0 and 2 are used ?
#ifdef USE_GTK2_10
	gtk_tree_store_insert_with_values (actions_store, &iter, parentptr, -1,
#else
	gtk_tree_store_append (actions_store, &iter, parentptr);
	gtk_tree_store_set (actions_store, &iter,
#endif
		0, action->name, 1, (child == NULL) ? action->name : g_strstrip (child + 1),
		2, action->exclude, 3, action->type != E2_ACTION_TYPE_DUMMY, 4, TRUE, -1);
	//keep reference around to be able to to find out the parent iter
	//for possible children later on
	action->ref = e2_tree_iter_to_ref (actions_store, &iter);

	//cleanup
	if (!registered_parent)
		g_free (search);  //registered actions are kept, but this one was not needed
//	else if (child != NULL)
//		*child = '.';	//what about ':' ?
//	if (--entry_level == 0)  //revert re-entrant level, for next time
//		g_free (name);  //and this is not re-entrant, so free the original name-space
	entry_level--;
	return action;
}
/**
@brief unregister action named @a name
This is for plugin actions, essentially
@param name action name string

@return TRUE if the action was registered
*/
gboolean e2_action_unregister (gchar *name)
{
	E2_Action *action = g_hash_table_lookup (actions_hash, name);
	if (action == NULL)
		return FALSE;

	GtkTreeIter iter, parent;
	if (e2_tree_ref_to_iter (actions_store, action->ref, &iter)
		&& gtk_tree_model_iter_parent (GTK_TREE_MODEL (actions_store),
			&parent, &iter)
		&& gtk_tree_model_iter_n_children (GTK_TREE_MODEL (actions_store),
			&parent) == 1)
		gtk_tree_store_remove (actions_store, &parent);

	g_hash_table_remove (actions_hash, name);
	return TRUE;
}
/**
@brief find an action by name

The action with the name @a name is looked up in the internal hash table
and a pointer to the action object is returned. The name should be the
complete name of the action including any parent categories. If the action
cannot be found, NULL is returned.

@param name the name of the action

@return pointer to an action object or NULL
*/
E2_Action *e2_action_get (gchar *name)
{
	return (g_hash_table_lookup (actions_hash, name));
}
/**
@brief find an action by name, reverting to custom if not a registered action

@param taskname action or external command string
@param arg the argument to be supplied to the action or command, or NULL
@param use_arg pointer to store newly-allocated "real" argument string
@return pointer to an action object, or NULL if @a taskname is NULL
*/
E2_Action *e2_action_get_with_custom (gchar *taskname, gchar *arg, gchar **use_arg)
{
	if (taskname == NULL)
		return NULL;
	E2_Action *action = e2_action_get (taskname);
	if (action == NULL)
	{	//fallback to custom command
		action = e2_action_get (_A(20));
		if (arg == NULL || *arg == '\0')
			*use_arg = g_strdup (taskname);
		else
			*use_arg = g_strconcat (taskname, " ", arg, NULL);
	}
	else
	{
		if (arg == NULL)
			*use_arg = g_strdup ("");
		else
			*use_arg = g_strdup (arg);
	}
	return action;
}
/**
@brief check whether @a command is an action

@param command the command string

@return pointer to the action, or NULL if @a command not an action
*/
E2_Action* e2_action_check (gchar *command)
{
	if (command == NULL)	//this should never happen, but ...
		return NULL;
//#warning ignore compiler warning about unitialized usage of c
	gchar c;
	gchar *gap = e2_utils_find_whitespace (command);
	if (gap != NULL)
	{
		c = *gap;
		*gap = '\0';
	}
	E2_Action *action = e2_action_get (command);
	if (gap != NULL)
		*gap = c;
	if (action == NULL)
		return NULL;
	if (action->type == E2_ACTION_TYPE_DUMMY)
		return NULL;
	return action;
}
/**
@brief run a named 'task' (may be a command or an action)

This function is to run a certain action. First, the action specified by
@a name is looked up. Then a runtime object is created and the action is
run. Afterwards the runtime object is freed again. If the action could not
be found, an error message is printed to the output pane.

@param name action name or command, string
@param arg pointer to data for the command, or possibly arguments or more of the command, or NULL
@param from the widget activated to initiate the action (best if non-NULL)

@return  TRUE on success, FALSE on failure
*/
gboolean e2_action_run_simple_from (gchar *name, gpointer arg, gpointer from)
{
	//CHECKME should these variables be volatilized ?
	if ((name == NULL || *name == '\0')	//chained keybindings may have no command
		&& (arg == NULL || *(gchar *)arg == '\0'))
		return FALSE;
	gchar *real_arg;
	E2_Action *action = e2_action_get_with_custom (name, arg, &real_arg);
	E2_ActionRuntime *rt = e2_action_pack_runtime (action, real_arg, g_free);
	gboolean retval = e2_action_run (from, rt);
	e2_action_free_runtime (rt);
	return retval;
}
/* *
@brief callback to run an action

This function wraps the real action run function, with thread unlocking
so that any such locking can be managed locally, without mutux reentrance.

@param from the item (GtkButton GtkMenuItem etc) activated to trigger the action
@param rt runtime data for the action

@return
*/
/*void e2_action_run_cb (gpointer from, E2_ActionRuntime *rt)
{
	gdk_threads_leave ();
	e2_action_run (from, rt);
	gdk_threads_enter ();
}*/
/**
@brief run a certain action

This function is the real action run function.
It may be used as callback for gtk signals, if downstream thread [un]locks
are managed.
There are several wrappers to simplify access.
Runtime data @a rt may contain additional user data.
@a from may be used by the action handler to determine the context.

@param from the item activated to trigger the action (eg toolbar GtkButton, maybe NULL)
@param rt runtime data for the action

@return TRUE on success of the action, FALSE on failure
*/
gboolean e2_action_run (gpointer from, E2_ActionRuntime *rt)
{
	//some quick checks
	if (rt == NULL || rt->action == NULL || rt->action->func == NULL)
	{
		printd (DEBUG, "cannot run action, it doesn't exist");
		return FALSE;
	}
	printd (DEBUG, "e2_action_run (from:, rt:%s)", rt->action->name);
	rt->state = e2_action_get_current_state ();
	if (from == NULL)
		from = app.main_window;
	gboolean (*func) (gpointer, E2_ActionRuntime *) = rt->action->func;
#ifndef DEBUG_MESSAGES
	return func (from, rt);
#else
	gboolean res = func (from, rt);
	printd (DEBUG,"e2_action_run () ends");
	return res;
#endif
}
/**
@brief create an action runtime object

This function creates an action runtime object and initializes it with
supplied user data.

@param action the action to create the runtime object for, or command string for a "custom command"
@param data action user data
@param data_free function to free data when the runtime object is freed

@return newly allocated action runtime object that should be freed
*/
E2_ActionRuntime *e2_action_pack_runtime (E2_Action *action, gpointer data,
	void(*data_free)(gpointer))
{
//	printd (DEBUG, "action pack runtime for %s", action->name);
	E2_ActionRuntime *rt = ALLOCATE (E2_ActionRuntime);
	CHECKALLOCATEDFATAL (rt);
	rt->action = action;
	rt->data = data;
	rt->data_free = (data == NULL) ? NULL : data_free;
	rt->state = 0;	//usually reset when action is run
	return rt;
}
/**
@brief free action runtime object

The runtime data is freed if a data free function has been supplied.
In addition the runtime object is freed and must not be used anymore after
calling this function.

@param rt the action runtime object to free

@return
*/
void e2_action_free_runtime (E2_ActionRuntime *rt)
{
//	printd (DEBUG, "e2_action_free_runtime");
	if (rt != NULL)
	{
		if (rt->data != NULL && rt->data_free != NULL)
			(*rt->data_free) (rt->data);

		DEALLOCATE (E2_ActionRuntime, rt);
	}
}
/**
@brief determine state flags for an action about to run

@return tne state flags
*/
E2_ActionState e2_action_get_current_state (void)
{
	E2_ActionState state = 0;
	guint btn;
	GdkEvent *event = gtk_get_current_event ();
	if (event != NULL)
	{
		if (!gdk_event_get_state (event, &state))
			state = e2_utils_get_modifiers ();

		switch (event->type)
		{
			case GDK_BUTTON_PRESS:
  			case GDK_2BUTTON_PRESS:
  			case GDK_3BUTTON_PRESS:
  			case GDK_BUTTON_RELEASE:	//normal for a "clicked" callback
				btn = ((GdkEventButton *)event)->button;
				break;
			case GDK_KEY_PRESS:
			case GDK_KEY_RELEASE:
				btn = 0;
			default:	//CHECKME dnd
				btn = 127;
				break;
		}
		gdk_event_free (event);
	}
	else
	{
		state = e2_utils_get_modifiers ();
		btn = 0;
	}
	ACTION_SETSTATE (state, btn);
	return state;
}
/**
@brief clear data for an entry in the toggles hash

@param ex pointer to hash table data item

@return
*/
static void _e2_action_free_toggle (E2_ToggleData *ex)
{
	e2_list_free_with_data (&ex->boxes);
	if (ex->true_action != NULL)
		g_free (ex->true_action);
	if (ex->false_action != NULL)
		g_free (ex->false_action);
	DEALLOCATE (E2_ToggleData, ex);
}
/**
@brief internal action system memory cleanup

@return
*/
static void _e2_action_clean1 (E2_Action *action)
{
	//action->name is the actions-hash key, therefore cleared by g_free
	if (G_UNLIKELY(action->type == E2_ACTION_TYPE_HOVER))
		DEMALLOCATE (E2_Callback, action->data);
//	if (action->data != NULL && action->cleaner != (GDestroyNotify)NULL)
//		(*action->cleaner) (data);
	GtkTreeIter iter;
	if (e2_tree_ref_to_iter (actions_store, action->ref, &iter))
		gtk_tree_store_remove (actions_store, &iter);
	gtk_tree_row_reference_free (action->ref);
#ifdef USE_GLIB2_10
	g_slice_free1 (sizeof (E2_Action), action);
//	DEALLOCATE (E2_Action, action);
#else
	DEALLOCATE (E2_Action, action);
#endif
}
/* presently unused
static void free_name (gchar *key, E2_Action *value, gpointer data)
{
	g_free (key); //=value->name
	return;
} */

/**
@brief internal action system cleanup

After calling the function, the action system may not be used anymore.
It is only called internally just before exit.

@return
*/
/* this done only at session end, don't bother
void e2_actions_clean ()
{
//CHECKME = should this be able to handle ->data &| ->data2 ?
	g_object_unref (actions_store);
	//clean up non-constant action name strings in the hash
	g_hash_table_foreach (actions_hash, (GFunc) free_name, NULL);
	g_hash_table_destroy (actions_hash);
	//CHECKME = array cleanup ok for non-constant strings? could this replace the hash foreach?
//	g_ptr_array_free (actions_array, TRUE);
} */

/**
@brief internal action system initialization

This function is only called once at startup to initialize the action
system. Afterwards, it has no effect.
It also registers most actions (and "pseudo-actions" for toolbars). Some
other actions are registered in the pane create func.

@return
*/
void e2_actions_init (void)
{
	//ensure that this function is only performed once
	RUN_ONCE_CHECK ();

	//initialize local data structures that are used to keep track of
	//the action parameters
	//note - this needs to be done before any plugin is loaded
	actions_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
		(GDestroyNotify) _e2_action_clean1);
//	actions_array = g_ptr_array_new ();
	actions_store = gtk_tree_store_new (5, G_TYPE_STRING, G_TYPE_STRING,
		G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
	//CHECKME is this still necessary?
//	g_object_ref (actions_store);

	toggles_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
		(GDestroyNotify) _e2_action_free_toggle);
	toggles_array [E2_TOGGLE_PANE1FULL] = g_strconcat (_A(11),".",_A(113),NULL);
	toggles_array [E2_TOGGLE_PANE2FULL] = g_strconcat (_A(12),".",_A(113),NULL);
	toggles_array [E2_TOGGLE_PANE1HIDDEN] = g_strconcat (_A(11),".",_A(84),NULL);
	toggles_array [E2_TOGGLE_PANE2HIDDEN] = g_strconcat (_A(12),".",_A(84),NULL);
	toggles_array [E2_TOGGLE_PANE1FILTERS] = g_strconcat (_A(11),".",_A(25),NULL);
	toggles_array [E2_TOGGLE_PANE2FILTERS] = g_strconcat (_A(12),".",_A(25),NULL);
	toggles_array [E2_TOGGLE_OUTPUTFULL] = g_strconcat (_A(10),".",_A(113),NULL);
	toggles_array [E2_TOGGLE_OUTPUTSHADE] = g_strconcat (_A(10),".",_A(83),NULL);
#ifdef E2_VFS
	toggles_array [E2_TOGGLE_PANE1SPACE] = g_strconcat (_A(11),".",_A(120),NULL);
	toggles_array [E2_TOGGLE_PANE2SPACE] = g_strconcat (_A(12),".",_A(120),NULL);
#endif
	//register most actions
	//note translated action labels needed here, i.e. before config loaded
	//all action names must be freeable
	E2_Action homeless_actions[] =
	{	//here we don't bother explicitly setting 3 trailing NULL parameters
		{ g_strconcat(_A(6),".",_A(23),NULL), (gboolean (*)(gpointer,E2_ActionRuntime*))NULL, FALSE, E2_ACTION_TYPE_FILE_ACTIONS,
			E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_TOOLBAR | E2_ACTION_EXCLUDE_LAYOUT, },
		{ g_strconcat(_A(16),".",_A(27),NULL), (gboolean (*)(gpointer,E2_ActionRuntime*))NULL, FALSE, E2_ACTION_TYPE_PLUGINS,
			E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_TOOLBAR, },
		{ g_strdup(_A(21)), (gboolean (*)(gpointer,E2_ActionRuntime*))NULL, FALSE, E2_ACTION_TYPE_SEPARATOR,
			E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_LAYOUT, },
		{ g_strdup(_A(22)), (gboolean (*)(gpointer,E2_ActionRuntime*))NULL, FALSE, E2_ACTION_TYPE_SUBMENU,
			E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_LAYOUT, },
		{ g_strconcat(_A(1),".",_A(72),NULL), e2_main_user_shutdown, E2_ACTION_TYPE_ITEM, FALSE, 0, },

		{ g_strconcat(_A(3),".",_C(34),NULL), e2_plugins_configure, FALSE, E2_ACTION_TYPE_ITEM, 0, },
		{ g_strdup(_A(20)), _e2_action_custom_command, TRUE, E2_ACTION_TYPE_ITEM,
			E2_ACTION_EXCLUDE_GENERAL, },
		{ g_strconcat(_A(17),".",_A(115),NULL), _e2_action_do_toggle, TRUE, E2_ACTION_TYPE_TOGGLE,
			E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_TOGGLE, },
		{ g_strconcat(_A(17),".",_A(116),NULL), _e2_action_do_toggle, TRUE, E2_ACTION_TYPE_TOGGLE,
			E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_TOGGLE, },

		{ g_strconcat(_A(3),".",_A(32),NULL), _e2_action_configure, FALSE, E2_ACTION_TYPE_ITEM, 0, },
		{ g_strconcat(_A(3),".",_A(40),NULL), _e2_action_configure_default, FALSE, E2_ACTION_TYPE_ITEM, 0, },
	};

	guint i, count = sizeof (homeless_actions) / sizeof (E2_Action);
	for (i = 0; i < count; i++)
		e2_action_register (&homeless_actions[i]);
	//register most other actions
	// (some others are in pane create fn)
	e2_task_actions_register ();
	e2_output_actions_register();
//	e2_context_menu_actions_register();
	e2_bookmark_actions_register ();
	e2_filetype_actions_register ();
	e2_command_line_actions_register ();
	e2_command_actions_register ();
	e2_window_actions_register ();
	e2_pane_actions_register ();
//	e2_toolbar_actions_register ();
	e2_about_dialog_actions_register ();
	e2_edit_dialog_actions_register ();
	e2_view_dialog_actions_register ();
//	e2_search_dialog_actions_register ();
	e2_mkdir_dialog_actions_register ();
#ifdef WITH_KEYFAKE
	e2_keybinding_actions_register ();
#endif
#ifdef WITH_BUTTONFAKE
	e2_mousebinding_actions_register ();
#endif
	e2_action_option_actions_register ();
#ifdef E2_FS_MOUNTABLE
	e2_fs_mount_actions_register ();
#endif
	//sort the actions tree store
	GtkTreeSortable *sortable = GTK_TREE_SORTABLE (actions_store);
	gtk_tree_sortable_set_sort_func (sortable, 0, _e2_action_tree_compare_cb,
		NULL, NULL);
	gtk_tree_sortable_set_sort_column_id (sortable, 0, GTK_SORT_ASCENDING);
}
/**
@brief setup array of translated action labels

No spaces in names, so action arguments can be separated by a space.
Some of these are the same as config dialog labels.
Array size defined in e2_action.h

@return
*/
void e2_action_setup_labels (void)
{   //CHECKME= which of these names can be rationalised ?
	//'parent' names
	action_labels[0] = _("bookmark");
	action_labels[1] = _("command");
	action_labels[2] = _("children");
	action_labels[3] = _("configure");
	action_labels[4] = _("dialog");
	action_labels[5] = _("dirline");
	action_labels[6] = _("file");
	action_labels[7] = _("filelist");
	action_labels[8] = _("history");
	action_labels[9] = _("option");
	action_labels[10] = _("output");  // = _C(28)
	action_labels[11] = _("pane1");  // ~ _C(29)
	action_labels[12] = _("pane2");  // ~ _C(31)
	action_labels[13] = _("pane");
	action_labels[14] = _("panes");  //= _C(33)
	action_labels[15] = _("pending");
	action_labels[16] = _("plugin");  //this is also used in menus - see #define PLUGIN in emelfm2.h
	action_labels[17] = _("toggle");
	action_labels[18] = _("trash");
	action_labels[19] = _("separator");  //not really an action, but not a config either
	action_labels[20] = _("<custom command>");
	action_labels[21] = _("<separator>");
	action_labels[22] = _("<submenu>");
	//'child' names
	action_labels[23] = _("<actions>");
	action_labels[24] = _("<bookmarks>");
	action_labels[25] = _("<filters>");
	action_labels[26] = _("<line>");
	action_labels[27] = _("<menu>");
	action_labels[28] = _("about");
	action_labels[29] = _("activate");
	action_labels[30] = _("add");
	action_labels[31] = _("adjust_ratio");
	action_labels[32] = _("application");
	action_labels[33] = _("clear");
	action_labels[34] = _("clear_history");
	action_labels[35] = _("complete");
	action_labels[36] = _("copy");
	action_labels[37] = _("copy_as");
	action_labels[38] = _("copy_merge");
	action_labels[39] = _("copy_with_time");
	action_labels[40] = _("default");
	action_labels[42] = _("delete");
	action_labels[43] = _("edit");
	action_labels[44] = _("edit_again");
	action_labels[45] = _("filetype");
	action_labels[46] = _("find");
	action_labels[47] = _("focus");
	action_labels[48] = _("fullscreen");
	action_labels[49] = _("go_back");
	action_labels[50] = _("go_forward");
	action_labels[51] = _("go_up");
	action_labels[52] = _("goto_bottom");
	action_labels[53] = _("goto_top");
	action_labels[54] = _("help");
	action_labels[55] = _("info");
	action_labels[56] = _("insert_selection");
	action_labels[57] = _("invert_selection");
	action_labels[58] = _("list");
	action_labels[59] = _("mirror");
	action_labels[60] = _("mkdir");
	action_labels[61] = _("mountpoints");
	action_labels[62] = _("move");
	action_labels[63] = _("move_as");
	action_labels[64] = _("open");
	action_labels[65] = _("open_in_other");
	action_labels[66] = _("open_with");
	action_labels[67] = _("owners");
	action_labels[68] = _("page_down");
	action_labels[69] = _("page_up");
	action_labels[70] = _("permissions");
	action_labels[71] = _("print");
	action_labels[72] = _("quit");
	action_labels[73] = _("refresh");
	action_labels[74] = _("refreshresume");
	action_labels[75] = _("refreshsuspend");
	action_labels[76] = _("rename");
	action_labels[77] = _("scroll_down");
	action_labels[78] = _("scroll_up");
	action_labels[79] = _("search");  //= _C(37)
	action_labels[80] = _("select_type");
	action_labels[81] = _("send");
	action_labels[82] = _("set");
	action_labels[83] = _("show");
	action_labels[84] = _("show_hidden");
	action_labels[85] = _("show_menu");
	action_labels[86] = _("sortaccesssed");
	action_labels[87] = _("sortchanged");
	action_labels[88] = _("sortext");
	action_labels[89] = _("sortgroup");
	action_labels[90] = _("sortmodified");
	action_labels[91] = _("sortname");
	action_labels[92] = _("sortpermission");
	action_labels[93] = _("sortsize");
	action_labels[94] = _("sortuser");
	action_labels[95] = _("switch");
	action_labels[96] = _("symlink");
	action_labels[97] = _("symlink_as");
	action_labels[98] = _("sync");
	action_labels[99] = _("toggle_direction");
	action_labels[100] = _("toggle_focus");
	action_labels[101] = _("toggle_select_all");
	action_labels[102] = _("toggle_selected");
#ifdef E2_TREEDIALOG
	action_labels[103] = _("tree");
#endif
	action_labels[104] = _("unpack");
#ifdef WITH_UNTRASH
	action_labels[105] = _("untrash");
#endif
	action_labels[106] = _("view");  //= _C(42)
	action_labels[107] = _("view_again");
	action_labels[108] = _("view_at");
	//these are action _parameter_ strings
	action_labels[109] = _("child");
	action_labels[110] = _("ctrl");
	action_labels[111] = _("dirs");
	action_labels[112] = _("escape");
	action_labels[113] = _("expand");
	action_labels[114] = _("files");
	action_labels[115] = _("off");
	action_labels[116] = _("on");
	action_labels[117] = _("quote");
	action_labels[118] = _("shift");
	action_labels[119] = _("top");
	//late entries, out of order
	//maybe more space here, see array definition in header file
#ifdef E2_VFS
	action_labels[120] = _("<spacemenu>");
	action_labels[121] = _("dummy");
	action_labels[122] = _("namespace");
	action_labels[123] = _("unpack_in_other");
#endif
#ifdef WITH_KEYFAKE
	action_labels[124] = _("key");
#endif
#if defined(WITH_KEYFAKE) || defined(WITH_BUTTONFAKE)
	action_labels[125] = _("fake");
#endif
#ifdef WITH_BUTTONFAKE
	action_labels[126] = _("button");
#endif
}
#define LAST_NAME_INDEX 126
#if LAST_NAME_INDEX > ALLOCATED_NAMES - 1
# warning must increase size of action names array
#endif
#undef LAST_NAME_INDEX

/*
//copy all actions, for help document
void build_list (gpointer key, gpointer value, GString *text)
{
	E2_Action *action = (E2_Action *) value;
	if (action->type != E2_ACTION_TYPE_DUMMY)
	{
		text = g_string_append (text, action->name);
		text = g_string_append_c (text, '\t');
		text = g_string_append_c (text, (action->has_arg) ? 'Y' : 'N');
		text = g_string_append_c (text, '\n');
	}
	return;
}
void e2_action_list_all (void)
{
	GString *cmds_list = g_string_new ("");
	g_hash_table_foreach (actions_hash, (GHFunc) build_list, cmds_list);

	GtkClipboard* clipper = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
	gtk_clipboard_set_text (clipper, cmds_list->str, cmds_list->len);
	ls e2*

	g_string_free (cmds_list, TRUE);
} */
