/*
** 1998-06-22 -	Redesigned the configuration. Now each page gets its own module, and this module also
**		contains the necessary data to add the page to the config-notebook. Much cleaner and
**		easier to extend, IMO.
** 1998-06-24 -	Implemented the rest of the GUI. This module is now the single biggest in the project!
**		GUI code has a way to grow unbounded, and I don't think gtk is any different from other
**		toolkits/APIs I've seen in, that respect. The results are, though.
** 1998-06-25 -	Um... I forgot about sorting, and it also occured to me that having a default path would
**		be useful. So I added those.
** 1998-06-30 -	Added a check button for selecting how sorting should treat case
** 1998-08-10 -	Finally added some numeric feedback for the column width config.
** 1998-08-13 -	Replaced button click signal with row selection for the 'sclist', made things a lot simpler.
**		Now changes sensitivity of edit/remove/up/down buttons, too.
** 1999-03-05 -	Added the control mode setting, for the new selection design.
** 1999-05-15 -	Rewrote large parts of this file in order to make the two panes have separate GUIs (notebook
**		pages), and make room for some color config. Also moved code out into the dpformat module.
*/

#include "gentoo.h"

#include <stdlib.h>

#include "widgets/odemilbutton.h"
#include "dirpane.h"
#include "guiutil.h"
#include "xmlutil.h"
#include "cmdseq.h"
#include "strutil.h"
#include "dpformat.h"
#include "color_dialog.h"

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

#include "cfg_dirpane.h"

#define	NODE	"DirPanes"

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

typedef struct _P_DirPane	P_DirPane;

typedef struct {			/* Pane definition widgets. */
	GtkWidget	*vbox;		/* Vbox for entire page. */

	GtkWidget	*aclist;	/* List showing available content types. */

	GtkWidget	*sclist;	/* List showing selected content. */
	GtkWidget	*sbutton[4];	/* The fun buttons themselves. */

	GtkWidget	*scmenu;	/* Column option menu. */
	GtkWidget	*smmenu;	/* Sorting mode option menu. */
	GtkWidget	*sinvert;	/* Check button for inverse sorting. */
	GtkWidget	*snocase;	/* Check button for case ignorance. */

	GtkWidget	*dentry;	/* An entry for the default directory. */

	GtkWidget	*fabove;	/* Check button to put path above pane. */
	GtkWidget	*fhide;		/* Check button to allow hiding. */
	GtkWidget	*fscroll;	/* Check button for scrollbar always. */

	GSList		*splist;
	GtkWidget	*spos[3];	/* Radio buttons for scrollbar position. */

	GSList		*mclist;
	GtkWidget	*mctrl[4];	/* Radio buttons for 'control' key mode. Only three are */
					/* visible ("clear" is kinda pointless, but there for completeness). */
	P_DirPane	*page;
	DPFormat	edit;
	guint		index;
	gint		aclist_row,	/* The last and previous (to last) rows that were clicked in aclist. */
			aclist_row2;
	gint		sclist_row;	/* Last clicked row in selected content list. */
} PDef;

typedef struct {
	GtkWidget	*vbox;

	GtkWidget	*widget[DPCOL_NUM];

	DPColors	edit;
	DpCol		index;		/* This is just used when editing, to record which color is being edited. */
} PColors;

struct _P_DirPane {		/* Information this module likes to have around. */
	GtkWidget	*vbox;		/* Root vbox, holds rest of GUI. Returned. */
	GtkWidget	*high_hbox;	/* Upper hbox, holds mostly buttons. */

	GtkWidget	*notebook;	/* A notebook holding left, right and color pages. */

	PDef		pane[2];
	PColors		colors;

	MainInfo	*min;
};

typedef struct {
	GtkWidget	*show_type;		/* A checkbox. */
	GtkWidget	*show_linkname;		/* Another checkbox. */
} EE_Name;

typedef struct {
	GtkWidget	*format;		/* An entry. */
	GtkWidget	*dir_show_fs_size;	/* A checkbox. */
} EE_Size;

typedef struct {
	GtkWidget	*dir_show_fs_size;	/* A checkbox. */
} EE_IQSize;

typedef struct {
	GtkWidget	*format;		/* An entry. */
} EE_Format;

typedef struct {
	GtkWidget	*format;		/* An entry. */
} EE_Date;

typedef enum { ET_NONE = 0, ET_NAME, ET_SIZE, ET_IQSIZE, ET_FORMAT, ET_DATE } ExtraType;

typedef struct {		/* Holds data used when editing a column format. */
	PDef		*def;
	DPContent	content;	/* Type of content we're editing. */
	GtkWidget	*dlg, *vbox;	/* Dialog and the primary vbox inside it. */
	GtkWidget	*title;		/* Entry widget holding the wanted title. */
	GtkWidget	*just;		/* Justification option menu. */
	gint		curr_just;	/* Current justification (0=left, 1=right, 2=center). */
	GtkObject	*wadj;		/* An adjustment for the width. */
	GtkWidget	*width;		/* A spin button for the width. */
	union {
	EE_Name		name;
	EE_Size		size;		/* Size has its own formatting. */
	EE_IQSize	iqsize;
	EE_Format	format;		/* General formatting, used by protection, IDs, etc. */
	EE_Date		date;
	} extra;
	ExtraType	extype;
} P_Edit;

static P_DirPane	the_page;	/* There can be only one. */

static char	*config_name[] = {"DirPaneLeft", "DirPaneRight"},
		*mode_name[] = {"dirs_first", "dirs_last", "dirs_mixed"},
		*sbarpos_name[] = {"system", "left", "right" },
		*color_name[] = { "pane_selected", "focus_unselected", "focus_selected" };

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

static void	evt_sort_set_column(GtkWidget *wid, gpointer user);

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

/* 1999-05-15 -	Set the state of the various widgets of <def> according to its 'edit' field. */
static void set_pane_widgets(PDef *def)
{
	const gchar	*row[2], *mtxt[DPC_NUM_TYPES + 1];
	GtkWidget	*menu;
	guint		i;

	gtk_clist_freeze(GTK_CLIST(def->sclist));
	gtk_clist_clear(GTK_CLIST(def->sclist));
	for(i = 0; i < def->edit.num_columns; i++)
	{
		row[0] = def->edit.format[i].title;
		row[1] = dpf_get_content_name(def->edit.format[i].content);
		gtk_clist_append(GTK_CLIST(def->sclist), (gchar **) row);
	}
	gtk_clist_thaw(GTK_CLIST(def->sclist));
	for(i = 0; i < sizeof def->sbutton / sizeof def->sbutton[0]; i++)
		gtk_widget_set_sensitive(def->sbutton[i], FALSE);

	gtk_option_menu_remove_menu(GTK_OPTION_MENU(def->scmenu));
	for(i = 0; i < def->edit.num_columns; i++)
		mtxt[i] = def->edit.format[i].title;
	mtxt[i] = NULL;
	menu = gui_build_menu(mtxt, evt_sort_set_column, def);
	gtk_option_menu_set_menu(GTK_OPTION_MENU(def->scmenu), menu);
	gtk_option_menu_set_history(GTK_OPTION_MENU(def->scmenu), def->edit.sort.column);
	gtk_option_menu_set_history(GTK_OPTION_MENU(def->smmenu), (guint) def->edit.sort.mode);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(def->sinvert), def->edit.sort.invert);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(def->snocase), def->edit.sort.nocase);

	gtk_entry_set_text(GTK_ENTRY(def->dentry), def->edit.def_path);

	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(def->fabove), def->edit.path_above);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(def->fhide),  def->edit.hide_allowed);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(def->fscroll), def->edit.scrollbar_always);

	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(def->spos[(guint) def->edit.sbar_pos]), TRUE);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(def->mctrl[(guint) def->edit.ctrl_mode]), TRUE);

	def->aclist_row = def->aclist_row2 = -1;
	def->sclist_row = -1;
}

/* 1999-05-16 -	Set state of color editing widgets to reflect current data. */
static void set_color_widgets(PColors *col)
{
	guint	i;

	for(i = 0; i < sizeof col->widget / sizeof col->widget[0]; i++)
		od_emil_button_set_colors(OD_EMIL_BUTTON(col->widget[i]), OD_EMIL_BUTTON_PRIMARY, NULL, &col->edit.color[i]);
}

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

/* 1999-05-15 -	Add a column showing the indicated content to the selected list of <def>. */
static void add_column(PDef *def, guint content)
{
	if(def->edit.num_columns < DP_COLUMNS_MAX)		/* Any room for another column? */
	{
		dpf_set_default_format(&def->edit.format[def->edit.num_columns], (DPContent) content);
		def->edit.num_columns++;
		set_pane_widgets(def);
		gtk_clist_select_row(GTK_CLIST(def->sclist), def->edit.num_columns - 1, 0);
		gtk_clist_moveto(GTK_CLIST(def->sclist), def->edit.num_columns - 1, -1, 1.0, 0.0);
	}
}

/* 1999-05-15 -	User clicked a row in the available clist (leftmost). If double-click, add
**		row to the selected (rightmost) list.
*/
static void evt_aclist_button_press(GtkWidget *wid, GdkEventButton *evt, gpointer user)
{
	PDef	*def = user;
	gint	row;

	if(gtk_clist_get_selection_info(GTK_CLIST(wid), (gint) evt->x, (gint) evt->y, &row, NULL))
	{
		if((row == def->aclist_row2) && (evt->type == GDK_2BUTTON_PRESS))
			add_column(def, row);
		def->aclist_row2 = def->aclist_row;
		def->aclist_row = row;
	}
}

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

/* 1998-06-24 -	Build the widgets needed to get a content format string. This consists of
**		a table with a label showing <ltext> and an entry widget containing
**		<dtext> initially. The created entry widget is stored at <wid>, and the
**		entire shabang is packed into the <vbox> given.
*/
static void build_content_format(GtkWidget *vbox, GtkWidget **wid, const gchar *ltext, const gchar *dtext)
{
	GtkWidget	*table, *label;

	table = gtk_table_new(1, 2, FALSE);
	label = gtk_label_new(ltext);
	*wid = gtk_entry_new_with_max_length(DP_DATEFMT_MAX - 1);
	gtk_entry_set_text(GTK_ENTRY(*wid), dtext);
	gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
	gtk_table_attach_defaults(GTK_TABLE(table), *wid,  1, 2, 0, 1);
	gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 0);
	gtk_widget_show(label);
	gtk_widget_show(*wid);
	gtk_widget_show(table);
}

/* 1998-06-23 -	Add widgetry to dialog being defined, if any is needed. */
static GtkWidget * add_content_widgets(P_Edit *edit)
{
	gchar		*tmp = "";
	DpCFmt		*fmt = &edit->def->edit.format[edit->def->sclist_row];
	GtkWidget	*vbox, *frame, *table, *label;
	ExtraType	type = ET_NONE;

	vbox = gtk_vbox_new(FALSE, 0);
	if(vbox == NULL)
		return NULL;

	switch(edit->content)
	{
		case DPC_NAME:
			edit->extra.name.show_type = gtk_check_button_new_with_label("Append Type Character?");
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(edit->extra.name.show_type), fmt->extra.name.show_type);
			gtk_box_pack_start(GTK_BOX(vbox), edit->extra.name.show_type, TRUE, TRUE, 0);
			gtk_widget_show(edit->extra.name.show_type);
			edit->extra.name.show_linkname = gtk_check_button_new_with_label("Append \"-> destination\" on Links?");
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(edit->extra.name.show_linkname), fmt->extra.name.show_linkname);
			gtk_box_pack_start(GTK_BOX(vbox), edit->extra.name.show_linkname, TRUE, TRUE, 0);
			gtk_widget_show(edit->extra.name.show_linkname);
			type = ET_NAME;
			break;
		case DPC_SIZE:
			edit->extra.size.format = gtk_entry_new_with_max_length(DP_FORMAT_MAX - 1);
			gtk_entry_set_text(GTK_ENTRY(edit->extra.size.format), fmt->extra.size.format);
			gtk_box_pack_start(GTK_BOX(vbox), edit->extra.size.format, TRUE, TRUE, 0);
			gtk_widget_show(edit->extra.size.format);
			edit->extra.size.dir_show_fs_size = gtk_check_button_new_with_label("Show Dir's File System Size?");
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(edit->extra.size.dir_show_fs_size), fmt->extra.size.dir_show_fs_size);
			gtk_box_pack_start(GTK_BOX(vbox), edit->extra.size.dir_show_fs_size, TRUE, TRUE, 0);
			gtk_widget_show(edit->extra.size.dir_show_fs_size);
			type = ET_FORMAT;
			break;
		case DPC_BLOCKS:
			build_content_format(vbox, &edit->extra.format.format, "Format", fmt->extra.blocks.format);
			type = ET_FORMAT;
			break;
		case DPC_IQSIZE:
			edit->extra.iqsize.dir_show_fs_size = gtk_check_button_new_with_label("Show Dir's File System Size?");
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(edit->extra.iqsize.dir_show_fs_size), fmt->extra.iqsize.dir_show_fs_size);
			gtk_box_pack_start(GTK_BOX(vbox), edit->extra.iqsize.dir_show_fs_size, TRUE, TRUE, 0);
			gtk_widget_show(edit->extra.iqsize.dir_show_fs_size);
			type = ET_IQSIZE;
			break;
		case DPC_MODENUM:
			build_content_format(vbox, &edit->extra.format.format, "Format", fmt->extra.mode.format);
			type = ET_FORMAT;
			break;
		case DPC_NLINK:
			build_content_format(vbox, &edit->extra.format.format, "Format", fmt->extra.nlink.format);
			type = ET_FORMAT;
			break;
		case DPC_UIDNUM:
			build_content_format(vbox, &edit->extra.format.format, "Format", fmt->extra.uidnum.format);
			type = ET_FORMAT;
			break;
		case DPC_GIDNUM:
			build_content_format(vbox, &edit->extra.format.format, "Format", fmt->extra.gidnum.format);
			type = ET_FORMAT;
			break;
		case DPC_DEVICE:
			build_content_format(vbox, &edit->extra.format.format, "Format", fmt->extra.device.format);
			type = ET_FORMAT;
			break;
		case DPC_DEVMAJ:
			build_content_format(vbox, &edit->extra.format.format, "Format", fmt->extra.devmaj.format);
			type = ET_FORMAT;
			break;
		case DPC_DEVMIN:
			build_content_format(vbox, &edit->extra.format.format, "Format", fmt->extra.devmin.format);
			type = ET_FORMAT;
			break;
		case DPC_ATIME:
		case DPC_MTIME:
		case DPC_CTIME:
			table = gtk_table_new(1, 2, FALSE);
			label = gtk_label_new("Format");
			edit->extra.date.format = gtk_entry_new_with_max_length(DP_DATEFMT_MAX - 1);
			if(edit->content == DPC_ATIME)
				tmp = fmt->extra.a_time.format;
			else if(edit->content == DPC_MTIME)
				tmp = fmt->extra.m_time.format;
			else if(edit->content == DPC_CTIME)
				tmp = fmt->extra.c_time.format;
			gtk_entry_set_text(GTK_ENTRY(edit->extra.date.format), tmp);
			gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
			gtk_table_attach_defaults(GTK_TABLE(table), edit->extra.date.format, 1, 2, 0, 1);
			gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 0);
			gtk_widget_show(label);
			gtk_widget_show(edit->extra.date.format);
			gtk_widget_show(table);
			type = ET_DATE;
		case DPC_TYPE:
			break;
		default:
			;
	}
	edit->extype = type;
	if(type != ET_NONE)
	{
		gchar	text[64];

		g_snprintf(text, sizeof text, "%s Settings", dpf_get_content_name(edit->content));
		if((frame = gtk_frame_new(text)) != NULL)
		{
			gtk_container_set_border_width(GTK_CONTAINER(frame), 5);
			gtk_container_add(GTK_CONTAINER(frame), vbox);
			gtk_widget_show(vbox);
			return frame;
		}
	}
	gtk_widget_destroy(vbox);
	return NULL;
}

/* 1998-06-24 -	This gets called when the user selects a new justification for the
**		column being edited. We better remember exactly which mode that is.
*/
static gint evt_edit_justification(GtkWidget *wid, gpointer data)
{
	P_Edit	*edit = (P_Edit *) data;

	edit->curr_just = (gint) gtk_object_get_user_data(GTK_OBJECT(wid));

	return TRUE;
}

/* 1998-06-24 -	This gets called when the user finishes editing a column. If the button was OK,
**		we save all the changes in the correct place (in our editing copy only , of
**		course).
*/
static gint evt_edit_button(GtkWidget *wid, gpointer user)
{
	gchar		*nfmt = "";
	P_Edit		*edit = (P_Edit *) user;
	DpCFmt		*fmt = &edit->def->edit.format[edit->def->sclist_row];

	if(gtk_object_get_user_data(GTK_OBJECT(wid)) == (gpointer) 0)	/* OK button? */
	{
		strcpy(fmt->title, gtk_entry_get_text(GTK_ENTRY(edit->title)));
		fmt->just = edit->curr_just;
		fmt->width = (gint) ((GtkAdjustment *) edit->wadj)->value;
		gtk_clist_set_text(GTK_CLIST(edit->def->sclist), edit->def->sclist_row, 0, fmt->title);

		/* If the content uses the general numerical formatting entry, get its text. */
		if(edit->extype == ET_FORMAT)
			nfmt = gtk_entry_get_text(GTK_ENTRY(edit->extra.format.format));

		switch(edit->content)
		{
			case DPC_NAME:
				fmt->extra.name.show_type = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(edit->extra.name.show_type));
				fmt->extra.name.show_linkname = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(edit->extra.name.show_linkname));
				break;
			case DPC_SIZE:
				strcpy(fmt->extra.size.format, gtk_entry_get_text(GTK_ENTRY(edit->extra.size.format)));
				fmt->extra.size.dir_show_fs_size = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(edit->extra.size.dir_show_fs_size));
				break;
			case DPC_BLOCKS:
				strcpy(fmt->extra.blocks.format, nfmt);
				break;
			case DPC_IQSIZE:
				fmt->extra.iqsize.dir_show_fs_size = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(edit->extra.iqsize.dir_show_fs_size));
				break;
			case DPC_MODENUM:
				strcpy(fmt->extra.mode.format, nfmt);
				break;
			case DPC_NLINK:
				strcpy(fmt->extra.nlink.format, nfmt);
				break;
			case DPC_UIDNUM:
				strcpy(fmt->extra.uidnum.format, nfmt);
				break;
			case DPC_GIDNUM:
				strcpy(fmt->extra.gidnum.format, nfmt);
				break;
			case DPC_DEVICE:
				strcpy(fmt->extra.device.format, nfmt);
				break;
			case DPC_DEVMIN:
				strcpy(fmt->extra.devmin.format, nfmt);
				break;
			case DPC_DEVMAJ:
				strcpy(fmt->extra.devmaj.format, nfmt);
				break;
			case DPC_ATIME:
				strcpy(fmt->extra.a_time.format, gtk_entry_get_text(GTK_ENTRY(edit->extra.date.format)));
				break;
			case DPC_MTIME:
				strcpy(fmt->extra.m_time.format, gtk_entry_get_text(GTK_ENTRY(edit->extra.date.format)));
				break;
			case DPC_CTIME:
				strcpy(fmt->extra.c_time.format, gtk_entry_get_text(GTK_ENTRY(edit->extra.date.format)));
				break;
			case DPC_TYPE:
				break;
			default:
				;
		}
	}

	gtk_grab_remove(edit->dlg);
	gtk_widget_destroy(edit->dlg);

	return TRUE;
}

/* 1998-06-23 -	Do the complex thing, and let the user edit the details of the selected
**		selected (!) row.
** 1998-06-24 -	Redesigned the gimp way, i.e. without checking for success of GTK calls.
**		I really don't like it, but it saves a bunch of indents. I will not ever
**		take this as a hint that I should reduce my indent size... :^)
**		The dialog created by this function has a very simple structure: the top
**		area (the vbox) contains two frames. The top frame holds settings that
**		are available for all content types (type, name, width, justification).
**		The bottom frame (which may be missing) contains content-specific settings.
*/
static gint evt_sclist_edit_clicked(GtkWidget *wid, gpointer user)
{
	PDef		*def = user;
	DpCFmt		*fmt = &def->edit.format[def->sclist_row];
	GtkWidget	*frame1, *frame2, *table, *label, *button;
	const gchar	*lab[]   = { "Content", "Title", "Justification", "Width" },
			*jtext[] = { "Left", "Right", "Center", NULL },
			*btext[] = { "OK", "Cancel" };
	guint		i;
	static P_Edit	edit;

	edit.def     = def;
	edit.content = fmt->content;

	edit.dlg = gtk_dialog_new();
	gtk_window_set_title(GTK_WINDOW(edit.dlg), "Edit Column Content");
	frame1 = gtk_frame_new("Basic Settings");

	table = gtk_table_new(4, 2, FALSE);
	for(i = 0; i < sizeof lab / sizeof lab[0]; i++)
	{
		label = gtk_label_new(lab[i]);
		gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
		gtk_table_attach(GTK_TABLE(table), label, 0, 1, i, i+1, 0,0,0,0);
		gtk_widget_show(label);
	}

	label = gtk_label_new(dpf_get_content_name(edit.content));
	gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 0, 1);
	gtk_widget_show(label);

	edit.title = gtk_entry_new_with_max_length(DP_TITLE_MAX - 1);
	gtk_entry_set_text(GTK_ENTRY(edit.title), fmt->title);
	gtk_table_attach_defaults(GTK_TABLE(table), edit.title, 1, 2, 1, 2);
	gtk_widget_show(edit.title);

	edit.just = gtk_option_menu_new();
	gtk_option_menu_set_menu(GTK_OPTION_MENU(edit.just),
				 gui_build_menu(jtext, GTK_SIGNAL_FUNC(evt_edit_justification), &edit));
	switch(fmt->just)
	{
		case GTK_JUSTIFY_LEFT:
		case GTK_JUSTIFY_FILL:
			edit.curr_just = 0;
			break;
		case GTK_JUSTIFY_RIGHT:
			edit.curr_just = 1;
			break;
		case GTK_JUSTIFY_CENTER:
			edit.curr_just = 2;
			break;
	}
	gtk_option_menu_set_history(GTK_OPTION_MENU(edit.just), edit.curr_just);
	gtk_table_attach_defaults(GTK_TABLE(table), edit.just, 1, 2, 2, 3);
	gtk_widget_show(edit.just);

	edit.wadj = gtk_adjustment_new(fmt->width, 10, 600, 1, 50, 20);
	edit.width = gtk_spin_button_new(GTK_ADJUSTMENT(edit.wadj), 0, 0);
	gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(edit.width), FALSE);
/*	edit.width = gtk_hscale_new((GtkAdjustment *) edit.wadj);
	gtk_scale_set_digits(GTK_SCALE(edit.width), 0);
	gtk_scale_set_draw_value(GTK_SCALE(edit.width), TRUE);
	gtk_scale_set_value_pos(GTK_SCALE(edit.width), GTK_POS_RIGHT);
*/	gtk_table_attach_defaults(GTK_TABLE(table), edit.width, 1, 2, 3, 4);
	gtk_widget_show(edit.width);

	gtk_container_set_border_width(GTK_CONTAINER(frame1), 5);
	gtk_container_add(GTK_CONTAINER(frame1), table);
	gtk_widget_show(table);
	gtk_widget_show(frame1);

	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(edit.dlg)->vbox), frame1, TRUE, TRUE, 0);

	if((frame2 = add_content_widgets(&edit)) != NULL)
	{
		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(edit.dlg)->vbox), frame2, TRUE, TRUE, 0);
		gtk_widget_show(frame2);
	}

	for(i = 0; i < sizeof btext / sizeof btext[0]; i++)
	{
		button = gtk_button_new_with_label(btext[i]);
		gtk_object_set_user_data(GTK_OBJECT(button), (gpointer) i);
		gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(evt_edit_button), &edit);
		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(edit.dlg)->action_area), button, TRUE, TRUE, 0);
		GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
		if(i == 0)		/* This doesn't seem to work. Weird. */
			gtk_widget_grab_default(button);
		gtk_widget_show(button);
	}

	gtk_widget_show(edit.dlg);
	gtk_grab_add(edit.dlg);

	return TRUE;
}

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

/* 1999-05-15 -	User selected a row in the selected clist. Remember which for later. */
static void evt_sclist_select_row(GtkWidget *wid, gint row, gint column, GdkEventButton *evt, gpointer user)
{
	PDef	*def = user;

	gtk_widget_set_sensitive(def->sbutton[0], TRUE);
	gtk_widget_set_sensitive(def->sbutton[1], TRUE);
	gtk_widget_set_sensitive(def->sbutton[2], row > 0);
	gtk_widget_set_sensitive(def->sbutton[3], row < def->edit.num_columns - 1);
	def->sclist_row = row;
	if((evt != NULL) && (evt->type == GDK_2BUTTON_PRESS))
		gtk_signal_emit_by_name(GTK_OBJECT(def->sbutton[0]), "clicked", def);
}

/* 1999-05-15 -	The up till now current row in the selected clist was just unselected, so forget about it. */
static void evt_sclist_unselect_row(GtkWidget *wid, gint row, gint column, GdkEventButton *evt, gpointer user)
{
	PDef	*def = user;

	gtk_widget_set_sensitive(def->sbutton[0], FALSE);
	gtk_widget_set_sensitive(def->sbutton[1], FALSE);
	gtk_widget_set_sensitive(def->sbutton[2], FALSE);
	gtk_widget_set_sensitive(def->sbutton[3], FALSE);
	def->sclist_row = -1;
}

/* 1999-05-15 -	User clicked the "Remove" button below selected column list, so kill a column. */
static void evt_sclist_remove_clicked(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;
	gint	row;

	row = def->sclist_row;
	if((row >= 0) && (row < def->edit.num_columns) && (def->edit.num_columns > 1))
	{
		guint	i;

		for(i = row; i < def->edit.num_columns - 1; i++)
			def->edit.format[i] = def->edit.format[i + 1];
		def->edit.num_columns--;
		set_pane_widgets(def);
		gtk_clist_select_row(GTK_CLIST(def->sclist), (row < def->edit.num_columns) ? row : def->edit.num_columns - 1, -1);
	}
}

/* 1999-05-15 -	Swap columns <c1> and <c2>. Typically used to move up or down. */
static void column_swap(PDef *def, gint c1, gint c2)
{
	DpCFmt	tmp;

	tmp = def->edit.format[c1];
	def->edit.format[c1] = def->edit.format[c2];
	def->edit.format[c2] = tmp;
}

/* 1999-05-15 -	The "up" arrow has been hit. Move the currently selected column. */
static void evt_sclist_up_clicked(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;
	gint	row;

	row = def->sclist_row;
	if(row > 0)
	{
		column_swap(def, row, row - 1);
		gtk_clist_row_move(GTK_CLIST(def->sclist), row, row - 1);
		gtk_clist_select_row(GTK_CLIST(def->sclist), row - 1, -1);
		gtk_clist_moveto(GTK_CLIST(def->sclist), row - 1, -1, 0.0, 0.0);
	}
}

/* 1999-05-15 -	The "down" arrow has been hit. Move the currently selected column. */
static void evt_sclist_down_clicked(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;
	gint	row;

	row = def->sclist_row;
	if((row >= 0) && (row < def->edit.num_columns))
	{
		column_swap(def, row, row + 1);
		gtk_clist_row_move(GTK_CLIST(def->sclist), row, row + 1);
		gtk_clist_select_row(GTK_CLIST(def->sclist), row + 1, -1);
		gtk_clist_moveto(GTK_CLIST(def->sclist), row + 1, -1, 1.0, 0.0);
	}
}

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

/* 1999-05-15 -	Set new sorting column, from option menu selection. */
static void evt_sort_set_column(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;

	def->edit.sort.column = GPOINTER_TO_UINT(gtk_object_get_user_data(GTK_OBJECT(wid)));
}

/* 1999-05-15 -	Set new sorting mode, from option menu selection. */
static void evt_sort_set_mode(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;

	def->edit.sort.mode = (SortMode) GPOINTER_TO_UINT(gtk_object_get_user_data(GTK_OBJECT(wid)));
}

/* 1999-05-15 -	User hit "Inverse Sort?" button, so update. */
static void evt_sort_invert_clicked(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;

	def->edit.sort.invert = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid));
}

/* 1999-05-15 -	User hit the case-insensitivity toggle. */
static void evt_sort_nocase_clicked(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;

	def->edit.sort.nocase = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid));
}

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

/* 1999-05-15 -	User is editing the default path, so update in editing copy. */
static void evt_default_path_changed(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;

	str_strncpy(def->edit.def_path, gtk_entry_get_text(GTK_ENTRY(wid)), sizeof def->edit.def_path);
}

/* 1999-05-15 -	User just hit the "Grab Current" button for the default path. Do it. */
static void evt_default_path_grab(GtkWidget *wid, gpointer user)
{
	PDef		*def = user;
	P_DirPane	*page;

	page = def->page;

	gtk_entry_set_text(GTK_ENTRY(def->dentry), page->min->gui->pane[def->index].dir.path);
}

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

/* 1999-05-15 -	Set state of "path above" flag. */
static void evt_flag_above_clicked(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;

	def->edit.path_above = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid));
}

/* 1999-05-15 -	Set state of "hide allowed" flag. */
static void evt_flag_hide_clicked(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;

	def->edit.hide_allowed = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid));
}

/* 1999-05-15 -	Set state of "scrollbar always" flag. */
static void evt_flag_scrollbar_clicked(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;

	def->edit.scrollbar_always = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid));
}

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

/* 1999-05-15 -	User clicked on one of the "scrollbar position" widgets. Update. */
static void evt_scrollbarpos_clicked(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;

	if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid)))
		def->edit.sbar_pos = (SBarPos) GPOINTER_TO_UINT(gtk_object_get_user_data(GTK_OBJECT(wid)));
}

/* 1999-05-15 -	User clicked a control mode radio button. Update. */
static void evt_controlmode_clicked(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;

	if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid)))
		def->edit.ctrl_mode = (CtrlMode) GPOINTER_TO_UINT(gtk_object_get_user_data(GTK_OBJECT(wid)));
}

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

/* 1999-05-16 -	Copy this pane's definition to the other one. */
static void evt_copyto_clicked(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;

	def->page->pane[1 - def->index].edit = def->edit;
	set_pane_widgets(&def->page->pane[1 - def->index]);
}

/* 1999-05-16 -	Copy the other pane's definition to this pane. */
static void evt_copyfrom_clicked(GtkWidget *wid, gpointer user)
{
	PDef	*def = user;

	def->edit = def->page->pane[1 - def->index].edit;
	set_pane_widgets(def);
}

/* 1999-05-16 -	Swap the two pane definitions. */
static void evt_swap_clicked(GtkWidget *wid, gpointer user)
{
	PDef		*def = user;
	DPFormat	tmp;

	tmp = def->edit;
	def->edit = def->page->pane[1 - def->index].edit;
	def->page->pane[1 - def->index].edit = tmp;
	set_pane_widgets(def);
}

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

/* 1999-05-15 -	Initialize widgets for configuring a pane. This function is horribly long,
**		but conceptually very simple: it just builds widgets.
*/
static void init_pane_page(P_DirPane *page, guint index, const gchar *other)
{
	PDef		*def;
	GtkWidget	*label, *scwin, *vbox, *frame, *hbox, *table, *button, *sep;
	gchar		*atitle[] = { "Default Title", "Content" }, *stitle[] = { "Title", "Content" }, buf[64];
	const gchar	*smitem[] = { "Directories First", "Directories Last", "Directories Mixed", NULL },
			*spos[] = { "System Default", "Left of List", "Right of List" },
			*mctrl[] = { "System Default", "Always Set", "(Always Clear)", "Toggle" },
			*tfmt[] = { "Copy To %s", "Copy From %s", "Swap With %s" };
	gchar		*row[2];
	guint		i;
	GtkSignalFunc	scfunc[] = { GTK_SIGNAL_FUNC(evt_sclist_edit_clicked), GTK_SIGNAL_FUNC(evt_sclist_remove_clicked),
				     GTK_SIGNAL_FUNC(evt_sclist_up_clicked), GTK_SIGNAL_FUNC(evt_sclist_down_clicked) },
			tool[] = { GTK_SIGNAL_FUNC(evt_copyto_clicked), GTK_SIGNAL_FUNC(evt_copyfrom_clicked),
				    GTK_SIGNAL_FUNC(evt_swap_clicked) };

	def = &page->pane[index];
	def->page  = page;
	def->index = index;

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

	/* Columns part. */
	frame = gtk_frame_new("Columns");
	hbox  = gtk_hbox_new(FALSE, 0);

	/*  Available content types. */
	vbox = gtk_vbox_new(FALSE, 0);
	label = gtk_label_new("Available Content Types");
	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
	gtk_widget_show(label);
	scwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	def->aclist = gtk_clist_new_with_titles(2, atitle);
	gtk_signal_connect(GTK_OBJECT(def->aclist), "button_press_event", GTK_SIGNAL_FUNC(evt_aclist_button_press), def);
	gtk_clist_set_column_width(GTK_CLIST(def->aclist), 0, 70);
	gtk_clist_column_titles_passive(GTK_CLIST(def->aclist));
	for(i = 0; i < DPC_NUM_TYPES; i++)
	{
		row[0] = (gchar *) dpf_get_content_title((DPContent) i);
		row[1] = (gchar *) dpf_get_content_name((DPContent) i);
		gtk_clist_append(GTK_CLIST(def->aclist), row);
	}
	gtk_container_add(GTK_CONTAINER(scwin), def->aclist);
	gtk_widget_show(def->aclist);
	gtk_box_pack_start(GTK_BOX(vbox), scwin, TRUE, TRUE, 0);
	gtk_widget_show(scwin);
	gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
	gtk_widget_show(vbox);

	/*  Separation between available & selected content types. */
	sep = gtk_vseparator_new();
	gtk_box_pack_start(GTK_BOX(hbox), sep, FALSE, FALSE, 5);
	gtk_widget_show(sep);

	/*  Selected content types. */
	vbox = gtk_vbox_new(FALSE, 0);
	label = gtk_label_new("Selected Content Types");
	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
	gtk_widget_show(label);
	scwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	def->sclist = gtk_clist_new_with_titles(2, stitle);
	gtk_signal_connect(GTK_OBJECT(def->sclist), "select_row", GTK_SIGNAL_FUNC(evt_sclist_select_row), def);
	gtk_signal_connect(GTK_OBJECT(def->sclist), "unselect_row", GTK_SIGNAL_FUNC(evt_sclist_unselect_row), def);
	gtk_clist_set_column_width(GTK_CLIST(def->sclist), 0, 70);
	gtk_clist_column_titles_passive(GTK_CLIST(def->sclist));
	gtk_container_add(GTK_CONTAINER(scwin), def->sclist);
	gtk_widget_show(def->sclist);
	gtk_box_pack_start(GTK_BOX(vbox), scwin, TRUE, TRUE, 0);
	gtk_widget_show(scwin);
	table = gtk_table_new(1, sizeof def->sbutton / sizeof def->sbutton[0], FALSE);
	def->sbutton[0] = gtk_button_new_with_label("Edit...");
	def->sbutton[1] = gtk_button_new_with_label("Remove");
	def->sbutton[2] = gui_arrow_button_new(GTK_ARROW_UP);
	def->sbutton[3] = gui_arrow_button_new(GTK_ARROW_DOWN);
	for(i = 0; i < sizeof def->sbutton / sizeof def->sbutton[0]; i++)
	{
		gtk_signal_connect(GTK_OBJECT(def->sbutton[i]), "clicked", GTK_SIGNAL_FUNC(scfunc[i]), def);
		gtk_table_attach_defaults(GTK_TABLE(table), def->sbutton[i], i, i + 1, 0, 1);
		gtk_widget_show(def->sbutton[i]);
	}
	gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
	gtk_widget_show(table);
	gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
	gtk_widget_show(vbox);
	gtk_container_add(GTK_CONTAINER(frame), hbox);
	gtk_widget_show(hbox);
	gtk_box_pack_start(GTK_BOX(def->vbox), frame, TRUE, TRUE, 0);
	gtk_widget_show(frame);

	/* Sorting. */
	frame = gtk_frame_new("Sorting");
	vbox = gtk_vbox_new(FALSE, 0);
	hbox = gtk_hbox_new(FALSE, 0);
	label = gtk_label_new("Sort On");
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_widget_show(label);
	def->scmenu = gtk_option_menu_new();
	gtk_box_pack_start(GTK_BOX(hbox), def->scmenu, TRUE, TRUE, 0);
	gtk_widget_show(def->scmenu);
	label = gtk_label_new("Mode");
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_widget_show(label);
	def->smmenu = gtk_option_menu_new();
	gtk_option_menu_set_menu(GTK_OPTION_MENU(def->smmenu), gui_build_menu(smitem, GTK_SIGNAL_FUNC(evt_sort_set_mode), def));
	gtk_box_pack_start(GTK_BOX(hbox), def->smmenu, FALSE, FALSE, 0);
	gtk_widget_show(def->smmenu);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);
	hbox = gtk_hbox_new(FALSE, 0);
	def->sinvert = gtk_check_button_new_with_label("Inverse Sorting?");
	gtk_signal_connect(GTK_OBJECT(def->sinvert), "clicked", GTK_SIGNAL_FUNC(evt_sort_invert_clicked), def);
	gtk_box_pack_start(GTK_BOX(hbox), def->sinvert, TRUE, FALSE, 0);
	gtk_widget_show(def->sinvert);
	def->snocase = gtk_check_button_new_with_label("Case Insensitive?");
	gtk_signal_connect(GTK_OBJECT(def->snocase), "clicked", GTK_SIGNAL_FUNC(evt_sort_nocase_clicked), def);
	gtk_box_pack_start(GTK_BOX(hbox), def->snocase, TRUE, FALSE, 0);
	gtk_widget_show(def->snocase);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);
	gtk_container_add(GTK_CONTAINER(frame), vbox);
	gtk_widget_show(vbox);
	gtk_box_pack_start(GTK_BOX(def->vbox), frame, FALSE, FALSE, 0);
	gtk_widget_show(frame);

	/* Default directory. */
	frame = gtk_frame_new("Default Directory");
	hbox = gtk_hbox_new(FALSE, 0);
	def->dentry = gtk_entry_new_with_max_length(PATH_MAX - 1);
	gtk_signal_connect(GTK_OBJECT(def->dentry), "changed", GTK_SIGNAL_FUNC(evt_default_path_changed), def);
	gtk_box_pack_start(GTK_BOX(hbox), def->dentry, TRUE, TRUE, 0);
	gtk_widget_show(def->dentry);
	button = gtk_button_new_with_label("Grab Current");
	gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(evt_default_path_grab), def);
	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
	gtk_widget_show(button);
	gtk_container_add(GTK_CONTAINER(frame), hbox);
	gtk_widget_show(hbox);
	gtk_box_pack_start(GTK_BOX(def->vbox), frame, FALSE, FALSE, 0);
	gtk_widget_show(frame);

	/* Flags. */
	hbox = gtk_hbox_new(FALSE, 0);
	frame = gtk_frame_new("Flags");
	vbox = gtk_vbox_new(FALSE, 0);
	def->fabove = gtk_check_button_new_with_label("Path Above?");
	gtk_signal_connect(GTK_OBJECT(def->fabove), "clicked", GTK_SIGNAL_FUNC(evt_flag_above_clicked), def);
	gtk_box_pack_start(GTK_BOX(vbox), def->fabove, FALSE, FALSE, 0);
	gtk_widget_show(def->fabove);
	def->fhide = gtk_check_button_new_with_label("Hide Allowed?");
	gtk_signal_connect(GTK_OBJECT(def->fhide), "clicked", GTK_SIGNAL_FUNC(evt_flag_hide_clicked), def);
	gtk_box_pack_start(GTK_BOX(vbox), def->fhide, FALSE, FALSE, 0);
	gtk_widget_show(def->fhide);
	def->fscroll = gtk_check_button_new_with_label("Scrollbar Always?");
	gtk_signal_connect(GTK_OBJECT(def->fscroll), "clicked", GTK_SIGNAL_FUNC(evt_flag_scrollbar_clicked), def);
	gtk_box_pack_start(GTK_BOX(vbox), def->fscroll, FALSE, FALSE, 0);
	gtk_widget_show(def->fscroll);
	gtk_container_add(GTK_CONTAINER(frame), vbox);
	gtk_widget_show(vbox);
	gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
	gtk_widget_show(frame);

	/* Scrollbar position (new). */
	frame = gtk_frame_new("Scrollbar Position");
	vbox = gtk_vbox_new(FALSE, 0);
	def->splist = gui_radio_group_new(sizeof def->spos / sizeof def->spos[0], spos, def->spos);
	for(i = 0; i < sizeof def->spos / sizeof def->spos[0]; i++)
	{
		gtk_signal_connect(GTK_OBJECT(def->spos[i]), "clicked", GTK_SIGNAL_FUNC(evt_scrollbarpos_clicked), def);
		gtk_box_pack_start(GTK_BOX(vbox), def->spos[i], FALSE, FALSE, 0);
		gtk_widget_show(def->spos[i]);
	}
	gtk_container_add(GTK_CONTAINER(frame), vbox);
	gtk_widget_show(vbox);
	gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
	gtk_widget_show(frame);

	/* Control state. */
	frame = gtk_frame_new("Modify 'Control' Key State");
	vbox = gtk_vbox_new(FALSE, 0);
	def->mclist = gui_radio_group_new(sizeof def->mctrl / sizeof def->mctrl[0], mctrl, def->mctrl);
	for(i = 0; i < sizeof def->mctrl / sizeof def->mctrl[0]; i++)
	{
		gtk_signal_connect(GTK_OBJECT(def->mctrl[i]), "clicked", GTK_SIGNAL_FUNC(evt_controlmode_clicked), def);
		gtk_box_pack_start(GTK_BOX(vbox), def->mctrl[i], FALSE, FALSE, 0);
		if(mctrl[i][0] != '(')		/* Hide button whose label is (parenthesised). A hack. */
			gtk_widget_show(def->mctrl[i]);
	}
	gtk_container_add(GTK_CONTAINER(frame), vbox);
	gtk_widget_show(vbox);
	gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
	gtk_widget_show(frame);
	gtk_box_pack_start(GTK_BOX(def->vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);

	/* Pane "tools" (i.e., copy/swap) buttons (formerly on top). */
	hbox = gtk_hbox_new(FALSE, 0);
	for(i = 0; i < sizeof tfmt / sizeof tfmt[0]; i++)
	{
		g_snprintf(buf, sizeof buf, tfmt[i], other);
		button = gtk_button_new_with_label(buf);
		gtk_signal_connect(GTK_OBJECT(button), "clicked", tool[i], def);
		gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 5);
		gtk_widget_show(button);
	}
	gtk_box_pack_start(GTK_BOX(def->vbox), hbox, FALSE, FALSE, 5);
	gtk_widget_show(hbox);
}

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

/* 1999-05-16 -	This gets called by the color_dialog when the color changes. */
static void color_changed(GdkColor *color, gpointer user)
{
	PColors	*col = user;

	col->edit.color[col->index] = *color;
	set_color_widgets(col);
}

/* 1999-05-16 -	User clicked a color. Pop up the good 'ol color dialog. */
static void evt_color_clicked(GtkWidget *wid, gpointer wuser, guint index, gpointer suser)
{
	PColors		*col = suser;
	GdkColor	initial;

	col->index = GPOINTER_TO_UINT(gtk_object_get_user_data(GTK_OBJECT(wid)));
	initial = col->edit.color[col->index];
	if(cdl_dialog(NULL, color_changed, &initial, col) == 1)	/* Dialog cancelled? */
	{
		col->edit.color[col->index] = initial;
		set_color_widgets(col);
	}
}

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

/* 1999-05-16 -	Initialize widgets for dirpane color editing. */
static void init_color_page(P_DirPane *page)
{
	PColors		*col = &page->colors;
	GtkWidget	*table, *label;
	const gchar	*ltxt[] = { "Active Pane Titles", "Focused Row, Unselected", "Focused Row, Selected" };
	guint		i;

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

	table = gtk_table_new(3, 2, FALSE);
	for(i = 0; i < sizeof ltxt / sizeof ltxt[0]; i++)
	{
		label = gtk_label_new(ltxt[i]);
		gtk_table_attach(GTK_TABLE(table), label, 0, 1, i, i + 1,  0,0,0,0);
		gtk_widget_show(label);
		col->widget[i] = od_emil_button_new();
		gtk_object_set_user_data(GTK_OBJECT(col->widget[i]), GUINT_TO_POINTER((DpCol) i));
		gtk_signal_connect(GTK_OBJECT(col->widget[i]), "activate", GTK_SIGNAL_FUNC(evt_color_clicked), col);
		gtk_table_attach(GTK_TABLE(table), col->widget[i], 1, 2, i, i + 1, GTK_EXPAND|GTK_FILL,0,5,2);
		gtk_widget_show(col->widget[i]);
	}
	gtk_box_pack_start(GTK_BOX(col->vbox), table, TRUE, TRUE, 0);
	gtk_widget_show(table);
}

/* 1998-06-22 -	Called by the main configuration code as it opens up. Asks this module
**		to initialize itself, and return a pointer to the root of its widgetry.
**		This module should also set the <name> pointer to its name ("DirPane").
** 1998-06-25 -	Gimpified this function, too. Terrible.
*/
static GtkWidget * cdp_init(MainInfo *min, char **name)
{
	P_DirPane	*page = &the_page;

	if(name == NULL)
		return NULL;

	*name = "Dir Panes";

	page->min   = min;

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

	page->notebook = gtk_notebook_new();
	init_pane_page(page, 0, "Right");
	gtk_notebook_append_page(GTK_NOTEBOOK(page->notebook), page->pane[0].vbox, gtk_label_new("Left"));
	gtk_widget_show(page->pane[0].vbox);
	init_pane_page(page, 1, "Left");
	gtk_notebook_append_page(GTK_NOTEBOOK(page->notebook), page->pane[1].vbox, gtk_label_new("Right"));
	gtk_widget_show(page->pane[1].vbox);

	init_color_page(page);
	gtk_notebook_append_page(GTK_NOTEBOOK(page->notebook), page->colors.vbox, gtk_label_new("Colors"));
	gtk_widget_show(page->colors.vbox);

	gtk_box_pack_start(GTK_BOX(page->vbox), page->notebook, TRUE, TRUE, 5);
	gtk_widget_show(page->notebook);

	gtk_widget_show(page->vbox);
	return page->vbox;
}

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

/* 1998-06-26 -	This gets called when the config UI opens up, and has already been up.
**		It gives this module a chance to update it's page.
*/
static void cdp_update(MainInfo *min)
{
	guint	i;

	for(i = 0; i < sizeof the_page.pane / sizeof the_page.pane[0]; i++)
		the_page.pane[i].edit = min->cfg.dp_format[i];
	the_page.colors.edit = min->cfg.dp_colors;

	set_pane_widgets(&the_page.pane[0]);
	set_pane_widgets(&the_page.pane[1]);
	set_color_widgets(&the_page.colors);
}

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

/* 1999-05-15 -	Check if any pane was modified, and if so update the "real" options.
** 1999-05-16 -	Added check on colors, too.
*/
static void cdp_accept(MainInfo *min)
{
	guint	i;

	for(i = 0; i < sizeof the_page.pane / sizeof the_page.pane[0]; i++)
	{
		if(memcmp(&the_page.pane[i].edit, &min->cfg.dp_format[i], sizeof the_page.pane[i].edit))
		{
			min->cfg.dp_format[i] = the_page.pane[i].edit;
			cfg_set_flags(CFLG_REBUILD_MIDDLE | CFLG_RESCAN_LEFT | CFLG_RESCAN_RIGHT);
		}
		if(memcmp(&the_page.colors.edit, &min->cfg.dp_colors, sizeof the_page.colors.edit))
		{
			min->cfg.dp_colors = the_page.colors.edit;
			cfg_set_flags(CFLG_REBUILD_MIDDLE | CFLG_RESCAN_LEFT | CFLG_RESCAN_RIGHT);
		}
	}
}

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

static void save_extra(FILE *out, DPContent content, DpCExtra *extra)
{
	xml_put_node_open(out, "DPExtra");
	switch(content)
	{
		case DPC_NAME:
			xml_put_boolean(out, "show_type", extra->name.show_type);
			xml_put_boolean(out, "show_linkname", extra->name.show_linkname);
			break;
		case DPC_SIZE:
			xml_put_text(out, "numformat", extra->size.format);
			xml_put_boolean(out, "dir_fs", extra->size.dir_show_fs_size);
			break;
		case DPC_IQSIZE:
			xml_put_boolean(out, "dir_fs", extra->iqsize.dir_show_fs_size);
			break;
		case DPC_BLOCKS:		/* Fall-through. */
		case DPC_MODENUM:
		case DPC_NLINK:
		case DPC_UIDNUM:
		case DPC_GIDNUM:
		case DPC_DEVICE:
		case DPC_DEVMAJ:
		case DPC_DEVMIN:
			xml_put_text(out, "numformat", extra->blocks.format);
			break;
		case DPC_ATIME:
			xml_put_text(out, "dateformat", extra->a_time.format);
			break;
		case DPC_MTIME:
			xml_put_text(out, "dateformat", extra->m_time.format);
			break;
		case DPC_CTIME:
			xml_put_text(out, "dateformat", extra->c_time.format);
			break;
		case DPC_TYPE:
			break;
		default:
			;
	}
	xml_put_node_close(out, "DPExtra");
}

static void save_column(FILE *out, DpCFmt *column, guint index)
{
	xml_put_node_open(out, "DPColumn");
	xml_put_integer(out, "index", index);
	xml_put_text(out, "title", column->title);
	xml_put_text(out, "content", dpf_get_content_name(column->content));
	save_extra(out, column->content, &column->extra);
	xml_put_integer(out, "justification", column->just);
	xml_put_integer(out, "width", column->width);
	xml_put_node_close(out, "DPColumn");
}

/* 1998-07-27 -	Rewritten to use the new xml_put_XXXX() funtions. Loads cleaner. */
static void save_sort(FILE *out, DPSort *sort)
{
	xml_put_node_open(out, "DPSort");
	xml_put_integer(out, "column", sort->column);
	xml_put_text(out, "mode", mode_name[sort->mode]);
	xml_put_boolean(out, "invert", sort->invert);
	xml_put_boolean(out, "nocase", sort->nocase);	
	xml_put_node_close(out, "DPSort");
}

static void save_pane(FILE *out, DPFormat *fmt, char *node_name)
{
	guint	i;

	xml_put_node_open(out, node_name);
	xml_put_integer(out, "columns", fmt->num_columns);
	for(i = 0; i < fmt->num_columns; i++)
		save_column(out, &fmt->format[i], i);
	save_sort(out, &fmt->sort);
	xml_put_text(out, "defpath", fmt->def_path);
	xml_put_boolean(out, "path_above", fmt->path_above);
	xml_put_boolean(out, "hide_allowed", fmt->hide_allowed);
	xml_put_boolean(out, "scrollbar_always", fmt->scrollbar_always);
	xml_put_text(out, "sbar_pos", sbarpos_name[fmt->sbar_pos]);
	xml_put_integer(out, "ctrl_mode", fmt->ctrl_mode);
	xml_put_node_close(out, node_name);
}

/* 1999-05-16 -	Save the current colors out. */
static void save_colors(FILE *out, DPColors *cols)
{
	guint	i;

	xml_put_node_open(out, "DirPaneColors");
	for(i = 0; i < DPCOL_NUM; i++)
		xml_put_color(out, color_name[i], &cols->color[i]);
	xml_put_node_close(out, "DirPaneColors");
}

/* 1998-07-25 -	Save out the dirpane configuration information, in XML format. Might tend
**		to be kind of huge.
*/
static gint cdp_save(MainInfo *min, FILE *out)
{
	guint	i, num = sizeof min->cfg.dp_format / sizeof min->cfg.dp_format[0];

	xml_put_node_open(out, NODE);
	xml_put_integer(out, "count", num);
	for(i = 0; i < num; i++)
		save_pane(out, &min->cfg.dp_format[i], config_name[i]);
	save_colors(out, &min->cfg.dp_colors);
	xml_put_node_close(out, NODE);

	return TRUE;
}

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

/* 1998-07-26 -	Load any "extraneous" information a column might have. Note that these
**		really rely on the fact (?) that fields of a union all have the same
**		offset from it (namely 0).
*/
static void load_extra(XmlNode *node, DPContent content, DpCExtra *extra)
{
	gint	tmp;

	switch(content)
	{
		case DPC_NAME:
			if(xml_get_boolean(node, "show_type", &tmp))
				extra->name.show_type = tmp;
			else
				extra->name.show_type = FALSE;
			if(xml_get_boolean(node, "show_linkname", &tmp))
				extra->name.show_linkname = tmp;
			else
				extra->name.show_linkname = FALSE;
			break;
		case DPC_SIZE:
			{
				gint	tmp = 1;	/* This is the compatibility-correct default for dir_show_fs_size. */

				xml_get_text_copy(node, "numformat", extra->size.format, sizeof extra->size.format);
				xml_get_boolean(node, "dir_fs", &tmp);
				extra->size.dir_show_fs_size = tmp;	/* If load fails, use default in tmp. */
			}
			break;
		case DPC_IQSIZE:
			{
				gint	tmp = 1;

				xml_get_boolean(node, "dir_fs", &tmp);
				extra->iqsize.dir_show_fs_size = tmp;
			}
			break;
		case DPC_BLOCKS:
		case DPC_MODENUM:
		case DPC_NLINK:
		case DPC_UIDNUM:
		case DPC_GIDNUM:
		case DPC_DEVICE:
		case DPC_DEVMAJ:
		case DPC_DEVMIN:
			xml_get_text_copy(node, "numformat", extra->blocks.format, sizeof extra->size.format);
			break;
		case DPC_ATIME:
		case DPC_MTIME:
		case DPC_CTIME:
			xml_get_text_copy(node, "dateformat", extra->a_time.format, sizeof extra->a_time.format);
			break;
		case DPC_TYPE:
			break;
		default:
			;
	}
}

/* 1998-07-26 -	Load sorting configuration. */
static void load_sort(XmlNode *node, DPSort *sort)
{
	gchar	*mode;
	gint	tmp;

	xml_get_integer(node, "column", (gint *) &sort->column);
	if(xml_get_text(node, "mode", &mode))
	{
		gboolean	found = FALSE;
		guint		i;

		for(i = 0; i < sizeof mode_name / sizeof mode_name[0]; i++)
		{
			if(strcmp(mode, mode_name[i]) == 0)
			{
				sort->mode = (SortMode) i;
				found = TRUE;
				break;
			}
		}
		if(!found)
			fprintf(stderr, "**DPCFG: Unknown sort mode %s\n", mode);
	}
	if(xml_get_boolean(node, "invert", &tmp))
		sort->invert = tmp;
	if(xml_get_boolean(node, "nocase", &tmp))
		sort->nocase = tmp;
}

/* 1998-07-26 -	Load a column configuration. Since this is called as a callback from the
**		xml_node_visit_children() function, we must make sure that the given node
**		really is for a column.
*/
static void load_column(XmlNode *node, gpointer user)
{
	DPFormat	*fmt = (DPFormat *) user;
	DpCFmt		*colfmt;
	XmlNode		*data;
	gchar		*cnt;
	gint		index;
	DPContent	cid;

	if(!xml_node_has_name(node, "DPColumn"))	/* Not actually a column? */
		return;

	if(xml_get_integer(node, "index", &index))
	{
		colfmt = &fmt->format[index];
		xml_get_text_copy(node, "title", colfmt->title, sizeof colfmt->title);
		if(xml_get_text(node, "content", &cnt))
		{
			if((cid = dpf_get_content_from_name(cnt)) < DPC_NUM_TYPES)
				colfmt->content = cid;
		}
		if((data = xml_tree_search(node, "DPExtra")) != NULL)
			load_extra(data, colfmt->content, &colfmt->extra);
		xml_get_integer(node, "justification", (gint *) &colfmt->just);
		xml_get_integer(node, "width", &colfmt->width);
	}
	else
		fprintf(stderr, "**DPCFG: Config node missing 'index' leaf\n");
}

static void load_pane(XmlNode *node, DPFormat *fmt)
{
	XmlNode *data;
	gchar	*sbarpos;
	gint	tmp;

	if(xml_get_integer(node, "columns", (gint *) &fmt->num_columns))
	{
		xml_node_visit_children(node, load_column, fmt);
		
		if((data = xml_tree_search(node, "DPSort")) != NULL)
			load_sort(data, &fmt->sort);
		xml_get_text_copy(node, "defpath", fmt->def_path, sizeof fmt->def_path);
		if(xml_get_boolean(node, "path_above", &tmp))
			fmt->path_above = tmp;
		if(xml_get_boolean(node, "hide_allowed", &tmp))
			fmt->hide_allowed = tmp;
		if(xml_get_boolean(node, "scrollbar_always", &tmp))
			fmt->scrollbar_always = tmp;
		if(xml_get_text(node, "sbar_pos", &sbarpos))
		{
			gboolean	found = FALSE;
			guint		i;

			for(i = 0; i < sizeof sbarpos_name / sizeof sbarpos_name[0]; i++)
			{
				if(strcmp(sbarpos, sbarpos_name[i]) == 0)
				{
					fmt->sbar_pos = (SBarPos) i;
					found = TRUE;
					break;
				}
			}
			if(!found)
				fprintf(stderr, "**DPCFG: Unknown scrollbar position '%s'\n", sbarpos);
		}
		if(xml_get_integer(node, "ctrl_mode", &tmp))
			fmt->ctrl_mode = (CtrlMode) tmp;
	}
}

static void load_colors(XmlNode *node, DPColors *colors)
{
	guint	i;

	for(i = 0; i < DPCOL_NUM; i++)
		xml_get_color(node, color_name[i], &colors->color[i]);
}

/* 1998-07-26 -	Load (e.g. parse) the DirPane data found in the XML tree <node>. */
static void cdp_load(MainInfo *min, XmlNode *node)
{
	XmlNode	*pane, *cols;
	gint	num, i;

	if(xml_get_integer(node, "count", &num))
	{
		for(i = 0; i < num; i++)
		{
			if((pane = xml_tree_search(node, config_name[i])) != NULL)
				load_pane(pane, &min->cfg.dp_format[i]);
			else
				fprintf(stderr, "**DPCFG: Couldn't find '%s' in config file!\n", config_name[i]);
		}
	}
	if((cols = xml_tree_search(node, "DirPaneColors")) != NULL)
		load_colors(cols, &min->cfg.dp_colors);
}

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

/* 1998-06-26 -	Return a description of this module. */
CfgPage * cdp_describe(MainInfo *min)
{
	static CfgPage	desc = { NODE, cdp_init, cdp_update, cdp_accept, cdp_save, cdp_load, NULL };

	return &desc;
}
