/* $Id: e2_command_line.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/command/e2_command_line.c
@brief command line functions

This file contains functions for e2's command and dir lines.
*/

#include <string.h>
#include <pthread.h>
#include "e2_command_line.h"
#include "e2_complete.h"
#include "e2_filelist.h"
#include "e2_task.h"

//list of names of command and dir lines, used to ensure
//that each command- or dir-line has a unique name
static GList *line_names = NULL;
extern GList *children;
extern const gchar *shellcmd;
extern pthread_mutex_t task_mutex;

  /*****************/
 /***** utils *****/
/*****************/

/**
@brief sync command line tree model history with cachable glist history
This is called in the process of destroying a command-line (eg when
rebuilding toolbars) and when session is ending
@param rt  pointer to command line runtime data struct

@return
*/
static void _e2_command_line_sync_history (E2_CommandLineRuntime *rt)
{
	//free old history
	if (rt->history != NULL)
		e2_list_free_with_data (&rt->history);
	GtkTreeIter iter;
	//fill with new state
	//CHECKME how long does the model exist when the widget is being destroyed ?
	if (gtk_tree_model_get_iter_first (rt->model, &iter))
	{
		gint count = 0;
		gint max = (rt->original) ?
			e2_option_int_get ("command-line-history-max"):
			e2_option_int_get ("dir-line-history-max");
		gchar *value;
		do
		{
			gtk_tree_model_get (rt->model, &iter, 0, &value, -1);
			rt->history = g_list_append (rt->history, value);
			if (++count == max)
				break;
		} while (gtk_tree_model_iter_next (rt->model, &iter));
	}
}
/**
@brief check if strings @a arg1 and @a arg2 match

@param arg1 string data for member of glist being scanned
@param arg2 string to be found in the glist

@return 0 if strings @a arg1 and @a arg2 are the same
*/
static gint _e2_command_line_match_list_str
	(const gchar *arg1, const gchar *arg2)
{
	return (!g_str_equal (arg1, arg2));
}
/**
@brief find commandline by its name @a option

@param option name of commandline
@param def

@return
*/
static gchar *_e2_command_line_find_name (gchar *option, gchar *def)
{
	if ((option == NULL) || (*option == '\0'))
		option = def;

	gchar *name = NULL;

	GList *tmp = g_list_find_custom (line_names, option,
		(GCompareFunc)_e2_command_line_match_list_str);
	if (tmp == NULL)
		name = g_strdup (option);
	else
	{
		gint turn = 2;
		while (tmp != NULL)
		{
			g_free (name);
			name = g_strdup_printf ("%s %d", option, turn++);
			tmp = g_list_find_custom (line_names, name,
				(GCompareFunc)_e2_command_line_match_list_str);
		}
	}
	return name;
}
/**
@brief set command-line text to last-used history value, if required, or to ""

@param rt pointer to command line runtime data struct

@return
*/
static void _e2_command_line_update_entry (E2_CommandLineRuntime *rt)
{
	GtkWidget *entry = GTK_BIN (rt->cl)->child;
	if (e2_option_bool_get_direct (rt->opt_history_last))
	{
		GtkTreeIter iter;
		if (gtk_tree_model_get_iter_first (rt->model, &iter))
		{
			e2_combobox_set_active (rt->cl, 0);
			gtk_editable_set_position (GTK_EDITABLE (entry), -1);
		}
	}
	else
		gtk_entry_set_text (GTK_ENTRY (entry), "");
}
/**
@brief get pointer to rt data for first-recorded command line (as opposed to dir line)

This does nothing if @a rt does not point to NULL

@param rt pointer to location to store command line runtime data struct pointer

@return
*/
static void _e2_command_line_get_first (E2_CommandLineRuntime **rt)
{
	if (*rt == NULL)
	{
//		if (app.command_lines == NULL)
//			return;	//no command lines registered
//		else
//		{
			//find the first-registered command-line
			E2_CommandLineRuntime *cl_rt;
			GList *member;
			for (member = app.command_lines; member != NULL ; member = g_list_next (member))
			{
				cl_rt = (E2_CommandLineRuntime *) member->data;
				if (cl_rt->original)
				{
					*rt = cl_rt;
					break;
				}
			}
//		}
	}
}
/**
@brief determine command line pointer(s)

This function tries to find the corresponding entry widget if
@a rt != NULL
Or if @a rt is NULL, tries to find the @a rt corresponding to @a widget.
But then, if @a entry is not actually an entry widget, @a rt is just set
to the one for the first-registered command line (as opposed to dir line) if any

@param widget pointer to widget to check/set (may be NULL)
@param rt pointer to the command line runtime object to check/set, or to NULL

@return  TRUE if @a entry and @a rt are set to valid values
*/
static gboolean _e2_command_line_get (GtkWidget **widget,
	E2_CommandLineRuntime **rt)
{
	//the runtime object has precedence
	if (*rt == NULL)
	{
		//if the entry really is an entry, get the pointer to the
		//runtime object
		if ((*widget != NULL) && (GTK_IS_ENTRY (*widget)))
		{
			*rt = g_object_get_data (G_OBJECT (*widget), "command-line-runtime");
			if (*rt != NULL)
				return TRUE;
			else
			{
				E2_CommandLineRuntime *clrt;
				GList *line;
				for (line = app.command_lines; line != NULL ; line = g_list_next (line))
				{
					clrt = line->data;
					if (GTK_BIN (clrt->cl)->child == *widget)
						break;
				}
				if (line != NULL)
				{
					*rt = clrt;
					return TRUE;
				}
			}
		}
		else
		//can't find anything from widget, just try to find the first available command line
			_e2_command_line_get_first (rt);
		if (*rt == NULL)	//OOPS can't find anything !!
			return FALSE;
	}
	*widget = GTK_BIN ((*rt)->cl)->child;  //set the real entry widget
	return TRUE;
}

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

/**
@brief change-dir hook function
This is initiated with BGL closed/on
@param path utf-8 string, path of the dir that is now current
@param rt pointer to command line runtime data struct

@return TRUE always
*/
//static gboolean
static void _e2_command_line_change_dir_hook (gchar *path,
	E2_CommandLineRuntime *rt)
{
	GtkWidget *entry = GTK_BIN (rt->cl)->child;
	gtk_entry_set_text (GTK_ENTRY (entry), path);
	e2_combobox_activated_cb (entry, GINT_TO_POINTER (e2_option_bool_get
		("dir-line-history-double")));
	if (!e2_option_bool_get_direct (rt->opt_history_last))
		gtk_entry_set_text (GTK_ENTRY (entry), "");
	if (e2_option_bool_get ("dir-line-pathname-hint"))
#ifdef USE_GTK2_12TIPS
		gtk_widget_set_tooltip_text (
#else
		e2_widget_set_tooltip (NULL,
#endif
		entry, path);
//	return TRUE;
}
/**
@brief mouse button-press callback
This triggers a directory selection dialog when the middle-button
is pressed while the mouse is on a directory-line
@param entry the entry widget where the button was pressed
@param event pointer to gtk event data struct
@param rt  pointer to pane runtime data struct

@return TRUE if the middle button was pressed
*/
static gboolean _e2_command_line_button_press_cb (GtkWidget *entry,
	GdkEventButton *event, E2_PaneRuntime *rt)
{
//	curr_pane->focus_widget = entry;
	if (event->button == 2)
		return (e2_pane_goto_choice (rt, entry));

	return FALSE;
}
/**
@brief autocomplete dir-line entries when dir is mounted-local
This is a(nother) callback for dir-line key-press-event signals
@param entry pointer to the widget that received the keypress
@param event event data struct
@param rt pointer to data struct for the dir-line

@return TRUE if the key was non-modified and a textkey, and triggered completion
*/
static gboolean _e2_command_line_key_press_cb (GtkWidget *entry,
	GdkEventKey *event, E2_CommandLineRuntime *rt)
{
	if ((event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0
//#ifdef USE_GTK2_10 | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK
		&& (event->keyval < 0xF000 || event->keyval > 0xFFFF))
	{
		guint pane = (rt->pane == &app.pane1) ? E2PANE1 : E2PANE2;
		if (e2_fs_complete_dir (entry, event->keyval, pane))
			return TRUE;
	}
	return FALSE;
}

  /**********************/
 /***** activation *****/
/**********************/

/**
@brief perform command entered in command line
This is a(nother) "activate" signal callback for (each) command line entry
@param entry widget containing command to run
@param rt  pointer to command line runtime data struct

@return
*/
static void _e2_command_line_command_activated_cb (GtkWidget *entry, E2_CommandLineRuntime *rt)
{
	printd (DEBUG, "_e2_command_line_default_cb (entry:,rt:)");
	const gchar *command_raw = gtk_entry_get_text (GTK_ENTRY (entry));
	//quick exit
	if ((command_raw == NULL) || (*command_raw == '\0'))
		return;
#ifdef E2_COMMANDQ
	e2_command_run ((gchar *)command_raw, E2_COMMAND_RANGE_DEFAULT, FALSE);
#else
	e2_command_run ((gchar *)command_raw, E2_COMMAND_RANGE_DEFAULT);
#endif
	_e2_command_line_update_entry (rt);
}
/**
@brief open directory entered in dir line
This is a(nother) "activate" signal callback for a directory line entry
@param entry widget containing directory string
@param rt  pointer to command line runtime data struct

@return
*/
void e2_command_line_cd_activated_cb (GtkWidget *entry, E2_CommandLineRuntime *rt)
{
	printd (DEBUG, "e2_command_line_directory_cb (entry:,rt:)");
	const gchar *command_raw = gtk_entry_get_text (GTK_ENTRY (entry));
	//quick exit
	if ((command_raw == NULL) || (*command_raw == '\0'))
		return;

	gchar *dir = e2_utf8_unescape (command_raw, ' ');

//E2_VFSTMPOK no need to handle any URI here
	E2_PaneRuntime *pane_rt = rt->pane;
	e2_pane_change_dir (pane_rt, dir);

	if (e2_option_bool_get ("dir-line-focus-after-activate"))
	{
		if (pane_rt == other_pane)
			e2_pane_activate_other ();
		else
			gtk_widget_grab_focus (pane_rt->view->treeview);
	}

	g_free (dir);
}

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

/**
@brief focus the first-logged command line

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

@return TRUE if the line is found
*/
static gboolean _e2_command_line_focus_action (gpointer from, E2_ActionRuntime *art)
{
	E2_CommandLineRuntime *rt = NULL;
	_e2_command_line_get_first (&rt);	//get the first line
	if (rt == NULL)
		return FALSE;
	gtk_editable_set_position (GTK_EDITABLE (GTK_BIN (rt->cl)->child), -1);
	gtk_widget_grab_focus (GTK_BIN (rt->cl)->child);
	return TRUE;
}
/**
@brief toggle focus between first-logged command line and active pane

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

@return TRUE if the line is found
*/
static gboolean _e2_command_line_focus_toggle_action
	(gpointer from, E2_ActionRuntime *art)
{
	E2_CommandLineRuntime *rt = NULL;
	_e2_command_line_get_first (&rt); //gets the first line
	if (rt == NULL)
		return FALSE;
	if (GTK_WIDGET_HAS_FOCUS (GTK_BIN (rt->cl)->child))
		gtk_widget_grab_focus (curr_view->treeview);
	else
	{
		gtk_editable_set_position (GTK_EDITABLE (GTK_BIN (rt->cl)->child), -1);
		gtk_widget_grab_focus (GTK_BIN (rt->cl)->child);
	}
	return TRUE;
}
/**
@brief clear contents of an entry, generally @a from

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

@return TRUE
*/
static gboolean _e2_command_line_clear_action
	(gpointer from, E2_ActionRuntime *art)
{
	//from may be entry, button, menuitem etc
	GtkWidget *entry = (from == NULL) ? NULL : GTK_WIDGET (from);
	if (from == NULL || ! GTK_IS_ENTRY (from))
	{
		//try to find the corresponding commandline
		E2_CommandLineRuntime *rt = NULL;
		_e2_command_line_get (&entry, &rt);
	}
	gtk_entry_set_text (GTK_ENTRY (entry), "");
	return TRUE;
}
/**
@brief clear command line history

@param from the entry which was activated
@param art action runtime data

@return TRUE if there was a history
*/
static gboolean _e2_command_line_clear_history_action
	(gpointer from, E2_ActionRuntime *art)
{
	GtkWidget *entry = (from == NULL) ? NULL : GTK_WIDGET (from);
	//find the command line for the entry
	E2_CommandLineRuntime *rt = NULL;
	_e2_command_line_get (&entry, &rt);

	if (rt->history != NULL)
	{
		gint num = g_list_length (rt->history);
		for (; num > 0; num--)
			gtk_combo_box_remove_text (GTK_COMBO_BOX (rt->cl), 0);
		e2_list_free_with_data (&rt->history);
		g_object_unref (rt->model);
		return TRUE;
	}
	return FALSE;
}
/**
@brief insert name of each active-pane selected item into @a entry

@param data UNUSED pointer to action runtime data, NULL
@param arg action argument string
@param entry entry for command/dir line to be processed

@return
*/
static gboolean _e2_command_line_insert_action
	(gpointer from, E2_ActionRuntime *art)
{
	gchar *arg = (gchar *)art->data;
	//find out from the action argument if the user wants quotes/escaping
	g_strstrip (arg);
	gchar *str_down = g_utf8_strdown (arg, -1);
	gboolean escape = g_str_equal (arg, _A(107));	//_("escape"
	gboolean quote = (escape) ? FALSE : g_str_equal (arg, _A(112));	//_("quote"
	g_free (str_down);

#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, insert action");
#endif
	e2_filelist_disable_refresh ();

	gchar *utf;
	GString *cmd = g_string_sized_new (128);

	GList *tmp, *base;
	base = tmp = e2_fileview_get_selected_local (curr_view);
	for (; tmp != NULL; tmp=tmp->next)
	{
		utf = F_FILENAME_FROM_LOCALE (((FileInfo *) tmp->data)->filename); //not DISPLAY
		if (escape)
		{
			gchar *temp = e2_utf8_escape (utf, ' ');
			F_FREE (utf);
			utf = temp;
		}
		cmd = g_string_append_c (cmd, ' ');
		if (quote)
			g_string_append_printf (cmd, "\"%s\"", utf);
		else
			cmd = g_string_append (cmd, utf);
		if (escape)
			g_free (utf);
		else
			F_FREE (utf);
	}

	GtkWidget *entry = (from == NULL) ? NULL : GTK_WIDGET (from);
	E2_CommandLineRuntime *rt = NULL;
	_e2_command_line_get (&entry, &rt);
	gint cursor = gtk_editable_get_position (GTK_EDITABLE (entry));
	gtk_editable_insert_text (GTK_EDITABLE (entry), cmd->str,
		cmd->len, &cursor);
	gtk_editable_set_position (GTK_EDITABLE (entry), cursor);

	g_string_free (cmd, TRUE);
	g_list_free (base);

#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "enable refresh, insert action");
#endif
	e2_filelist_enable_refresh();
	return TRUE;
}
/**
@brief complete the string in the command line associated with @a from
This is the mechanism for tab-completion, via a keybinding to the relevant
widget
@param from a pointer to the entry widget whose cntents are to be completed, or NULL
@param art pointer to action rt data

@return
*/
static gboolean _e2_command_line_complete_action
	(gpointer from, E2_ActionRuntime *art)
{
	//find which command line is active
	GtkWidget *entry = (from == NULL) ? NULL : GTK_WIDGET (from);
	E2_CommandLineRuntime *rt = NULL;
	_e2_command_line_get (&entry, &rt);
	if (rt == NULL)
		return FALSE;	//can't find what to work on

	gint pos = gtk_editable_get_position (GTK_EDITABLE (entry));	//characters, not bytes
	gchar *text = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry)));	//could be ""

	gchar *arg = (gchar *)art->data;
	printd (DEBUG, "complete_action (rt:_,arg:%s,entry:_", arg);
	//find out what type of completion is wanted and relevant
	E2_CompleteFlags flags;
	if (!rt->original)
		flags = E2_COMPLETE_FLAG_DIRS;	//dir-lines just find dirs
	else
	{
		flags = E2_COMPLETE_FLAG_PATH;	//command-lines default to finding executables
		if (g_strstr_len (text, sizeof("mount") + 3,"mount") != NULL)	//_I( encoding ok ?
			flags |= E2_COMPLETE_FLAG_MOUNT;
		//BUT completion might be for command argument
		if (*text != '\0')
		{
			gchar *s = e2_utils_pass_whitespace (text);
			if (s != NULL)
			{
				gint i, p = pos - (s - text);	//whitespace chars are all 1-byte
				for (i = 0; i < p; i++)
				{
					//FIXME handle quoted whitepace in a path string
					if (s[i] == ' ' || s[i] == '\t')
					{	//gap before cursor means we are completing an argument
						flags &= ~E2_COMPLETE_FLAG_PATH;
						g_strstrip (arg);
				//		gchar *str_down = g_utf8_strdown (arg, -1);
						if (strstr (arg, _A(106)) != NULL)
							flags |= E2_COMPLETE_FLAG_DIRS;
						if (strstr (arg, _A(109)) != NULL)
							flags |= E2_COMPLETE_FLAG_FILES;
						if (strstr (arg, _("mounts")) != NULL)	//_I("mounts")
							flags |= E2_COMPLETE_FLAG_MOUNT;
						if (strstr (arg, _("all")) != NULL)	//_I("all")
							flags |= E2_COMPLETE_FLAG_ALL;
						if (flags == 0)
							flags = E2_COMPLETE_FLAG_ALL;
				//		g_free (str_down);
						break;
					}
				}
			}
		}
	}

	GList *found = NULL;
	gint num = e2_complete_str (&text, &pos, &found, flags, 0);
//	printd (DEBUG, "found %d - %s - %d", num, text, pos);

	if (num > 0)
	{
		// if it isn't a dir: append a <space>
		if ((e2_option_bool_get ("command-line-complete-append-space")) &&
			(num == 1) && (g_utf8_get_char (g_utf8_offset_to_pointer (text, pos - 1)) != G_DIR_SEPARATOR))
		{
			gchar *part1 = e2_utf8_ndup (text, pos);
			gchar *part2 = g_utf8_offset_to_pointer (text, pos);
			gchar *text_spaced = g_strconcat (part1, " ", *part2 != '\0' ? part2 : "" , NULL);
			g_free (part1);
			gtk_entry_set_text (GTK_ENTRY (entry), text_spaced);
			gtk_editable_set_position (GTK_EDITABLE (entry), pos +1);
			g_free (text_spaced);
		}
		else
		{
			gtk_entry_set_text (GTK_ENTRY (entry), text);
			gtk_editable_set_position (GTK_EDITABLE (entry), pos);
		}
		if (num > 1)
		{
			GList *tmp;
			for (tmp = found; tmp != NULL; tmp = g_list_next (tmp))
			{
				e2_output_print (&app.tab, (gchar *)tmp->data, NULL, TRUE, NULL);
			}
			e2_output_print_end (&app.tab, FALSE);
		}
	}
	e2_list_free_with_data (&found);
	g_free (text);
	return TRUE;
}
/**
@brief children-menu callback, prepends a pid string to the command line

@param item the selected menu item
@param rt pointer to data for the command related to @a item, or NULL for "no children" item

@return
*/
static void _e2_commmand_line_child_menu_cb (GtkWidget *item, E2_TaskRuntime *rt)
{
	if (rt == NULL)	//rt == NULL for a menu item "no children"
		return;
	pthread_mutex_lock (&task_mutex);
	GList *member = g_list_find (app.taskhistory, rt);
	pthread_mutex_unlock (&task_mutex);
	if (member != NULL)	//command data still exists
	{
		E2_CommandLineRuntime *clrt = NULL;
		_e2_command_line_get_first (&clrt); //in principle, there could be > 1 OK ??
		if (clrt != NULL)
		{
			GtkWidget *entry = GTK_BIN (clrt->cl)->child;
			gint newlen;
			gint oldcursor = gtk_editable_get_position (GTK_EDITABLE (entry));
			GdkModifierType mask = e2_utils_get_modifiers ();
			if (oldcursor == 0 || (mask & GDK_CONTROL_MASK))
			{
				gint newpos = 0;
				gchar *newtext = g_strconcat (rt->pidstr, ":", NULL);
				newlen = strlen (newtext);
				gtk_editable_insert_text (GTK_EDITABLE (entry), newtext,
					newlen, &newpos);
				g_free (newtext);
			}
			else
			{
				newlen = strlen (rt->pidstr);
				gtk_editable_insert_text (GTK_EDITABLE (entry), rt->pidstr,
					newlen, &oldcursor);
			}
			gtk_editable_set_position (GTK_EDITABLE (entry), oldcursor+newlen);

			if (rt->status != E2_TASK_RUNNING)
			{
				gchar *msg = g_strdup_printf (_("Warning - process %s is not active"),
					rt->pidstr);
				e2_output_print (&app.tab, msg, NULL, TRUE, NULL);
				g_free (msg);
			}
		}
	}
}
/**
@brief create and pop up a menu of child processes, in response to a toolbar button-click
This essentially gets data for prepending to a command line
@param action_data ptr to data assigned when action struct created at session start NULL
@param rt_data data suppplied to the action
@param widget the thing that was activated to popup the menu

@return TRUE if the action succeeded
*/
static gboolean _e2_commmand_line_insert_child_pid
	(gpointer from, E2_ActionRuntime *art)
{
	GtkWidget *menu = e2_menu_create_child_menu (E2_CHILD_ACTIVE,
		_e2_commmand_line_child_menu_cb);
	//determine the menu's popup position
	if (g_str_equal (IS_A (from), "GtkButton"))
		gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
			(GtkMenuPositionFunc) e2_toolbar_set_menu_position, GTK_WIDGET (from), 1, 0);
	else
		gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
			(GtkMenuPositionFunc) e2_output_set_menu_position, app.tab.text, 0, 0);

	return TRUE;
}

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

/**
@brief create a commandline or dir-line

This creates a command line runtime struct and a command line
widget. The @a name_runtime parameter may be the same for
several command lines as a unique name is created at runtime
from it.

@param name_default  the default name e.g. "dir line" in case the runtime name is NULL or empty
@param cl_data pointer to pane rt struct for a dir line, or NULL for a command line
@param activation_cb a(nother) callabck for any "activate" signal from the command line entry
@param flags commmand line flags

@return pointer to the created runtime data struct
*/
E2_CommandLineRuntime *e2_command_line_create (gchar *name_default,
	gpointer cl_data, gpointer activation_cb, E2_CommandLineFlags flags)
{
	E2_CommandLineRuntime *rt = ALLOCATE (E2_CommandLineRuntime);
	CHECKALLOCATEDFATAL (rt);

	app.command_lines = g_list_append (app.command_lines, rt);

	//get unique name for this line
	//BUT don't want 'public' name for cache entry - just use the default
	rt->name = _e2_command_line_find_name (name_default, "default cmd line");
	//append name to local name register to ensure uniqueness of each name
	line_names = g_list_append (line_names, rt->name);

	rt->func = activation_cb;
	rt->flags = flags;
	rt->original = (flags & E2_COMMAND_LINE_ORIGINAL); //TRUE for commandline, FALSE for dirline
	rt->pane = (E2_PaneRuntime *) cl_data;	//pane rt for dirline, NULL for commandline

	rt->history = NULL;
	E2_Cache *cache = e2_cache_list_register (rt->name, &rt->history);
	cache->sync_func = _e2_command_line_sync_history;
	cache->sync_data = rt;

	//now, create the widget
	E2_ComboBoxFlags flags2 = E2_COMBOBOX_HAS_ENTRY;

	//sort out some options for command line or dir line;
	//the options have been registered in the general init function
	if (rt->original)
	{	//this is a command line
		if (e2_option_bool_get ("command-line-history-double"))
			flags2 |= E2_COMBOBOX_ALLOW_DOUBLE;
		if (e2_option_bool_get ("command-line-history-cycle"))
			flags2 |= E2_COMBOBOX_CYCLE_HISTORY;
		if (e2_option_bool_get ("command-line-menu-style"))
			flags2 |= E2_COMBOBOX_MENU_STYLE;
		flags2 |= E2_COMBOBOX_FOCUS_ON_CHANGE;
		//we need to access this often, thus we save the pointer to the
		//option set and will not have to look it up in the hashtable
		//each time
		rt->opt_history_last = e2_option_get ("command-line-history-last");
	}
	else
	{	//this is a dir line
		if (e2_option_bool_get ("dir-line-history-double"))
			flags2 |= E2_COMBOBOX_ALLOW_DOUBLE;
		if (e2_option_bool_get ("dir-line-history-cycle"))
			flags2 |= E2_COMBOBOX_CYCLE_HISTORY;
		if (e2_option_bool_get ("dir-line-menu-style"))
			flags2 |= E2_COMBOBOX_MENU_STYLE;
		rt->opt_history_last = e2_option_get ("dir-line-history-last");
	}

	//actually create the comboboxentry and its history
	rt->cl = e2_combobox_get (activation_cb, rt, &rt->history, flags2);
	//allow the entry to find its rt data
	g_object_set_data (G_OBJECT (GTK_BIN (rt->cl)->child), "command-line-runtime", rt);

	rt->model = gtk_combo_box_get_model (GTK_COMBO_BOX (rt->cl));	//model is for a iiststore
	g_object_ref (rt->model);

	//select most-recent history entry if necessary
	if ((!rt->original) || (e2_option_bool_get ("command-line-history-last")))
	{
		if (e2_combobox_has_history (GTK_COMBO_BOX (rt->cl)))
		{
			//suspend "activate" signals connected when combo was created
			if (!(flags2 & E2_COMBOBOX_NO_AUTO_HISTORY))
				g_signal_handlers_block_matched (
					G_OBJECT (GTK_BIN (rt->cl)->child),
					G_SIGNAL_MATCH_FUNC,
					0, 0, NULL, e2_combobox_activated_cb, NULL);
			if (activation_cb != NULL)
				g_signal_handlers_block_matched (
					G_OBJECT (GTK_BIN (rt->cl)->child),
					G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
					0, 0, NULL, activation_cb, rt);

			gtk_combo_box_set_active (GTK_COMBO_BOX (rt->cl), 0);

			if (!(flags2 & E2_COMBOBOX_NO_AUTO_HISTORY))
				g_signal_handlers_unblock_matched (
					G_OBJECT (GTK_BIN (rt->cl)->child),
					G_SIGNAL_MATCH_FUNC,
					0, 0, NULL, e2_combobox_activated_cb, NULL);
			if (activation_cb!= NULL)
				g_signal_handlers_unblock_matched (
					G_OBJECT (GTK_BIN (rt->cl)->child),
					G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
					0, 0, NULL, activation_cb, rt);
		}
	}

	gchar *bindingname;
	if (rt->original)
	{	//this is a command line
		if (e2_option_bool_get ("command-line-show-output-on-focus-in"))
			g_signal_connect (G_OBJECT (GTK_BIN (rt->cl)->child),
				"focus-in-event", G_CALLBACK (e2_window_output_show), NULL);
		if (e2_option_bool_get ("command-line-hide-output-on-focus-out"))
			g_signal_connect (G_OBJECT (GTK_BIN (rt->cl)->child),
				"focus-out-event", G_CALLBACK (e2_window_output_hide), NULL);
		bindingname = g_strconcat (_C(17),".",_C(5),NULL);  //_(general.command-line"
	}
	else
	{	//this is a dir line
		g_signal_connect (G_OBJECT (GTK_BIN (rt->cl)->child),
			"key-press-event", G_CALLBACK (_e2_command_line_key_press_cb), rt);
		g_signal_connect (G_OBJECT (GTK_BIN (rt->cl)->child),
			"button-press-event", G_CALLBACK (_e2_command_line_button_press_cb), rt->pane);
		//register to change_dir hook for dir-lines so we always know the
		//latest directory to add it to the history and show it if wanted
//		gboolean DEBUGhookhere;
//		e2_hook_register (&rt->pane->hook_change_dir,
//			_e2_command_line_change_dir_hook, rt);
		bindingname = g_strconcat (_C(17),".",_C(12),NULL);  //_(general.dir-line"
	}

	e2_keybinding_register (bindingname, GTK_BIN (rt->cl)->child);
	g_free (bindingname);

	return rt;
}
/* *
@brief

@param rt pointer to command line runtime data struct

@return the commandline widget
*/
//static GtkWidget *_e2_command_line_create (E2_CommandLineRuntime *rt)
//{
//	return GTK_WIDGET (rt->cl);
//}
/**
@brief clear runtime data for a command/dir line

This is the 'destroy data' function supplied when command
lines are created.
It will sync the history and free all internal memory.

@param rt  the runtime object to destroy

@return
*/
void e2_command_line_destroy (E2_CommandLineRuntime *rt)
{
	line_names = g_list_remove (line_names, rt->name);

/*	if (rt->original)
	{	//line is a command-line
		GList *list;
		for (list = app.command_lines; list != NULL ; list = g_list_next (list))
		{
			if (list->data == rt)
			{
				app.command_lines = g_list_delete_link (app.command_lines, list);
				break;
			}
		}
	}
	else */
	app.command_lines = g_list_remove (app.command_lines, rt);
/*	gboolean DEBUGhookhere;
	if (!rt->original)
	{	//line is a dir line
		E2_PaneRuntime *pane_rt = rt->pane;
		e2_hook_unregister (&pane_rt->hook_change_dir,
			_e2_command_line_change_dir_hook, rt, TRUE);
	} */

	_e2_command_line_sync_history (rt);	//CHECKME a cache-unregister sync function?

	//now we can unregister
	e2_cache_unregister (rt->name);
	e2_list_free_with_data (&rt->history);
	g_free (rt->name);
	//and the combo box data model may be freed
	g_object_unref (rt->model);
	DEALLOCATE (E2_CommandLineRuntime, rt);
}
/**
@brief clear runtime data for all command/dir lines

@return
*/
void e2_command_line_clean (void)
{
	GList *member;
	for (member = app.command_lines; member != NULL; member = member->next)
		DEALLOCATE (E2_CommandLineRuntime, (E2_CommandLineRuntime *)member->data);
}
/**
@brief change-dirline contents function
This is initiated with BGL closed/on
@param path utf-8 string, path of the dir that is being opened, with trailer
@param rt pointer to data struct for the pane being changed

@return
*/
void e2_command_line_change_dir (gchar *utfpath, E2_PaneRuntime *rt)
{
	GList *member;
	for (member = app.command_lines; member != NULL; member = member->next)
	{
		E2_CommandLineRuntime *cl = (E2_CommandLineRuntime *)member->data;
		if (cl->pane == rt)
		{
			_e2_command_line_change_dir_hook (utfpath, cl);
			break;
		}
	}
}
/**
@brief initialize command line actions

This function initializes the command line actions.
It is, and should only be, called once at startup.

@return
*/
void e2_command_line_actions_register (void)
{
	gchar *action_name = g_strconcat(_A(1),".",_A(23),NULL);
	e2_action_register (action_name, E2_ACTION_TYPE_COMMAND_LINE,
		_e2_command_line_command_activated_cb, "command line", TRUE, //name not translated
		E2_ACTION_EXCLUDE_MENU | E2_ACTION_EXCLUDE_ACCEL, NULL);
	action_name = g_strconcat(_A(1),".",_A(42),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_command_line_focus_action, NULL, FALSE);
	action_name = g_strconcat(_A(1),".",_A(43),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_command_line_focus_toggle_action, NULL, FALSE);
	action_name = g_strconcat(_A(1),".",_A(29),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_command_line_clear_action, NULL, FALSE);
	action_name = g_strconcat(_A(1),".",_A(30),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_command_line_clear_history_action, NULL, FALSE);
	action_name = g_strconcat(_A(1),".",_A(31),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_command_line_complete_action, NULL, FALSE);
	action_name = g_strconcat(_A(1),".",_A(53),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_command_line_insert_action, NULL, FALSE);
	action_name = g_strconcat(_A(28),".",_A(24),NULL);
	e2_action_register (action_name, E2_ACTION_TYPE_ITEM,
		_e2_commmand_line_insert_child_pid, NULL, FALSE,
		E2_ACTION_EXCLUDE_MENU, NULL);
}
/**
@brief initialize command line options

This function initializes the options for the various command lines.
It is, and should only be, called once at startup.

@return
*/
void e2_command_line_options_register (void)
{
	//none of these require a rebuild after change
	//command line
	gchar *group_name = g_strconcat(_C(6),".",_C(5),":",_C(25),NULL);  //_("commands.command line:misc"
	e2_option_bool_register ("command-line-history-last", group_name, _("show last"),
		_("If activated, the last-entered command will be displayed, instead of an empty line"),
		NULL, FALSE,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP);

	group_name = g_strconcat(_C(6),".",_C(5),":",_C(18),NULL);  //_("commands.command line:history"
	e2_option_int_register ("command-line-history-max", group_name, _("maximum number of history entries"),
		_("This is the largest number of command-line history entries that will be recorded"),
		NULL, 10, 0, 9999999,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP);
	e2_option_bool_register ("command-line-history-double", group_name, _("double entries"),
		_("This allows entries to be recorded more than once in the history list, so the last entry is always close to hand"),
		NULL, FALSE,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS);
	e2_option_bool_register ("command-line-history-cycle", group_name, _("cyclic list"),
		_("When scanning the history list, cycle from either end around to the other end, instead of stopping"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS);
	e2_option_bool_register ("command-line-menu-style",
		group_name, _("show as a menu"),
		_("If activated, the history entries will be presented as a menu. "
		"For most Gtk+2 themes, this will not be as attractive as the list view"),
		NULL, FALSE,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS);

	group_name =g_strconcat(_C(6),".",_C(5),":",_C(38),NULL); //_("commands.command line:tab completion"
	e2_option_bool_register ("command-line-complete-append-space",
		group_name, _("append space after unique items"),
		_("This appends a 'space' character to the end of a unique successful match of a file"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP);

	//dir line
	group_name = g_strconcat(_C(32),".",_C(12),":",_C(25),NULL); //_("panes.directory line:misc"
	e2_option_bool_register ("dir-line-history-last",
		group_name, _("show last entry"),
		_("If activated, the last-entered directory will be displayed, instead of an empty line"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP);
	e2_option_bool_register ("dir-line-pathname-hint",
		group_name, _("show pathname as a tooltip"),
		_("If activated, the full directory pathname will display as a tooltip. "
		"This is useful when the path is too long for the normal display"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED);
	const gchar *opt_completions[] =
		{_("<Tab> only"), _("inserted"), _("selected"), NULL};
	e2_option_sel_register ("dir-line-completion", group_name, _("directory path completion"),
		_("This determines the mode of completion when keying a directory-path"),
		NULL, 1, opt_completions,
		E2_OPTION_FLAG_ADVANCED);

	group_name = g_strconcat(_C(32),".",_C(12),":",_C(18),NULL); //_("panes.directory line:history"
	e2_option_int_register ("dir-line-history-max", group_name, _("maximum number of history entries"),
		_("This is the largest number of directory-line history entries that will be retained"),
		NULL, 10, 0, 999999,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP);
	e2_option_bool_register ("dir-line-history-double",
		group_name, _("double entries"),
		_("This allows entries to be recorded more than once in the history list, so the last entry is always close to hand"),
		NULL, FALSE,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS);
	e2_option_bool_register ("dir-line-history-cycle",
		group_name, _("cyclic list"),
		_("When scanning the history list, cycle from either end around to the other end, instead of stopping"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS);
	e2_option_bool_register ("dir-line-menu-style",
		group_name, _("show as a menu"),
		_("If activated, the directory line history will be presented as a menu. "
		"For most for most Gtk+2 themes, this will not be as attractive as the list view"),
		NULL, FALSE,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_BUILDBARS);
}
