/*
** 1998-09-26 -	A brand new command configuration page!
** 1998-10-03 -	There's a weird sensitivity bug with the command row type option menu...
** 1999-03-09 -	Redesigned the Before/After flag GUI. It's less stupid now, and vertically shorter.
*/

#include "gentoo.h"
#include "guiutil.h"
#include "cmdseq.h"
#include "dialog.h"
#include "hash_dialog.h"

#include "cfg_gui.h"
#include "cfg_module.h"
#include "cfg_cmdseq.h"

#define	NODE	"CmdSeqs"

/* ----------------------------------------------------------------------------------------- */

typedef struct {		/* Extra widgetry for built-in rows. Not very much. */
	GtkWidget	*vbox;
	GtkWidget	*label;		/* Just a big fat label saying that there's nothing here. */
} PX_Builtin;

typedef struct {		/* External command general flags. */
	GtkWidget	*vbox;		/* A box that holds the page. */
	GtkWidget	*runbg;		/* Run in background? */
	GtkWidget	*killprev;	/* Kill previous instance? */
	GtkWidget	*survive;	/* Survive quit? */
	GtkWidget	*graboutput;	/* Grab output? */
} PXE_General;

typedef struct {
	GtkWidget	*hbox;
	GtkWidget	*bframe;
	GtkWidget	*cdsrc;
	GtkWidget	*cddst;
	GtkWidget	*aframe;
	GtkWidget	*rssrc;
	GtkWidget	*rsdst;
} PXE_BAF;

typedef struct {		/* Extra widgetry for external commands. Plenty of flags. */
	GtkWidget	*nbook;		/* A notebook. */
	PXE_General	gflags;		/* General flags page. */
	PXE_BAF		baf;
} PX_External;

typedef struct {
	GtkWidget	*vbox;		/* Standard. */
	GtkWidget	*scwin;		/* Scrolled window wrapping the main clist. */
	GtkWidget	*clist;		/* Main clist, shows defined command sequences. */

	GtkWidget	*nhbox;		/* A hbox holding name label and entry. */
	GtkWidget	*name;		/* Name entry widget. */

	GtkWidget	*dframe;	/* Definition frame. */
	GtkWidget	*dbtn[5];	/* Definition row commands. */
	GtkWidget	*dscwin;	/* Scrolled window for definition clist. */
	GtkWidget	*dclist;	/* Definition clist, shows sequence rows. */
	GtkWidget	*dhbox;		/* Hbox for type, definition, and pick button. */
	GtkWidget	*dtype;		/* Type option menu, select built-in/external. */
	GtkWidget	*dtmenu;	/* The actual menu for type selection. */
	GtkWidget	*ddef;		/* Row definition. */
	GtkWidget	*dpick;		/* Pick button, for definition help. */

	GtkWidget	*dextra;	/* Notebook for type-specific info (flags etc). */

	GtkWidget	*drepeat;	/* Repeat-flag, global for sequence. */

	PX_Builtin	px_builtin;
	PX_External	px_external;

	GtkWidget	*bhbox;		/* Command button hbox ("Add" & "Delete"). */
	GtkWidget	*badd, *bdel;

	MainInfo	*min;		/* Very handy. */
	gint		modified : 1;	/* Has the page been modified? */
	GHashTable	*cmdseq;	/* Copies of all command sequences live here. */
	CmdSeq		*curr_seq;	/* Pointer to currently selected sequence. */
	gint		curr_crow;	/* Row index of currently selected sequence. */
	CmdRow		*curr_row;	/* Pointer to currently selected row. */
	gint		curr_rrow;	/* Row index (in clist) of currently selected command row. */
	CRType		curr_type;	/* Current row type. */
} P_CmdSeq;

typedef struct {
	Dialog		*dlg;
	P_CmdSeq	*page;
	GtkWidget	*scwin;
	GtkWidget	*clist;
	char		*code;
	gint		row;
} D_Ext;

static P_CmdSeq	the_page;

/* ----------------------------------------------------------------------------------------- */

static void	set_row_widgets(P_CmdSeq *page);
static void	reset_row_widgets(P_CmdSeq *page);

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-26 -	Compare two command sequences, lexicographically on their names. */
static gint cmp_cmdseq(gpointer a, gpointer b)
{
	CmdSeq	*sa = (CmdSeq *) a, *sb = (CmdSeq *) b;

	if(sa != NULL && sb != NULL)
		return strcmp(sa->name, sb->name);
	return 0;
}

/* 1998-09-26 -	Insert command sequence named <key> into list at <user>, sorted. */
static void sort_cmdseq(gpointer key, gpointer data, gpointer user)
{
	GList	**sorted = (GList **) user;

	*sorted = g_list_insert_sorted(*sorted, data, (GCompareFunc) cmp_cmdseq);
}

/* 1998-09-26 -	Zzap the clist and reinitialize its contents, based on the current contents
**		of our editing hashtable.
*/
static void repopulate_clist(P_CmdSeq *page)
{
	char	*line[] = { NULL };
	GList	*sorted = NULL, *iter;
	gint	row;

	if(page->cmdseq == NULL)
		return;

	gtk_clist_freeze(GTK_CLIST(page->clist));
	gtk_clist_clear(GTK_CLIST(page->clist));
	g_hash_table_foreach(page->cmdseq, sort_cmdseq, (gpointer) &sorted);
	for(iter = sorted; iter != NULL; iter = g_list_next(iter))
	{
		line[0] = ((CmdSeq *) iter->data)->name;
		row = gtk_clist_append(GTK_CLIST(page->clist), line);
		gtk_clist_set_row_data(GTK_CLIST(page->clist), row, iter->data);
	}
	g_list_free(sorted);
	gtk_clist_thaw(GTK_CLIST(page->clist));
}

/* 1998-09-26 -	Copy the command sequence pointed to by <data>, and insert it into our
**		editing hash table.
*/
static void copy_cmdseq(gpointer key, gpointer data, gpointer user)
{
	P_CmdSeq	*page = (P_CmdSeq *) user;
	CmdSeq		*seq;

	if((seq = csq_cmdseq_copy((CmdSeq *) data)) != NULL)
		csq_cmdseq_hash(&page->cmdseq, seq);
}

/* 1998-09-26 -	Copy all current commands into our editing version, and also populate the
**		main clist with a list of the available commands.
*/
static void populate_clist(MainInfo *min, P_CmdSeq *page)
{
	page->cmdseq = NULL;
	if(min->cfg.commands.cmdseq != NULL)
	{
		g_hash_table_foreach(min->cfg.commands.cmdseq, copy_cmdseq, (gpointer) page);
		repopulate_clist(page);
	}
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-26 -	We have a selection, so go ahead and populate the definition clist. */
static void populate_def_clist(P_CmdSeq *page)
{
	gchar	*line[] = { NULL, NULL };
	gint	pos;
	GList	*iter;
	CmdRow	*row;

	if(page != NULL && page->curr_seq != NULL)
	{
		gtk_clist_freeze(GTK_CLIST(page->dclist));
		gtk_clist_clear(GTK_CLIST(page->dclist));
		for(iter = page->curr_seq->rows; iter != NULL; iter = g_list_next(iter))
		{
			row = (CmdRow *) iter->data;

			line[0] = (gchar *) csq_cmdrow_type_to_string(row->type);
			line[1] = row->def->str;
			pos = gtk_clist_append(GTK_CLIST(page->dclist), line);
			gtk_clist_set_row_data(GTK_CLIST(page->dclist), pos, row);
		}
		gtk_clist_thaw(GTK_CLIST(page->dclist));
	}
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-26 -	Reset all widgets to their most passive state. */
static void reset_widgets(P_CmdSeq *page)
{
	page->curr_seq = NULL;
	gtk_entry_set_text(GTK_ENTRY(page->name), "");
	gtk_widget_set_sensitive(page->nhbox, FALSE);

	gtk_clist_clear(GTK_CLIST(page->dclist));
	gtk_option_menu_set_history(GTK_OPTION_MENU(page->dtype), 0);	
	gtk_entry_set_text(GTK_ENTRY(page->ddef), "");
	gtk_notebook_set_page(GTK_NOTEBOOK(page->dextra), 0);
	reset_row_widgets(page);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(page->drepeat), FALSE);
	gtk_widget_set_sensitive(page->dframe, FALSE);

	gtk_widget_set_sensitive(page->bdel, FALSE);
}

/* ----------------------------------------------------------------------------------------- */

/* 1999-03-08 -	Set the state of the command's repeat flag. Only available if no command row
**		runs in the background, will be automatically disabled as soon as a row is
**		given the background flag.
*/
static void set_repeat(P_CmdSeq *page)
{
	if(page != NULL && page->curr_seq != NULL)
	{
		CmdRow	*row;
		GList	*iter;

		for(iter = page->curr_seq->rows; iter != NULL; iter = g_list_next(iter))
		{
			row = (CmdRow *) iter->data;
			if((row->type == CRTP_EXTERNAL) && (row->extra.external.gflags & CGF_RUNINBG))
				break;
		}
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(page->drepeat), (page->curr_seq->flags & CSFLG_REPEAT)
						&& (iter == NULL));
		gtk_widget_set_sensitive(page->drepeat, iter == NULL);
	}
}

/* 1998-09-26 -	Set widgets in a state suitable to the current selection. */
static void set_widgets(P_CmdSeq *page)
{
	if(page != NULL && page->curr_seq != NULL)
	{
		gtk_entry_set_text(GTK_ENTRY(page->name), page->curr_seq->name);
		gtk_widget_set_sensitive(page->nhbox, TRUE);

		populate_def_clist(page);
		reset_row_widgets(page);
		gtk_widget_set_sensitive(page->dframe, TRUE);
		gtk_widget_set_sensitive(page->bdel, TRUE);
		set_repeat(page);
	}
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-26 -	User just selected a new command sequence to work with. */
static gint evt_cmdseq_selected(GtkWidget *wid, gint row, gint column, GdkEventButton *evt)
{
	P_CmdSeq	*page = (P_CmdSeq *) gtk_object_get_user_data(GTK_OBJECT(wid));
	CmdSeq		*cs;

	if((cs = gtk_clist_get_row_data(GTK_CLIST(wid), row)) != NULL)
	{
		page->curr_seq = cs;
		page->curr_crow = row;
		page->curr_row = NULL;
		page->curr_rrow = -1;
		set_widgets(page);
	}
	return TRUE;
}

/* 1998-09-26 -	There is no longer a current command sequence. */
static gint evt_cmdseq_unselected(GtkWidget *wid, gint row, gint column, GdkEventButton *evt)
{
	P_CmdSeq	*page = (P_CmdSeq *) gtk_object_get_user_data(GTK_OBJECT(wid));

	page->curr_seq = NULL;
	page->curr_crow = -1;
	page->curr_row = NULL;
	page->curr_rrow = -1;
	reset_widgets(page);
	return TRUE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-26 -	User is editing the name of the current sequence. */
static gint evt_name_changed(GtkWidget *wid, gpointer data)
{
	char		*text;
	P_CmdSeq	*page = (P_CmdSeq *) data;

	if(page != NULL && page->curr_seq != NULL)
	{
		if((text = gtk_entry_get_text(GTK_ENTRY(wid))) != NULL)
		{
			page->modified = TRUE;
			csq_cmdseq_set_name(page->cmdseq, page->curr_seq, text);
			gtk_clist_set_text(GTK_CLIST(page->clist), page->curr_crow, 0, page->curr_seq->name);
		}
	}
	return TRUE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-27 -	Set general flag widgets in <pxeg> to match <flags>. */
static void set_cx_g_flags(PXE_General *pxeg, guint32 flags)
{
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pxeg->runbg), flags & CGF_RUNINBG);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pxeg->killprev), flags & CGF_KILLPREV);
	gtk_widget_set_sensitive(pxeg->killprev, flags & CGF_RUNINBG);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pxeg->graboutput), flags & CGF_GRABOUTPUT);
	gtk_widget_set_sensitive(pxeg->survive, flags & CGF_RUNINBG);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pxeg->survive), flags & CGF_SURVIVE);
}

/* 1999-03-09 -	Set visual state of before/after checkbuttons, according to <flags>. Sets for
**		the before group if <after> is FALSE, otherwise for the after group. Geddit?
*/
static void set_cx_ba_flags(PXE_BAF *pxbaf, guint after, guint32 flags)
{
	if(!after)		/* Set "before" type flags, i.e. CD source XOR dest. */
	{
		guint	st_src = FALSE, st_dst = FALSE;		/* Guess a few states. */

		if(flags & CBAF_CD_SOURCE)
			st_src = TRUE;
		else if(flags & CBAF_CD_DEST)
			st_dst = TRUE;
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pxbaf->cdsrc), st_src);
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pxbaf->cddst), st_dst);
	}
	else			/* Set "after" flags, i.e. rescan source IOR dest. */
	{
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pxbaf->rssrc), flags & CBAF_RESCAN_SOURCE);
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pxbaf->rsdst), flags & CBAF_RESCAN_DEST);
	}
}

/* 1998-09-27 -	Set the "extra" widgets for the current row, which is known to be external. */
static void set_cx_external_widgets(P_CmdSeq *page)
{
	CX_Ext	*ext = &page->curr_row->extra.external;

	set_cx_g_flags(&page->px_external.gflags, ext->gflags);

	set_cx_ba_flags(&page->px_external.baf, FALSE, ext->baflags[0]);
	set_cx_ba_flags(&page->px_external.baf, TRUE,  ext->baflags[1]);
}

/* 1998-09-27 -	Set up widgets for row, assuming there is a row selected. */
static void set_row_widgets(P_CmdSeq *page)
{
	guint	i;

	if(page != NULL)
	{
		for(i = 1; i < sizeof page->dbtn / sizeof page->dbtn[0]; i++)
			gtk_widget_set_sensitive(page->dbtn[i], TRUE);
		gtk_option_menu_set_history(GTK_OPTION_MENU(page->dtype), page->curr_row->type);
		gtk_entry_set_text(GTK_ENTRY(page->ddef), page->curr_row->def->str);
		gtk_notebook_set_page(GTK_NOTEBOOK(page->dextra), page->curr_row->type);
		gtk_widget_set_sensitive(page->dhbox,  TRUE);
		gtk_widget_set_sensitive(page->dextra, TRUE);
		gtk_widget_set_sensitive(page->dframe, TRUE);

		if(page->curr_row->type == CRTP_EXTERNAL)
			set_cx_external_widgets(page);
	}
}

/* 1998-09-27 -	Reset the row widgets. */
static void reset_row_widgets(P_CmdSeq *page)
{
	guint	i;

	if(page != NULL)
	{
		for(i = 1; i < sizeof page->dbtn / sizeof page->dbtn[0]; i++)
			gtk_widget_set_sensitive(page->dbtn[i], FALSE);
		gtk_entry_set_text(GTK_ENTRY(page->ddef), "");
		gtk_widget_set_sensitive(page->dhbox, FALSE);
		gtk_widget_set_sensitive(page->dextra, FALSE);
	}
}

/* 1998-09-26 -	Called when the user selects a row of the current sequence. */
static gint evt_row_selected(GtkWidget *wid, gint row, gint column, GdkEventButton *evt)
{
	P_CmdSeq	*page = (P_CmdSeq *) gtk_object_get_user_data(GTK_OBJECT(wid));

	if(page != NULL)
	{
		page->curr_row = (CmdRow *) gtk_clist_get_row_data(GTK_CLIST(page->dclist), row);
		page->curr_rrow = row;
		page->curr_type = page->curr_row->type;
		set_row_widgets(page);
	}

	return TRUE;
}

/* 1998-09-26 -	Called as the row clist loses its selection. */
static gint evt_row_unselected(GtkWidget *wid, gint row, gint column, GdkEventButton *evt)
{
	P_CmdSeq	*page = (P_CmdSeq *) gtk_object_get_user_data(GTK_OBJECT(wid));

	if(page != NULL)
	{
		page->curr_row = NULL;
		page->curr_rrow = -1;
		reset_row_widgets(page);
	}

	return TRUE;
}

/* 1999-03-08 -	User hit the "Repeat" check button, grab it and set sequence's flag
**		accordingly.
*/
static gint evt_repeat_clicked(GtkWidget *wid, gpointer user)
{
	P_CmdSeq	*page = user;

	if((page != NULL) && (page->curr_seq != NULL))
	{
		if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid)))
			page->curr_seq->flags |= CSFLG_REPEAT;
		else
			page->curr_seq->flags &= ~CSFLG_REPEAT;
	}
	return TRUE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-27 -	User just hit the "Add Row" button, so let's do just that. */
static gint evt_addrow_clicked(GtkWidget *wid, gpointer data)
{
	P_CmdSeq	*page = (P_CmdSeq *) data;
	CmdRow		*nr;
	gint		pos;

	if(page != NULL && page->curr_seq != NULL)
	{
		if((nr = csq_cmdrow_new(page->curr_type, "", 0UL)) != NULL)
		{
			page->modified = TRUE;
			pos = csq_cmdseq_row_append(page->curr_seq, nr);
			populate_def_clist(page);
			gtk_clist_select_row(GTK_CLIST(page->dclist), pos, 0);
/*			gtk_clist_moveto(GTK_CLIST(page->dclist), pos, -1, 0.5, 0);*/
			gtk_widget_grab_focus(page->ddef);
		}
	}
	return TRUE;
}

/* 1998-09-27 -	Delete the current row from the current command sequence. */
static gint evt_delrow_clicked(GtkWidget *wid, gpointer data)
{
	P_CmdSeq	*page = (P_CmdSeq *) data;
	gint		opos, npos;

	if(page != NULL && page->curr_seq != NULL && page->curr_row != NULL)
	{
		page->modified = TRUE;
		opos = page->curr_rrow;
		npos = csq_cmdseq_row_delete(page->curr_seq, page->curr_row);
		gtk_clist_unselect_row(GTK_CLIST(page->dclist), opos, 0);
		populate_def_clist(page);
		gtk_clist_select_row(GTK_CLIST(page->dclist), npos, 0);
		gtk_clist_moveto(GTK_CLIST(page->dclist), npos, -1, 0.5, 0);
	}
	return TRUE;
}

/* 1998-09-27 -	User wants to duplicate the current row. Fine. */
static gint evt_duprow_clicked(GtkWidget *wid, gpointer data)
{
	P_CmdSeq	*page = (P_CmdSeq *) data;
	CmdRow		*nr;
	gint		pos;

	if(page != NULL && page->curr_seq != NULL && page->curr_row != NULL)
	{
		if((nr = csq_cmdrow_copy(page->curr_row)) != NULL)
		{
			page->modified = TRUE;
			pos = csq_cmdseq_row_append(page->curr_seq, nr);
			populate_def_clist(page);
			gtk_clist_select_row(GTK_CLIST(page->dclist), pos, 0);
			gtk_clist_moveto(GTK_CLIST(page->dclist), pos, -1, 0.5, 0);
		}
	}
	return TRUE;
}

/* 1998-09-27 -	Move currently selected row up one row (exchange with previous row). */
static gint evt_uprow_clicked(GtkWidget *wid, gpointer data)
{
	P_CmdSeq	*page = (P_CmdSeq *) data;
	gint		pos;

	if(page != NULL && page->curr_seq != NULL && page->curr_row != NULL)
	{
		page->modified = TRUE;
		pos = csq_cmdseq_row_move(page->curr_seq, page->curr_row, -1);
		populate_def_clist(page);
		gtk_clist_select_row(GTK_CLIST(page->dclist), pos, 0);
		gtk_clist_moveto(GTK_CLIST(page->dclist), pos, -1, 0.5, 0);
	}
	return TRUE;
}

/* 1998-09-27 -	Move currently selected row down one row (exchange with next row). */
static gint evt_downrow_clicked(GtkWidget *wid, gpointer data)
{
	P_CmdSeq	*page = (P_CmdSeq *) data;
	gint		pos;

	if(page != NULL && page->curr_seq != NULL && page->curr_row != NULL)
	{
		page->modified = TRUE;
		pos = csq_cmdseq_row_move(page->curr_seq, page->curr_row, 1);
		populate_def_clist(page);
		gtk_clist_select_row(GTK_CLIST(page->dclist), pos, 0);
		gtk_clist_moveto(GTK_CLIST(page->dclist), pos, -1, 0.5, 0);
	}
	return TRUE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-26 -	User choose a new type for the current row. */
static gint evt_type_selected(GtkWidget *wid, gpointer data)
{
	P_CmdSeq	*page = (P_CmdSeq *) data;
	gint		pos;

	if(page != NULL && page->curr_seq != NULL && page->curr_row != NULL)
	{
		page->modified = TRUE;
		pos = page->curr_rrow;
		page->curr_type = (CRType) gtk_object_get_user_data(GTK_OBJECT(wid));
		csq_cmdrow_set_type(page->curr_row, page->curr_type);
		populate_def_clist(page);
		gtk_clist_select_row(GTK_CLIST(page->dclist), pos, 0);
		gtk_clist_moveto(GTK_CLIST(page->dclist), pos, -1, 0.5, 0);
		gtk_notebook_set_page(GTK_NOTEBOOK(page->dextra), page->curr_type);
	}

	return TRUE;
}

/* 1998-09-27 -	User changed the command definition, so we need to store the new one.
**		Notice how this routine avoids to rebuild the entire clist; this is
**		probably a good idea since the changes may be rapid.
*/
static gint evt_def_changed(GtkWidget *wid, gpointer data)
{
	P_CmdSeq	*page = (P_CmdSeq *) data;
	char		*def;

	if(page != NULL && page->curr_seq != NULL && page->curr_row != NULL)
	{
		if((def = gtk_entry_get_text(GTK_ENTRY(wid))) != NULL)
		{
			page->modified = TRUE;
			csq_cmdrow_set_def(page->curr_row, def);
			gtk_clist_set_text(GTK_CLIST(page->dclist), page->curr_rrow, 1, def);
		}
	}
	return TRUE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-27 -	Callback for hash_dialog. Just set the definition to the chosen command name. */
static void sel_builtin(gpointer hdata, gpointer data)
{
	P_CmdSeq	*page = (P_CmdSeq *) data;

	gtk_entry_set_text(GTK_ENTRY(page->ddef), (char *) hdata);
}

/* 1998-09-27 -	Pop up a requester where the user can choose among all the built-in commands. */
static void pick_builtin(P_CmdSeq *page)
{
	hdl_dialog(page->min->cfg.commands.builtin, sel_builtin, (gpointer) page);
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-27 -	A dialog callback for the external command "pick" (code) dialog. */
static void sel_external(Dialog *dlg, guint result, gpointer user)
{
	char	buf[1024];
	D_Ext	*dext = (D_Ext *) user;

	if(result == DLG_POSITIVE)
	{
		if(dext->code[0] == '\\')		/* Is the code an escaped character? */
			sprintf(buf, dext->code);
		else
			sprintf(buf, " {%s}", dext->code);
		gtk_entry_append_text(GTK_ENTRY(dext->page->ddef), buf);
	}
}

/* 1998-09-27 -	A code has been selected. */
static gint evt_extcode_selected(GtkWidget *wid, gint row, gint column, GdkEventButton *evt)
{
	D_Ext	*dext = (D_Ext *) gtk_object_get_user_data(GTK_OBJECT(wid));

	dext->code = gtk_clist_get_row_data(GTK_CLIST(wid), row);
	if(evt != NULL && evt->type == GDK_2BUTTON_PRESS)
		dlg_dialog_close_left(dext->dlg);
	return TRUE;
}

/* 1998-09-27 -	Let user pick a special code sequence for use in external commands. This
**		is very simple, but becomes complex because I want to use a clist.
*/
static void pick_external(P_CmdSeq *page)
{
	GtkWidget	*clist;
	static char	*code[] = {	"\\{", "Opening brace",	"\\}", "Closing brace",
					"f", "First selected", "fu", "First selected, unselect",
					"fp", "First selected, with path", "fpu", "First selected, with path, unselect",
					"fd", "First selected (destination pane)",
					"F", "All selected",	"Fu", "All selected, unselect",
					"Fp", "All selected, with paths", "Fpu", "All selected, with paths, unselect",
					"Fd", "All selected (destination pane)",
					"Ps", "Path to source pane's directory",	"Pd", "Path to destination pane's directory",
					"Ph", "Path to home directory",	"Pl", "Path of left pane",
					"Pr", "Path of right pane",
					"Ic:\"label\"=\"choice1\",...", "Input combo box",
					"Im:\"label\"=\"text1:choice1\",...", "Input using menu",
					"Is:\"label\"=\"default\"", "Input string",
					"It:\"label\"", "Set title text of input window",
					"$NAME", "Value of $NAME (environment)",
					"#", "gentoo's PID",
					"~NAME", "Home directory for user NAME",
					NULL};
	int		i;
	gint		row;
	static D_Ext	dext;

	dext.page = page;
	dext.code = NULL;
	dext.scwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dext.scwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
	dext.clist = clist = gtk_clist_new(2);
	gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_BROWSE);
	gtk_clist_set_column_width(GTK_CLIST(clist), 0, 160);
	gtk_clist_set_column_width(GTK_CLIST(clist), 1, 256);
	gtk_signal_connect(GTK_OBJECT(clist), "select_row", GTK_SIGNAL_FUNC(evt_extcode_selected), (gpointer) &dext);
	gtk_object_set_user_data(GTK_OBJECT(clist), (gpointer) &dext);
	for(i = 0; code[i] != NULL; i += 2)
	{
		row = gtk_clist_append(GTK_CLIST(clist), &code[i]);
		gtk_clist_set_row_data(GTK_CLIST(clist), row, code[i]);
	}
	gtk_container_add(GTK_CONTAINER(dext.scwin), clist);
	gtk_widget_show(clist);
	dext.dlg = dlg_dialog(dext.scwin, "Pick Code", "OK|Cancel", sel_external, &dext);
	dlg_dialog_set_size(dext.dlg, 384, 352);
}

/* 1998-09-27 -	The details (formerly "...") button has been clicked; pop up a quick-selection
**		window where the user can select something s?he's too lazy to type.
*/
static gint evt_pick_clicked(GtkWidget *wid, gpointer data)
{
	P_CmdSeq	*page = (P_CmdSeq *) data;

	if(page != NULL && page->curr_seq != NULL && page->curr_row != NULL)
	{
		if(page->curr_row->type == CRTP_BUILTIN)
			pick_builtin(page);
		else if(page->curr_row->type == CRTP_EXTERNAL)
			pick_external(page);
	}

	return TRUE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-27 -	Add a new command sequence to the happy bunch. */
static gint evt_add_clicked(GtkWidget *wid, gpointer data)
{
	gchar		*line[] = { NULL };
	gint		row;
	P_CmdSeq	*page = (P_CmdSeq *) data;
	CmdSeq		*ns;

	if(page != NULL)
	{
		line[0] = (gchar *) csq_cmdseq_unique_name(page->cmdseq);
		if((ns = csq_cmdseq_new(line[0], 0UL)) != NULL)
		{
			csq_cmdseq_hash(&page->cmdseq, ns);
			row = gtk_clist_append(GTK_CLIST(page->clist), line);
			gtk_clist_set_row_data(GTK_CLIST(page->clist), row, (gpointer) ns);
			gtk_clist_select_row(GTK_CLIST(page->clist), row, 0);
			gtk_entry_select_region(GTK_ENTRY(page->name), 0, -1);
			gtk_widget_grab_focus(page->name);
		}
	}
	return TRUE;
}

/* 1998-09-27 -	Delete the currently selected command sequence. */
static gint evt_del_clicked(GtkWidget *wid, gpointer data)
{
	P_CmdSeq	*page = (P_CmdSeq *) data;

	if(page != NULL && page->curr_seq != NULL)
	{
		g_hash_table_remove(page->cmdseq, page->curr_seq->name);
		csq_cmdseq_destroy(page->curr_seq);
		repopulate_clist(page);
		reset_widgets(page);
	}
	return TRUE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-26 -	Build the (currently very pointless) extra controls for builtin rows. */
static void build_px_builtin(P_CmdSeq *page, PX_Builtin *pxb)
{
	pxb->vbox = gtk_vbox_new(FALSE, 0);
	pxb->label = gtk_label_new("(No Options Available)");
	gtk_box_pack_start(GTK_BOX(pxb->vbox), pxb->label, TRUE, TRUE, 0);
	gtk_widget_show(pxb->label);
}

/* 1998-09-27 -	One of the general flag check buttons was clicked. */
static gint evt_gf_clicked(GtkWidget *wid, gpointer data)
{
	P_CmdSeq	*page = (P_CmdSeq *) data;
	guint32		flags = (guint32) gtk_object_get_user_data(GTK_OBJECT(wid));

	if(page != NULL && page->curr_seq != NULL && page->curr_row != NULL && page->curr_row->type == CRTP_EXTERNAL)
	{
		page->modified = TRUE;
		if(GTK_TOGGLE_BUTTON(wid)->active)
			page->curr_row->extra.external.gflags |= flags;
		else
			page->curr_row->extra.external.gflags &= ~flags;
		gtk_widget_set_sensitive(page->px_external.gflags.killprev, page->curr_row->extra.external.gflags & CGF_RUNINBG);
		gtk_widget_set_sensitive(page->px_external.gflags.survive, page->curr_row->extra.external.gflags & CGF_RUNINBG);
		set_repeat(page);
	}

	return TRUE;
}

/* 1998-09-26 -	Build the page for external commands that deals with general flags. */
static void build_pxe_general(P_CmdSeq *page, PXE_General *pxg)
{
	GtkWidget	*hbox;

	pxg->vbox = gtk_vbox_new(FALSE, 0);

	hbox = gtk_hbox_new(FALSE, 0);
	pxg->runbg = gtk_check_button_new_with_label("Run in Background?");
	gtk_object_set_user_data(GTK_OBJECT(pxg->runbg), GINT_TO_POINTER(CGF_RUNINBG));
	gtk_signal_connect(GTK_OBJECT(pxg->runbg), "clicked", GTK_SIGNAL_FUNC(evt_gf_clicked), page);
	gtk_box_pack_start(GTK_BOX(hbox), pxg->runbg, FALSE, FALSE, 0);
	gtk_widget_show(pxg->runbg);
	pxg->killprev = gtk_check_button_new_with_label("Kill Previous Instance?");
	gtk_object_set_user_data(GTK_OBJECT(pxg->killprev), GINT_TO_POINTER(CGF_KILLPREV));
	gtk_signal_connect(GTK_OBJECT(pxg->killprev), "clicked", GTK_SIGNAL_FUNC(evt_gf_clicked), page);
	gtk_box_pack_start(GTK_BOX(hbox), pxg->killprev, FALSE, FALSE, 0);
	gtk_widget_show(pxg->killprev);
	pxg->survive = gtk_check_button_new_with_label("Survive Quit?");
	gtk_object_set_user_data(GTK_OBJECT(pxg->survive), GINT_TO_POINTER(CGF_SURVIVE));
	gtk_signal_connect(GTK_OBJECT(pxg->survive), "clicked", GTK_SIGNAL_FUNC(evt_gf_clicked), page);
	gtk_box_pack_start(GTK_BOX(hbox), pxg->survive, FALSE, FALSE, 0);
	gtk_widget_show(pxg->survive);
	gtk_box_pack_start(GTK_BOX(pxg->vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);

	pxg->graboutput = gtk_check_button_new_with_label("Capture Output?");
	gtk_object_set_user_data(GTK_OBJECT(pxg->graboutput), GINT_TO_POINTER(CGF_GRABOUTPUT));
	gtk_signal_connect(GTK_OBJECT(pxg->graboutput), "clicked", GTK_SIGNAL_FUNC(evt_gf_clicked), page);
	gtk_box_pack_start(GTK_BOX(pxg->vbox), pxg->graboutput, FALSE, FALSE, 0);
	gtk_widget_show(pxg->graboutput);
}

/* 1999-03-09 -	One of the CD check buttons was clicked. Do the pseudo-radio logic, and alter the
**		flag setting of the current command row. Note that this is kind'a sneakily written
**		(some would say ugly (uglily?)), since it actually relies on being called twice for
**		a single click in some cases (since it modifies the "other" widget, thus causing an
**		event).
*/
static gint evt_baf_cd_clicked(GtkWidget *wid, gpointer user)
{
	P_CmdSeq	*page = user;

	if((page != NULL) && (page->curr_seq != NULL) && (page->curr_row != NULL))
	{
		GtkWidget	*other;
		guint32		mask;

		/* Figure out if we're in source or dest (same handler). */
		if(gtk_object_get_data(GTK_OBJECT(wid), "destination"))
		{
			other = page->px_external.baf.cdsrc;
			mask  = CBAF_CD_DEST;
		}
		else
		{
			other = page->px_external.baf.cddst;
			mask  = CBAF_CD_SOURCE;
		}
		if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid)))
		{
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(other), FALSE);
			page->curr_row->extra.external.baflags[0] |= mask;
		}
		else
			page->curr_row->extra.external.baflags[0] &= ~mask;
	}
	return TRUE;
}

/* 1999-03-09 -	Handle click on one of the rescanning check buttons. */
static gint evt_baf_rescan_clicked(GtkWidget *wid, gpointer user)
{
	P_CmdSeq	*page = user;

	if((page != NULL) && (page->curr_seq != NULL) && (page->curr_row != NULL))
	{
		guint32	mask = GPOINTER_TO_INT(gtk_object_get_user_data(GTK_OBJECT(wid)));

		if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid)))
			page->curr_row->extra.external.baflags[1] |= mask;
		else
			page->curr_row->extra.external.baflags[1] &= ~mask;
	}
	return TRUE;
}

/* 1999-03-09 -	Build new simplified widgetry for editing before and after flags. Note that the actual
**		flags have not changed, they're still muy redundant-capable. It's just the GUI that's
**		become a bit simpler to look at. At less vertically tall.
*/
static void build_px_baf(P_CmdSeq *page, PXE_BAF *pxbaf)
{
	GtkWidget	*vbox;

	pxbaf->hbox = gtk_hbox_new(FALSE, 0);

	pxbaf->bframe = gtk_frame_new("Before Execution");
	vbox = gtk_vbox_new(FALSE, 0);
	pxbaf->cdsrc = gtk_check_button_new_with_label("CD Source?");
	gtk_signal_connect(GTK_OBJECT(pxbaf->cdsrc), "clicked", GTK_SIGNAL_FUNC(evt_baf_cd_clicked), page);
	gtk_box_pack_start(GTK_BOX(vbox), pxbaf->cdsrc, TRUE, TRUE, 0);
	gtk_widget_show(pxbaf->cdsrc);
	pxbaf->cddst = gtk_check_button_new_with_label("CD Destination?");
	gtk_object_set_data(GTK_OBJECT(pxbaf->cddst), "destination", GINT_TO_POINTER(1));	/* Just a silly flag. */
	gtk_signal_connect(GTK_OBJECT(pxbaf->cddst), "clicked", GTK_SIGNAL_FUNC(evt_baf_cd_clicked), page);
	gtk_box_pack_start(GTK_BOX(vbox), pxbaf->cddst, TRUE, TRUE, 0);
	gtk_widget_show(pxbaf->cddst);
	gtk_container_add(GTK_CONTAINER(pxbaf->bframe), vbox);
	gtk_widget_show(vbox);
	gtk_box_pack_start(GTK_BOX(pxbaf->hbox), pxbaf->bframe, TRUE, TRUE, 0);
	gtk_widget_show(pxbaf->bframe);

	pxbaf->aframe = gtk_frame_new("After Execution");
	vbox = gtk_vbox_new(FALSE, 0);
	pxbaf->rssrc = gtk_check_button_new_with_label("Rescan Source?");
	gtk_object_set_user_data(GTK_OBJECT(pxbaf->rssrc), GINT_TO_POINTER(CBAF_RESCAN_SOURCE));
	gtk_signal_connect(GTK_OBJECT(pxbaf->rssrc), "clicked", GTK_SIGNAL_FUNC(evt_baf_rescan_clicked), page);
	gtk_box_pack_start(GTK_BOX(vbox), pxbaf->rssrc, TRUE, TRUE, 0);
	gtk_widget_show(pxbaf->rssrc);
	pxbaf->rsdst = gtk_check_button_new_with_label("Rescan Destination?");
	gtk_object_set_user_data(GTK_OBJECT(pxbaf->rsdst), GINT_TO_POINTER(CBAF_RESCAN_DEST));
	gtk_signal_connect(GTK_OBJECT(pxbaf->rsdst), "clicked", GTK_SIGNAL_FUNC(evt_baf_rescan_clicked), page);
	gtk_box_pack_start(GTK_BOX(vbox), pxbaf->rsdst, TRUE, TRUE, 0);
	gtk_widget_show(pxbaf->rsdst);
	gtk_container_add(GTK_CONTAINER(pxbaf->aframe), vbox);
	gtk_widget_show(vbox);
	gtk_box_pack_start(GTK_BOX(pxbaf->hbox), pxbaf->aframe, TRUE, TRUE, 0);
	gtk_widget_show(pxbaf->aframe);
}

/* 1998-09-26 -	Build the extra configuration widgetry for external commands. Plenty of stuff. */
static void build_px_external(P_CmdSeq *page, PX_External *pxe)
{
	pxe->nbook = gtk_notebook_new();
	gtk_notebook_set_tab_pos(GTK_NOTEBOOK(pxe->nbook), GTK_POS_LEFT);
	build_pxe_general(page, &pxe->gflags);
	gtk_notebook_append_page(GTK_NOTEBOOK(pxe->nbook), pxe->gflags.vbox, gtk_label_new("General"));
	gtk_widget_show(pxe->gflags.vbox);

	build_px_baf(page, &pxe->baf);
	gtk_notebook_append_page(GTK_NOTEBOOK(pxe->nbook), pxe->baf.hbox, gtk_label_new("Before&After"));
	gtk_widget_show(pxe->baf.hbox);
}

static GtkWidget * ccs_init(MainInfo *min, char **name)
{
	const gchar	*tlab[] = { "Built-In", "External", NULL },
			*dblab[] = { "Add Row","Duplicate", "(Up)", "(Down)", "Delete Row" };
	GtkSignalFunc	dbfunc[] = { GTK_SIGNAL_FUNC(evt_addrow_clicked), GTK_SIGNAL_FUNC(evt_duprow_clicked),
				     GTK_SIGNAL_FUNC(evt_uprow_clicked),  GTK_SIGNAL_FUNC(evt_downrow_clicked),
				     GTK_SIGNAL_FUNC(evt_delrow_clicked) };
	guint		i;
	GtkWidget	*label, *vbox, *hbox, *vbox2, *sep;
	P_CmdSeq	*page = &the_page;

	if(name == NULL)
		return NULL;

	*name = "Commands";

	page->vbox = gtk_vbox_new(FALSE, 0);

	page->scwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(page->scwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
	page->clist = gtk_clist_new(1);
	gtk_object_set_user_data(GTK_OBJECT(page->clist), (gpointer) page);
	gtk_signal_connect(GTK_OBJECT(page->clist), "select_row", GTK_SIGNAL_FUNC(evt_cmdseq_selected), (gpointer) page);
	gtk_signal_connect(GTK_OBJECT(page->clist), "unselect_row", GTK_SIGNAL_FUNC(evt_cmdseq_unselected), (gpointer) page);
	gtk_container_add(GTK_CONTAINER(page->scwin), page->clist);
	gtk_widget_show(page->clist);
	gtk_box_pack_start(GTK_BOX(page->vbox), page->scwin, TRUE, TRUE, 0);
	gtk_widget_show(page->scwin);

	page->nhbox = gtk_hbox_new(FALSE, 0);
	label = gtk_label_new("Name");
	gtk_box_pack_start(GTK_BOX(page->nhbox), label, FALSE, FALSE, 0);
	gtk_widget_show(label);
	page->name = gtk_entry_new_with_max_length(CSQ_NAME_MAX - 1);
	gtk_signal_connect(GTK_OBJECT(page->name), "changed", GTK_SIGNAL_FUNC(evt_name_changed), (gpointer) page);
	gtk_box_pack_start(GTK_BOX(page->nhbox), page->name, TRUE, TRUE, 0);
	gtk_widget_show(page->name);
	gtk_box_pack_start(GTK_BOX(page->vbox), page->nhbox, FALSE, FALSE, 0);
	gtk_widget_show(page->nhbox);

	page->dframe = gtk_frame_new("Definition");
	vbox = gtk_vbox_new(FALSE, 0);

	hbox = gtk_hbox_new(FALSE, 0);
	vbox2 = gtk_vbox_new(FALSE, 0);
	for(i = 0; i < sizeof page->dbtn / sizeof page->dbtn[0]; i++)
	{
		if(dblab[i][0] == '(')	/* Um, this is a rather gross hack. */
		{
			GtkWidget	*hbox;

			hbox = gtk_hbox_new(FALSE, 0);
			for(; dblab[i][0] == '('; i++)
			{
				page->dbtn[i] = gui_arrow_button_new(dblab[i][1] == 'U' ? GTK_ARROW_UP : GTK_ARROW_DOWN);
				gtk_signal_connect(GTK_OBJECT(page->dbtn[i]), "clicked", dbfunc[i], (gpointer) page);
				gtk_box_pack_start(GTK_BOX(hbox), page->dbtn[i], TRUE, TRUE, 0);
				gtk_widget_show(page->dbtn[i]);
			}
			gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, FALSE, 0);
			gtk_widget_show(hbox);
			i--;		/* Compensate for outermost for loop. Told you it was gross... */
		}
		else
		{
			page->dbtn[i] = gtk_button_new_with_label(dblab[i]);
			gtk_signal_connect(GTK_OBJECT(page->dbtn[i]), "clicked", dbfunc[i], (gpointer) page);
			gtk_box_pack_start(GTK_BOX(vbox2), page->dbtn[i], FALSE, FALSE, 0);
			gtk_widget_show(page->dbtn[i]);
		}
	}
	gtk_box_pack_start(GTK_BOX(hbox), vbox2, FALSE, FALSE, 0);
	gtk_widget_show(vbox2);

	page->dscwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(page->dscwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
	page->dclist = gtk_clist_new(2);
	gtk_clist_set_column_width(GTK_CLIST(page->dclist), 0, 56);
	gtk_object_set_user_data(GTK_OBJECT(page->dclist), (gpointer) page);
	gtk_signal_connect(GTK_OBJECT(page->dclist), "select_row", GTK_SIGNAL_FUNC(evt_row_selected), (gpointer) page);
	gtk_signal_connect(GTK_OBJECT(page->dclist), "unselect_row", GTK_SIGNAL_FUNC(evt_row_unselected), (gpointer) page);
	gtk_container_add(GTK_CONTAINER(page->dscwin), page->dclist);
	gtk_widget_show(page->dclist);
	gtk_box_pack_start(GTK_BOX(hbox), page->dscwin, TRUE, TRUE, 0);
	gtk_widget_show(page->dscwin);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
	gtk_widget_show(hbox);

	page->dhbox = gtk_hbox_new(FALSE, 0);
	page->dtmenu = gui_build_menu(tlab, GTK_SIGNAL_FUNC(evt_type_selected), (gpointer) page);
	page->dtype = gtk_option_menu_new();
	gtk_option_menu_set_menu(GTK_OPTION_MENU(page->dtype), page->dtmenu);
	gtk_box_pack_start(GTK_BOX(page->dhbox), page->dtype, FALSE, FALSE, 0);
	gtk_widget_show(page->dtype);
	page->ddef = gtk_entry_new();
	gtk_signal_connect(GTK_OBJECT(page->ddef), "changed", GTK_SIGNAL_FUNC(evt_def_changed), (gpointer) page);
	gtk_box_pack_start(GTK_BOX(page->dhbox), page->ddef, TRUE, TRUE, 0);
	gtk_widget_show(page->ddef);
	page->dpick = gui_details_button_new(min->gui->window->window);
	gtk_signal_connect(GTK_OBJECT(page->dpick), "clicked", GTK_SIGNAL_FUNC(evt_pick_clicked), (gpointer) page);
	gtk_box_pack_start(GTK_BOX(page->dhbox), page->dpick, FALSE, FALSE, 0);
	gtk_widget_show(page->dpick);	
	gtk_box_pack_start(GTK_BOX(vbox), page->dhbox, FALSE, FALSE, 0);
	gtk_widget_show(page->dhbox);

	page->dextra = gtk_notebook_new();
	gtk_notebook_set_show_tabs(GTK_NOTEBOOK(page->dextra), FALSE);
	gtk_notebook_set_show_border(GTK_NOTEBOOK(page->dextra), FALSE);

	build_px_builtin(page, &page->px_builtin);
	gtk_notebook_append_page(GTK_NOTEBOOK(page->dextra), page->px_builtin.vbox, NULL);
	gtk_widget_show(page->px_builtin.vbox);

	build_px_external(page, &page->px_external);	
	gtk_notebook_append_page(GTK_NOTEBOOK(page->dextra), page->px_external.nbook, NULL);
	gtk_widget_show(page->px_external.nbook);
	gtk_box_pack_start(GTK_BOX(vbox), page->dextra, FALSE, FALSE, 0);
	gtk_widget_show(page->dextra);

	sep = gtk_hseparator_new();
	gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 5);
	gtk_widget_show(sep);
	page->drepeat = gtk_check_button_new_with_label("Repeat Sequence Until No Source Selection?");
	gtk_signal_connect(GTK_OBJECT(page->drepeat), "clicked", GTK_SIGNAL_FUNC(evt_repeat_clicked), page);
	gtk_box_pack_start(GTK_BOX(vbox), page->drepeat, FALSE, FALSE, 0);
	gtk_widget_show(page->drepeat);

	gtk_container_add(GTK_CONTAINER(page->dframe), vbox);
	gtk_widget_show(vbox);
	gtk_box_pack_start(GTK_BOX(page->vbox), page->dframe, FALSE, FALSE, 0);
	gtk_widget_show(page->dframe);

	page->bhbox = gtk_hbox_new(FALSE, 0);
	page->badd = gtk_button_new_with_label("Add");
	gtk_signal_connect(GTK_OBJECT(page->badd), "clicked", GTK_SIGNAL_FUNC(evt_add_clicked), (gpointer) page);
	gtk_box_pack_start(GTK_BOX(page->bhbox), page->badd, TRUE, TRUE, 5);
	gtk_widget_show(page->badd);
	page->bdel = gtk_button_new_with_label("Delete");
	gtk_signal_connect(GTK_OBJECT(page->bdel), "clicked", GTK_SIGNAL_FUNC(evt_del_clicked), (gpointer) page);
	gtk_box_pack_start(GTK_BOX(page->bhbox), page->bdel, TRUE, TRUE, 5);
	gtk_widget_show(page->bdel);
	gtk_box_pack_start(GTK_BOX(page->vbox), page->bhbox, FALSE, FALSE, 5);
	gtk_widget_show(page->bhbox);

	gtk_widget_show(page->vbox);

	return page->vbox;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-26 -	Update command sequence page. */
static void ccs_update(MainInfo *min)
{
	the_page.min = min;

	populate_clist(min, &the_page);
	reset_widgets(&the_page);
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-27 -	Just a simple g_hash_table_foreach() callback. */
static void destroy_cmdseq(gpointer key, gpointer data, gpointer user)
{
	csq_cmdseq_destroy((CmdSeq *) data);
}

/* 1998-09-27 -	Accept any changes, and put them in the "real" config. */
static void ccs_accept(MainInfo *min)
{
	P_CmdSeq	*page = &the_page;

	if(page->modified)
	{
		if(min->cfg.commands.cmdseq != NULL)
		{
			g_hash_table_foreach(min->cfg.commands.cmdseq, destroy_cmdseq, NULL);
			g_hash_table_destroy(min->cfg.commands.cmdseq);
		}
		min->cfg.commands.cmdseq = page->cmdseq;
		page->cmdseq = NULL;
		page->modified = FALSE;
	}
}

/* ----------------------------------------------------------------------------------------- */

static void save_external(CX_Ext *cext, FILE *out)
{
	xml_put_node_open(out, "CX_External");
	xml_put_integer(out, "gflags", cext->gflags);
	xml_put_integer(out, "bflags", cext->baflags[0]);
	xml_put_integer(out, "aflags", cext->baflags[1]);
	xml_put_node_close(out, "CX_External");
}

/* 1998-09-27 -	Save a command row. */
static void save_cmdrow(CmdRow *row, FILE *out)
{
	xml_put_node_open(out, "CmdRow");
	xml_put_text(out, "type", csq_cmdrow_type_to_string(row->type));
	xml_put_text(out, "def", row->def->str);
	xml_put_integer(out, "flags", row->flags);
	switch(row->type)
	{
		case CRTP_BUILTIN:
			break;
		case CRTP_EXTERNAL:
			save_external(&row->extra.external, out);
			break;
		default:
			break;
	}
	xml_put_node_close(out, "CmdRow");
}

/* 1998-09-27 -	Save out a single command sequence. */
static void save_cmdseq(gpointer key, gpointer data, gpointer user)
{
	CmdSeq	*cs = (CmdSeq *) data;
	FILE	*out = (FILE *) user;
	GList	*iter;

	xml_put_node_open(out, "CmdSeq");
	xml_put_text(out, "name", cs->name);
	xml_put_integer(out, "flags", cs->flags);
	if(cs->rows != NULL)			/* Any rows in this sequence? */
	{
		xml_put_node_open(out, "CmdRows");
		for(iter = cs->rows; iter != NULL; iter = g_list_next(iter))
			save_cmdrow((CmdRow *) iter->data, out);
		xml_put_node_close(out, "CmdRows");
	}
	xml_put_node_close(out, "CmdSeq");
}

/* 1998-09-28 -	Save the command sequence config data right out of min->cfg. */
static gint ccs_save(MainInfo *min, FILE *out)
{
	xml_put_node_open(out, NODE);
	g_hash_table_foreach(min->cfg.commands.cmdseq, save_cmdseq, (gpointer) out);
	xml_put_node_close(out, NODE);
	return TRUE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-27 -	Load extra data for external command. */
static void load_external(CX_Ext *cext, XmlNode *node)
{
	xml_get_integer(node, "gflags", (gint *) &cext->gflags);
	xml_get_integer(node, "bflags", (gint *) &cext->baflags[0]);
	xml_get_integer(node, "aflags", (gint *) &cext->baflags[1]);
}

/* 1998-09-27 -	Load a single command sequence row. */
static void load_cmdrow(XmlNode *node, gpointer user)
{
	CmdSeq	*cs = (CmdSeq *) user;
	CmdRow	*row;
	XmlNode	*data;
	CRType	type = CRTP_BUILTIN;
	gchar	*def = NULL, *tname = NULL;
	guint32	flags = 0UL;

	if(xml_get_text(node, "type", &tname))
		type = csq_cmdrow_string_to_type(tname);
	xml_get_text(node, "def", &def);
	xml_get_integer(node, "flags", (gint *) flags);

	if((row = csq_cmdrow_new(type, def, flags)) != NULL)
	{
		if((row->type == CRTP_EXTERNAL) && (data = xml_tree_search(node, "CX_External")) != NULL)
			load_external(&row->extra.external, data);
		csq_cmdseq_row_append(cs, row);
	}
}

/* 1998-09-27 -	Trampoline. */
static void load_cmdseq_rows(XmlNode *node, CmdSeq *cs)
{
	xml_node_visit_children(node, load_cmdrow, (gpointer) cs);
}

/* 1998-09-27 -	Load a command sequence. */
static void load_cmdseq(XmlNode *node, gpointer user)
{
	MainInfo	*min = (MainInfo *) user;
	char		*name = "Unknown";
	guint32		flags = 0UL;
	CmdSeq		*cs;
	XmlNode		*data;

	xml_get_text(node, "name", &name);
	xml_get_integer(node, "flags", (gint *) &flags);
	if((cs = csq_cmdseq_new(name, flags)) != NULL)
	{
		if((data = xml_tree_search(node, "CmdRows")) != NULL)
			load_cmdseq_rows(data, cs);
		csq_cmdseq_hash(&min->cfg.commands.cmdseq, cs);
	}
}

/* 1998-09-27 -	Load a tree full of command sequence data. */
static void ccs_load(MainInfo *min, XmlNode *node)
{
	/* First destroy any existing commands. */
	if(min->cfg.commands.cmdseq != NULL)
	{
		g_hash_table_foreach(min->cfg.commands.cmdseq, destroy_cmdseq, NULL);
		g_hash_table_destroy(min->cfg.commands.cmdseq);
	}
	min->cfg.commands.cmdseq = NULL;
	xml_node_visit_children(node, load_cmdseq, (gpointer) min);
}

/* ----------------------------------------------------------------------------------------- */

CfgPage * ccs_describe(MainInfo *min)
{
	static CfgPage	desc = { NODE, ccs_init, ccs_update, ccs_accept, ccs_save, ccs_load, NULL };

	return &desc;
}

/* 1998-09-28 -	This returns a pointer to the current set of command sequnces hash. Completely
**		lethal if used in the wrong way, but very useful when selecting cmdseqs in
**		the config.
*/
GHashTable * ccs_get_current(void)
{
	return the_page.cmdseq;
}
