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

Copyright (C) 2003-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/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_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 (g_str_equal (toggles_array [i], ex->true_action)
			&& g_str_equal (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
	{
#ifdef E2_COMMANDQ
		gint res = e2_command_run (cc, E2_COMMAND_RANGE_DEFAULT, TRUE);
#else
		gint res = e2_command_run (cc, E2_COMMAND_RANGE_DEFAULT);
#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);
#ifdef E2_COMMANDQ
	gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT, TRUE);
#else
	gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT);
#endif
	g_free (command);
	return (res == 0);
}
#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(5)));
	g_free (name);
//	g_free (child);
	return retval;
}

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

/**
@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;
}
/** @brief short-form register action function

See e2_action_register function description. This one has no exclude flags or data2

@return registered action E2_Action
*/
E2_Action *e2_action_register_simple (gchar *name, E2_ACTION_TYPE type,
	void *func, gpointer data, gboolean has_arg)
{
	return e2_action_register (name, type, func, data, has_arg, 0, NULL);
}

gint entry_level = 0;

/**
@brief register action names

This function registers '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.
@a has_arg is stored here, but never used elsehwhere !
As another convenience, the non-constant name string used to register the action is freed at exit.

@param name freeable string with possible '.' separating 'parent/ and 'child' parts of the action
@param type flags for one of: item (?) submenu, file_actions, custom_command, plugins, separator,
	tear_off_menu, toggle, command_line, bookmarks, dummy
@param func the function to be called when the action is run
@param data pointer to data to be supplied to @a func, or NULL
@param has_arg boolean flag signalling whether the action-string uses context-specific argument(s)
@param exclude flags indicating whether to exclude this action from toolbars, menus &| keybindings
@param data2 additional data needed by the action, or NULL

@return registered action E2_Action
*/
E2_Action *e2_action_register (gchar *name, E2_ACTION_TYPE type,
	void *func, gpointer data, gboolean has_arg, E2_ACTION_EXCLUDE exclude,
	gpointer data2)
{
	//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);
	//freed if the action is removed from the actions_hash table
	//because the names are used as keys
	action->name = name;
	action->type = type;
	action->func = func;
	action->has_arg = has_arg;
	action->data = data;
	action->exclude = exclude;
	action->data2 = data2;
	//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 (name);
	gchar *child = strrchr (search, '.');	//always ascii '.', don't need g_utf8_strrchr()
//	if (child == NULL)
//		child = strrchr (search, ':'); //ditto
	if (child != NULL)
	{ //also register a new parent, if need be
		*child = '\0';
		parent = e2_action_get (search);
		if (parent == NULL)
		{
			//re-entry !!
			parent = e2_action_register_simple (search, E2_ACTION_TYPE_DUMMY,
				NULL, NULL, FALSE);
			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(17));
		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 certain action by name

This function is to run a certain action and wraps around
e2_action_run_simple_from(). It sets the @a from widget
of e2_action_run_simple_from to NULL.

@param name the name of the action to run
@param arg additional user data, may be NULL

@return  TRUE on success, FALSE on failure
*/
gboolean e2_action_run_simple (gchar *name, gpointer arg)
{
	return e2_action_run_simple_from (name, arg, NULL);
}

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

@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);
	gboolean (*fun) (gpointer, E2_ActionRuntime *) = rt->action->func;
#ifndef DEBUG_MESSAGES
	return fun (from, rt);
#else
	gboolean res = fun (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,
	gpointer data_free)
{
//	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;
	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)
		return;

	if (rt->data_free != NULL)
	{
		void (*fun) (gpointer) = rt->data_free;
		fun (rt->data);
	}
	DEALLOCATE (E2_ActionRuntime, rt);
}
/**
@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 hash key, cleared by g_free
	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(108),NULL);
	toggles_array [E2_TOGGLE_PANE2FULL] = g_strconcat (_A(12),".",_A(108),NULL);
	toggles_array [E2_TOGGLE_PANE1HIDDEN] = g_strconcat (_A(11),".",_A(80),NULL);
	toggles_array [E2_TOGGLE_PANE2HIDDEN] = g_strconcat (_A(12),".",_A(80),NULL);
	toggles_array [E2_TOGGLE_PANE1FILTERS] = g_strconcat (_A(11),".",_A(22),NULL);
	toggles_array [E2_TOGGLE_PANE2FILTERS] = g_strconcat (_A(12),".",_A(22),NULL);
	toggles_array [E2_TOGGLE_OUTPUTFULL] = g_strconcat (_A(9),".",_A(108),NULL);
	toggles_array [E2_TOGGLE_OUTPUTSHADE] = g_strconcat (_A(9),".",_A(79),NULL);
#ifdef E2_VFS
	toggles_array [E2_TOGGLE_PANE1SPACE] = g_strconcat (_A(11),".",_A(115),NULL);
	toggles_array [E2_TOGGLE_PANE2SPACE] = g_strconcat (_A(12),".",_A(115),NULL);
#endif
	//register most actions
	//note translated action labels needed here, i.e. before config loaded
	E2_Action homeless_actions[] =
	{	//here we don't bother explicitly setting 3 trailing NULL parameters
		{ NULL, NULL, FALSE, E2_ACTION_TYPE_FILE_ACTIONS,
			E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_TOOLBAR | E2_ACTION_EXCLUDE_LAYOUT },
		{ NULL, NULL, FALSE, E2_ACTION_TYPE_PLUGINS,
			E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_TOOLBAR },
		{ NULL, NULL, FALSE, E2_ACTION_TYPE_SEPARATOR,
			E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_LAYOUT },
		{ NULL, NULL, FALSE, E2_ACTION_TYPE_SUBMENU,
			E2_ACTION_EXCLUDE_ACCEL | E2_ACTION_EXCLUDE_LAYOUT },
		{ NULL, e2_main_user_shutdown, E2_ACTION_TYPE_ITEM, FALSE, 0 },

		{ NULL, e2_task_configure, TRUE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_configure_default, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_plugins_configure, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, _e2_action_custom_command, TRUE, E2_ACTION_TYPE_ITEM,
			E2_ACTION_EXCLUDE_GENERAL },
		{ NULL, _e2_action_do_toggle, TRUE, E2_ACTION_TYPE_TOGGLE,
			E2_ACTION_EXCLUDE_TOGGLE },
		{ NULL, _e2_action_do_toggle, TRUE, E2_ACTION_TYPE_TOGGLE,
			E2_ACTION_EXCLUDE_TOGGLE },
	};
	//all action names must be freeable
	homeless_actions[0].name = g_strconcat(_A(5),".",_A(20),NULL);
	homeless_actions[1].name = g_strconcat(_A(14),".",_A(24),NULL);
	homeless_actions[2].name = g_strdup(_A(18));
	homeless_actions[3].name = g_strdup(_A(19));

	homeless_actions[4].name = g_strconcat(_A(1),".",_A(69),NULL);
	//configuration
	homeless_actions[5].name = g_strconcat(_A(2),".",_A(32),NULL);
	homeless_actions[6].name = g_strconcat(_A(2),".",_A(37),NULL);
	homeless_actions[7].name = g_strconcat(_A(2),".",_C(33),NULL);
	homeless_actions[8].name = g_strdup(_A(17));
	homeless_actions[9].name = g_strconcat(_A(15),".",_A(110),NULL);
	homeless_actions[10].name = g_strconcat(_A(15),".",_A(111),NULL);

	gint count = sizeof (homeless_actions) / sizeof (E2_Action);
	gint i;
	for (i = 0; i < count; i++)
	{
		e2_action_register (
			homeless_actions[i].name,
			homeless_actions[i].type,
			homeless_actions[i].func,
			NULL,
			homeless_actions[i].has_arg,
			homeless_actions[i].exclude,
			NULL);
	}
	//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 E2_KEYALIAS
	e2_keybinding_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] = _("configure");
	action_labels[3] = _("dialog");
	action_labels[4] = _("dirline");
	action_labels[5] = _("file");
	action_labels[6] = _("find");
	action_labels[7] = _("list");	//was <list>
	action_labels[8] = _("option");
	action_labels[9] = _("output");  // = _C(27)
	action_labels[10] = _("pane_active");
	action_labels[11] = _("pane1");  // ~ _C(28)
	action_labels[12] = _("pane2");  // ~ _C(30)
	action_labels[13] = _("panes");  // = _C(32)
	action_labels[14] = _("plugin");  //this is also used in menus - see #define PLUGIN in emelfm2.h
	action_labels[15] = _("toggle");

	action_labels[16] = _("separator");  //not really an action, but not a config either
	action_labels[17] = _("<custom command>");
	action_labels[18] = _("<separator>");
	action_labels[19] = _("<submenu>");

	//'child' names
	action_labels[20] = _("<actions>");
	action_labels[21] = _("<bookmarks>");
	action_labels[22] = _("<filters>");
	action_labels[23] = _("<line>");
	action_labels[24] = _("<menu>");
	action_labels[25] = _("about");
	action_labels[26] = _("add");
	action_labels[27] = _("adjust_ratio");
	action_labels[28] = _("children");
	action_labels[29] = _("clear");
	action_labels[30] = _("clear_history");
	action_labels[31] = _("complete");
	action_labels[32] = _("application");
	action_labels[33] = _("copy");
	action_labels[34] = _("copy_as");
	action_labels[35] = _("copy_merge");
	action_labels[36] = _("copy_with_time");
	action_labels[37] = _("default");
	action_labels[38] = _("delete");
	action_labels[39] = _("edit");
	action_labels[40] = _("edit_again");
	action_labels[41] = _("filetype");
	action_labels[42] = _("focus");
	action_labels[43] = _("focus_toggle");
	action_labels[44] = _("fullscreen");
	action_labels[45] = _("go_back");
	action_labels[46] = _("go_forward");
	action_labels[47] = _("go_up");
	action_labels[48] = _("goto_bottom");
	action_labels[49] = _("goto_top");
	action_labels[50] = _("help");
	action_labels[51] = _("history");
	action_labels[52] = _("info");
	action_labels[53] = _("insert_selection");
	action_labels[54] = _("invert_selection");
	action_labels[55] = _("mirror");
	action_labels[56] = _("mkdir");
	action_labels[57] = _("mountpoints");
	action_labels[58] = _("move");
	action_labels[59] = _("move_as");
	action_labels[60] = _("open");
	action_labels[61] = _("open_in_other");
	action_labels[62] = _("open_with");
	action_labels[63] = _("owners");
	action_labels[64] = _("page_down");
	action_labels[65] = _("page_up");
	action_labels[66] = _("pending");
	action_labels[67] = _("permissions");
	action_labels[68] = _("print");
	action_labels[69] = _("quit");
	action_labels[70] = _("refresh");
	action_labels[71] = _("refreshresume");
	action_labels[72] = _("refreshsuspend");
	action_labels[73] = _("rename");
	action_labels[74] = _("scroll_down");
	action_labels[75] = _("scroll_up");
	action_labels[76] = _("search");  //= _C(35)
	action_labels[77] = _("send");
	action_labels[78] = _("set");
	action_labels[79] = _("show");
	action_labels[80] = _("show_hidden");
	action_labels[81] = _("show_menu");
	action_labels[82] = _("sortaccesssed");
	action_labels[83] = _("sortchanged");
	action_labels[84] = _("sortgroup");
	action_labels[85] = _("sortmodified");
	action_labels[86] = _("sortname");
	action_labels[87] = _("sortpermission");
	action_labels[88] = _("sortsize");
	action_labels[89] = _("sortuser");
	action_labels[90] = _("switch");
	action_labels[91] = _("symlink");
	action_labels[92] = _("symlink_as");
	action_labels[93] = _("sync");
	action_labels[94] = _("toggle_direction");
//	action_labels[95] = _("toggle_full");	//changed to _A(108)"expand" with new toggles
	action_labels[95] = _("toggle_select_all");
	action_labels[96] = _("toggle_selected");
	action_labels[97] = _("trash");
	action_labels[98] = _("trashempty");
#ifdef E2_TREEDIALOG
	action_labels[99] = _("tree");
#endif
	action_labels[100] = _("unpack");
	action_labels[101] = _("view");  //= _C(40)
	action_labels[102] = _("view_again");
	action_labels[103] = _("view_at");
	//these are action _parameter_ strings
	action_labels[104] = _("child");
	action_labels[105] = _("ctrl");
	action_labels[106] = _("dirs");
	action_labels[107] = _("escape");
	action_labels[108] = _("expand");
	action_labels[109] = _("files");
	action_labels[110] = _("off");
	action_labels[111] = _("on");
	action_labels[112] = _("quote");
	action_labels[113] = _("shift");
	action_labels[114] = _("top");
	//late entries, out of order
	//maybe more space here, see array definition in header file
#ifdef E2_VFS
	action_labels[115] = _("<spacemenu>");
	action_labels[116] = _("dummy");
	action_labels[117] = _("namespace");
	action_labels[118] = _("unpack_in_other");
#endif
#ifdef E2_KEYALIAS
	action_labels[119] = _("key");
	action_labels[120] = _("alias");
#endif
}

/*
//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);
} */
