/*
** 1999-04-04 -	Backend support for command sequence configuration. This deals with the specific
**		configuration data for command sequences. This data is used by individual commands,
**		and loaded/saved/visualized by the cfg_cmdseq.c module.
** BUG BUG BUG	Currently handles only "flat", simple data types. Goes a long way, though.
*/

#include "gentoo.h"

#include <stdio.h>

#include "widgets/odemilbutton.h"
#include "strutil.h"
#include "xmlutil.h"
#include "sizeutil.h"
#include "color_dialog.h"
#include "cmdseq_config.h"

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

#define	CMC_FIELD_MAX	(24)

struct _CmdCfg {
	gchar		name[CSQ_NAME_MAX];
	gpointer	base;
	GList		*fields;
};

typedef enum { CFT_INTEGER = 0, CFT_BOOLEAN, CFT_SIZE, CFT_STRING, CFT_COLOR } CFType;

typedef struct {
	gint	min, max;
} CFInt;

typedef struct {
	gsize	size;
} CFStr;

typedef struct {
	gsize	min, max;
	gsize	step;			/* Minimum change. */
} CFSize;

typedef struct {
	CFType	type;
	gchar	name[CMC_FIELD_MAX];
	gchar	*desc;
	gsize	offset;

	union {
	CFInt	integer;
	CFSize	size;
	CFStr	string;
	}	field;
} CField;

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

/* This holds "registered" command configs; these are the ones that are stored in the config
** file, and dealt with by the command options config page. For now, this will in fact be all
** cmc's created. That will change if I ever add nesting support, though.
*/
static GList	*cmdcfg_list = NULL;

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

static void	field_destroy(CField *fld);

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

/* 1999-04-04 -	Create a new, empty, command config descriptor to which fields can then be
**		added. Also, in most cases you'd want to register it. The <base_instance>
**		is the (typically static) base configuration data store.
*/
CmdCfg * cmc_config_new(const gchar *cmdname, gpointer base_instance)
{
	CmdCfg	*cmc;

	cmc = g_malloc(sizeof *cmc);
	str_strncpy(cmc->name, cmdname, sizeof cmc->name);
	cmc->base   = base_instance;
	cmc->fields = NULL;

	return cmc;
}

/* 1999-04-05 -	Just get the name of a given config descriptor. Typically, this will be the
**		name of the command whose config data is described by it.
*/
const gchar * cmc_config_get_name(CmdCfg *cmc)
{
	if(cmc != NULL)
		return cmc->name;
	return NULL;
}

/* 1999-04-05 -	Save given <cmc>'s base instance to file <out>, in XML format. */
void cmc_config_base_save(CmdCfg *cmc, FILE *out)
{
	if((cmc != NULL) && (out != NULL))
	{
		GList	*iter;
		CField	*fld;

		xml_put_node_open(out, cmc->name);
		for(iter = cmc->fields; iter != NULL; iter = g_list_next(iter))
		{
			fld = iter->data;

			if(strcmp(fld->name, "modified") == 0)
				continue;
			switch(fld->type)
			{
				case CFT_INTEGER:
					xml_put_integer(out, fld->name, *(gint *) ((gchar *) cmc->base + fld->offset));
					break;
				case CFT_BOOLEAN:
					xml_put_boolean(out, fld->name, *(gboolean *) ((gchar *) cmc->base + fld->offset));
					break;
				case CFT_SIZE:
					xml_put_uinteger(out, fld->name, *(guint *) ((gchar *) cmc->base + fld->offset));
					break;
				case CFT_STRING:
					xml_put_text(out, fld->name, (gchar *) cmc->base + fld->offset);
					break;
				case CFT_COLOR:
					xml_put_color(out, fld->name, (GdkColor *) ((gchar *) cmc->base + fld->offset));
					break;
			}
		}
		xml_put_node_close(out, cmc->name);
	}
}

/* 1999-04-05 -	Load (parse) data from <node> into the base instance of <cmc>. */
void cmc_config_base_load(CmdCfg *cmc, XmlNode *node)
{
	if((cmc != NULL) && (node != NULL))
	{
		GList	*iter;
		CField	*fld;
		gint	ok;

		for(iter = cmc->fields; iter != NULL; iter = g_list_next(iter))
		{
			fld = iter->data;

			if(strcmp(fld->name, "modified") == 0)
				continue;
			switch(fld->type)
			{
				case CFT_INTEGER:
					ok = xml_get_integer(node, fld->name, (gint *) ((gchar *) cmc->base + fld->offset));
					break;
				case CFT_BOOLEAN:
					ok = xml_get_boolean(node, fld->name, (gboolean *) ((gchar *) cmc->base + fld->offset));
					break;
				case CFT_SIZE:
					ok = xml_get_uinteger(node, fld->name, (gsize *) ((gchar *) cmc->base + fld->offset));
					break;
				case CFT_STRING:
					ok = xml_get_text_copy(node, fld->name, (gchar *) cmc->base + fld->offset, fld->field.string.size);
					break;
				case CFT_COLOR:
					ok = xml_get_color(node, fld->name, (GdkColor *) ((gchar *) cmc->base + fld->offset));
					break;
			}
			if(!ok)
				fprintf(stderr, "CMDCFG: Couldn't load data for %s::%s\n", cmc->name, fld->name);
		}
	}
}

/* 1999-04-05 -	Compare two command config namess; useful to keep them sorted. */
static gint cmp_cmc_name(gconstpointer a, gconstpointer b)
{
	return strcmp(((CmdCfg *) a)->name, ((CmdCfg *) b)->name);
}

/* 1999-04-05 -	Register given <cmc>, so iterators and other things become aware of it. */
void cmc_config_register(CmdCfg *cmc)
{
	if(cmc != NULL)
		cmdcfg_list = g_list_insert_sorted(cmdcfg_list, cmc, cmp_cmc_name);
}

/* 1999-04-05 -	Call a user-defined function for each registered command config descriptor. */
void cmc_config_registered_foreach(void (*func)(CmdCfg *cmc, gpointer user), gpointer user)
{
	GList	*iter;

	for(iter = cmdcfg_list; iter != NULL; iter = g_list_next(iter))
		func(iter->data, user);
}

/* 1999-04-05 -	Return the number of registered command config descriptors. */
guint cmc_config_registered_num(void)
{
	return g_list_length(cmdcfg_list);
}

/* 1999-04-05 -	Unregister a <cmc>. */
void cmc_config_unregister(CmdCfg *cmc)
{
	if(cmc != NULL)
	{
		GList	*link;

		if((link = g_list_find(cmdcfg_list, cmc)) != NULL)
		{
			cmdcfg_list = g_list_remove_link(cmdcfg_list, link);
			g_list_free(link);
		}
	}
}

/* 1999-04-05 -	Free a single field. */
static void free_field(gpointer data, gpointer user)
{
	field_destroy(data);
}

/* 1999-04-05 -	Destroy a command config descriptor. Also causes it to be unregistered. */
void cmc_config_destroy(CmdCfg *cmc)
{
	if(cmc != NULL)
	{
		cmc_config_unregister(cmc);
		if(cmc->fields != NULL)
		{
			g_list_foreach(cmc->fields, free_field, NULL);
			g_list_free(cmc->fields);
		}
		g_free(cmc);
	}
}

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

static CField * field_new(CFType type, const gchar *name, const gchar *desc, gsize offset)
{
	CField	*fld;

	fld = g_malloc(sizeof *fld);

	fld->type = type;
	if(name != NULL)
		str_strncpy(fld->name, name, sizeof fld->name);
	if(desc != NULL)
		fld->desc = strdup(desc);
	else
		fld->desc = NULL;
	fld->offset = offset;

	return fld;
}

static void field_destroy(CField *fld)
{
	if(fld != NULL)
	{
		if(fld->desc != NULL)
			g_free(fld->desc);
		g_free(fld);
	}
}

/* 1999-04-04 -	Compare fields, sorting them in increasing offset order. */
static gint cmp_field(gconstpointer a, gconstpointer b)
{
	return (((CField *) a)->offset - ((CField *) b)->offset);
}

static void field_add(CmdCfg *cmc, CField *fld)
{
	if((cmc != NULL) && (fld != NULL))
	{
		if((cmc->fields == NULL) && strcmp(fld->name, "modified"))
			fprintf(stderr, "CMDCFG: First field should be boolean named 'modified' (%s)\n", cmc->name);

		cmc->fields = g_list_insert_sorted(cmc->fields, fld, cmp_field);
	}
}

void cmc_field_add_integer(CmdCfg *cmc, const gchar *name, const gchar *desc, gsize offset, gint min, gint max)
{
	if(cmc != NULL)
	{
		CField	*fld = field_new(CFT_INTEGER, name, desc, offset);

		fld->field.integer.min = min;
		fld->field.integer.max = max;

		field_add(cmc, fld);
	}
}

void cmc_field_add_boolean(CmdCfg *cmc, const gchar *name, const gchar *desc, gsize offset)
{
	if(cmc != NULL)
		field_add(cmc, field_new(CFT_BOOLEAN, name, desc, offset));
}

void cmc_field_add_size(CmdCfg *cmc, const gchar *name, const gchar *desc, gsize offset, gsize min, gsize max, gsize step)
{
	if(cmc != NULL)
	{
		CField	*fld = field_new(CFT_SIZE, name, desc, offset);

		fld->field.size.min = min;
		fld->field.size.max = max;
		fld->field.size.step = step;

		field_add(cmc, fld);
	}
}

void cmc_field_add_string(CmdCfg *cmc, const gchar *name, const gchar *desc, gsize offset, gsize size)
{
	if(cmc != NULL)
	{
		CField	*fld = field_new(CFT_STRING, name, desc, offset);

		fld->field.string.size = size;

		field_add(cmc, fld);
	}
}

void cmc_field_add_color(CmdCfg *cmc, const gchar *name, const gchar *desc, gsize offset)
{
	if(cmc != NULL)
		field_add(cmc, field_new(CFT_COLOR, name, desc, offset));
}

/* 1999-04-05 -	Find a named field. */
static CField * field_find(CmdCfg *cmc, const gchar *name)
{
	if((cmc != NULL) && (name != NULL))
	{
		GList	*iter;

		for(iter = cmc->fields; iter != NULL; iter = g_list_next(iter))
		{
			if(strcmp(((CField *) iter->data)->name, name) == 0)
				return iter->data;
		}
	}
	return NULL;
}

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

/* 1999-04-05 -	Attach some data handy to have in event handlers. */
static void set_object_data(GtkObject *obj, CField *fld, gpointer instance)
{
	if(obj != NULL)
	{
		gtk_object_set_data(obj, "field", fld);
		gtk_object_set_data(obj, "instance", instance);
	}
}

/* 1999-04-05 -	Retrieve the data set by set_object_data() above. */
static void get_object_data(GtkObject *obj, CField **fld, gpointer *instance)
{
	if(obj != NULL)
	{
		if(fld != NULL)
			*fld = gtk_object_get_data(obj, "field");
		if(instance != NULL)
			*instance = gtk_object_get_data(obj, "instance");
	}
}

/* 1999-04-05 -	Set the mandatory 'modified' field to <value>. */
static void set_modified(CmdCfg *cmc, gpointer instance, gboolean value)
{
	if((cmc != NULL) && (instance != NULL))
	{
		CField	*fld;

		if((fld = field_find(cmc, "modified")) != NULL)
			*(gboolean *) ((gchar *) instance + fld->offset) = value;
	}
}

/* 1999-04-05 -	Return the value of the mandatory 'modified' field. */
static gboolean get_modified(CmdCfg *cmc, gpointer instance)
{
	if((cmc != NULL) && (instance != NULL))
	{
		CField	*fld;

		if((fld = field_find(cmc, "modified")) != NULL)
			return *(gboolean *) ((gchar *) instance + fld->offset);
	}
	return FALSE;		/* A safe default? */
}

/* 1999-04-05 -	User clicked a check button (boolean field). Update field value. */
static gint evt_boolean_clicked(GtkWidget *wid, gpointer user)
{
	CmdCfg		*cmc = user;
	CField		*fld = NULL;
	gpointer	instance = NULL;

	get_object_data(GTK_OBJECT(wid), &fld, &instance);

	if((cmc != NULL) && (fld != NULL) && (instance != NULL))
	{
		*(gboolean *) ((gchar *) instance + fld->offset) = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid));
		set_modified(cmc, instance, TRUE);
	}
	return TRUE;
}

/* 1999-04-05 -	A string has been modified, so we need to update the instance. */
static gint evt_string_changed(GtkWidget *wid, gpointer user)
{
	CmdCfg		*cmc = user;
	CField		*fld;
	gpointer	instance;

	get_object_data(GTK_OBJECT(wid), &fld, &instance);

	if((cmc != NULL) && (fld != NULL) && (instance != NULL))
	{
		gchar	*text;

		if((text = gtk_entry_get_text(GTK_ENTRY(wid))) != NULL)
			str_strncpy((gchar *) instance + fld->offset, text, fld->field.string.size);
		set_modified(cmc, instance, TRUE);
	}
	return TRUE;
}

static gint evt_size_changed(GtkObject *obj, gpointer user)
{
	CmdCfg		*cmc = user;
	CField		*fld;
	gpointer	instance;

	get_object_data(obj, &fld, &instance);

	if((cmc != NULL) && (fld != NULL) && (instance != NULL))
	{
		gchar		buf[32];
		gsize		value = GTK_ADJUSTMENT(obj)->value;
		GtkWidget	*label = gtk_object_get_data(obj, "label");

		value *= fld->field.size.step;
		if(fld->field.size.step >= 512)
			sze_put_size(value, buf, sizeof buf, SZE_KB);
		else
			sze_put_size(value, buf, sizeof buf, SZE_BYTES);
		gtk_label_set_text(GTK_LABEL(label), buf);

		*(gsize *) ((gchar *) instance + fld->offset) = value;
		set_modified(cmc, instance, TRUE);
	}

	return TRUE;
}

/* 1999-05-13 -	User is editing a color field through the color dialog. Update. */
static void color_changed(GdkColor *color, gpointer user)
{
	CField		*fld;
	gpointer	instance;

	get_object_data(GTK_OBJECT(user), &fld, &instance);
	od_emil_button_set_colors(OD_EMIL_BUTTON(user), OD_EMIL_BUTTON_PRIMARY, NULL, color);
	*(GdkColor *) ((gchar *) instance + fld->offset) = *color;
}

/* 1999-05-13 -	User clicked the "Edit" button for a color field. Pop up the color edit
**		dialog, and update the field.
*/
static void evt_color_clicked(GtkWidget *wid, gpointer wuser, guint index, gpointer suser)
{
	CmdCfg		*cmc = suser;
	CField		*fld;
	gpointer	instance;

	get_object_data(GTK_OBJECT(wid), &fld, &instance);

	if((cmc != NULL) && (fld != NULL) && (instance != NULL))
	{
		GdkColor	initial = *(GdkColor *) ((gchar *) instance + fld->offset);

		if(cdl_dialog(NULL, color_changed, &initial, wid) != 0)		/* Cancel? */
			od_emil_button_set_colors(OD_EMIL_BUTTON(wid), OD_EMIL_BUTTON_PRIMARY, NULL, &initial);
	}
}

/* 1999-04-05 -	Build config widgetry for given <fld>. This function is a bit schizofrenic, since it
**		does one of two things: if <table> is non-NULL, the widgetry is packed into it, which
**		looks neat. If it is NULL, a new table is created (height==1), packed, and returned.
*/
static GtkWidget * field_build(CmdCfg *cmc, CField *fld, gpointer instance, GtkWidget *table, gint row)
{
	if((cmc != NULL) && (fld != NULL) && (instance != NULL))
	{
		GtkWidget	*label, *wid, *hbox;
		GtkAdjustment	*adj;
		GtkObject	*obj;

		if(table == NULL)
		{
			table = gtk_table_new(1, 2, FALSE);
			row = 0;
		}

		switch(fld->type)
		{
			case CFT_INTEGER:
				label = gtk_label_new(fld->desc);
				gtk_table_attach(GTK_TABLE(table), label, 0, 1, row, row + 1,  0,0,0,0);
				gtk_widget_show(label);
				wid = gtk_spin_button_new(NULL, 1, 0);
				set_object_data(GTK_OBJECT(wid), fld, instance);
				adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(wid));
				gtk_adjustment_clamp_page(adj, fld->field.integer.min, fld->field.integer.max);
				gtk_adjustment_set_value(adj, *(gint *) ((gchar *) instance + fld->offset));
				gtk_table_attach(GTK_TABLE(table), wid, 1, 2, row, row + 1,  GTK_EXPAND|GTK_FILL,0,5,0);
				gtk_widget_show(wid);
				break;
			case CFT_BOOLEAN:
				wid = gtk_check_button_new_with_label(fld->desc);
				set_object_data(GTK_OBJECT(wid), fld, instance);
				gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(wid), *(gboolean *) ((gchar *) instance + fld->offset));
				gtk_signal_connect(GTK_OBJECT(wid), "clicked", GTK_SIGNAL_FUNC(evt_boolean_clicked), cmc);
				gtk_table_attach(GTK_TABLE(table), wid, 0, 2, row, row + 1,  GTK_EXPAND|GTK_FILL,0,5,0);
				gtk_widget_show(wid);
				break;
			case CFT_SIZE:
				label = gtk_label_new(fld->desc);
				gtk_table_attach(GTK_TABLE(table), label, 0, 1, row, row + 1,  GTK_SHRINK,0,0,0);
				gtk_widget_show(label);
				hbox = gtk_hbox_new(FALSE, 0);
				obj = gtk_adjustment_new((gfloat) *(gint *) ((gchar *) instance + fld->offset) / fld->field.size.step,
							 fld->field.size.min / fld->field.size.step,
							 fld->field.size.max / fld->field.size.step,
							 1, 1, 1);

				set_object_data(obj, fld, instance);
				wid = gtk_hscale_new(GTK_ADJUSTMENT(obj));
				gtk_range_set_update_policy(GTK_RANGE(wid), GTK_UPDATE_DELAYED);
				gtk_scale_set_draw_value(GTK_SCALE(wid), FALSE);
				gtk_box_pack_start(GTK_BOX(hbox), wid, TRUE, TRUE, 0);
				gtk_widget_show(wid);
				label = gtk_label_new("");
				gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
				gtk_widget_show(label);
				gtk_object_set_data(obj, "label", label);
				gtk_signal_connect(obj, "value_changed", GTK_SIGNAL_FUNC(evt_size_changed), cmc);
				gtk_adjustment_value_changed(GTK_ADJUSTMENT(obj));
				gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, row, row + 1,  GTK_EXPAND|GTK_FILL,0,5,0);
				gtk_widget_show(hbox);
				break;
			case CFT_STRING:
				label = gtk_label_new(fld->desc);
				gtk_table_attach(GTK_TABLE(table), label, 0, 1, row, row + 1,  0,0,0,0);
				gtk_widget_show(label);
				wid = gtk_entry_new_with_max_length(fld->field.string.size - 1);
				set_object_data(GTK_OBJECT(wid), fld, instance);
				gtk_entry_set_text(GTK_ENTRY(wid), (gchar *) instance + fld->offset);
				gtk_signal_connect(GTK_OBJECT(wid), "changed", GTK_SIGNAL_FUNC(evt_string_changed), cmc);
				gtk_table_attach(GTK_TABLE(table), wid, 1, 2, row, row + 1,  GTK_EXPAND|GTK_FILL,0,5,0);
				gtk_widget_show(wid);
				break;
			case CFT_COLOR:
				label = gtk_label_new(fld->desc);
				gtk_table_attach(GTK_TABLE(table), label, 0, 1, row, row + 1,  0,0,0,0);
				gtk_widget_show(label);
				wid = od_emil_button_new();
				set_object_data(GTK_OBJECT(wid), fld, instance);
				od_emil_button_set_colors(OD_EMIL_BUTTON(wid), OD_EMIL_BUTTON_PRIMARY, NULL, (GdkColor *) ((gchar *) instance + fld->offset));
				gtk_signal_connect(GTK_OBJECT(wid), "activate", GTK_SIGNAL_FUNC(evt_color_clicked), cmc);
				gtk_table_attach(GTK_TABLE(table), wid, 1, 2, row, row + 1, GTK_EXPAND|GTK_FILL,0,5,0);
				gtk_widget_show(wid);
				break;
		}
		return table;
	}
	return NULL;
}

/* 1999-04-05 -	Build configuration widgetry for field <name> in given <cmc>. */
GtkWidget * cmc_field_build(CmdCfg *cmc, const gchar *name, gpointer instance)
{
	if((cmc != NULL) && (name != NULL))
	{
		CField	*fld;

		if((fld = field_find(cmc, name)) != NULL)
			return field_build(cmc, fld, instance, NULL, 0);
	}
	return NULL;
}

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

/* 1999-04-04 -	Remove a named field from given <cmc>. Rarely used, if ever. */
void cmc_field_remove(CmdCfg *cmc, const gchar *name)
{
	if((cmc != NULL) && (name != NULL))
	{
		CField	*fld;

		if((fld = field_find(cmc, name)) != NULL)		
		{
			cmc->fields = g_list_remove(cmc->fields, fld);
			field_destroy(fld);
		}
	}
}

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

/* 1999-04-05 -	Allocate memory to hold an instance of a command config structure, as described
**		by the fields list in <cmc>.
*/
gpointer cmc_instance_new(CmdCfg *cmc)
{
	if(cmc != NULL)
	{
		GList	*iter;
		gsize	size = 0, fsize;
		CField	*fld;

		for(iter = cmc->fields; iter != NULL; iter = g_list_next(iter))
		{
			fld = iter->data;

			fsize = fld->offset;
			switch(fld->type)
			{
				case CFT_INTEGER:
					fsize += sizeof(gint);
					break;
				case CFT_BOOLEAN:
					fsize += sizeof(gboolean);
					break;
				case CFT_SIZE:
					fsize += sizeof(gsize);
					break;
				case CFT_STRING:
					fsize += fld->field.string.size;
					break;
				case CFT_COLOR:
					fsize += sizeof(GdkColor);
					break;
			}
			if(fsize > size)
				size = fsize;
		}
		if(size)
			return g_malloc(size);
	}
	return NULL;
}

/* 1999-04-05 -	Create a new instance initialized to the current contents of the base one.
**		This is infinitely more useful than an empty instance.
*/
gpointer cmc_instance_new_from_base(CmdCfg *cmc)
{
	if(cmc != NULL)
	{
		gpointer	inst;

		if((inst = cmc_instance_new(cmc)) != NULL)
		{
			cmc_instance_copy(cmc, inst, cmc->base);
			return inst;
		}
	}
	return NULL;
}

/* 1999-04-05 -	Copy the instance <src> into <dst>. This would probably work as a straight
**		memory copy once the correct size of the instance is computed, but doing it
**		field-by-field seems somehow clearer, or something. There's no great rush.
*/
void cmc_instance_copy(CmdCfg *cmc, gpointer dst, gpointer src)
{
	if((cmc != NULL) && (dst != NULL) && (src != NULL))
	{
		GList	*iter;
		CField	*fld;

		for(iter = cmc->fields; iter != NULL; iter = g_list_next(iter))
		{
			fld = iter->data;

			switch(fld->type)
			{
				case CFT_INTEGER:
					*(gint *) ((gchar *) dst + fld->offset) = *(gint *) ((gchar *) src + fld->offset);
					break;
				case CFT_BOOLEAN:
					*(gboolean *) ((gchar *) dst + fld->offset) = *(gboolean *) ((gchar *) src + fld->offset);
					break;
				case CFT_SIZE:
					*(gsize *) ((gchar *) dst + fld->offset) = *(gsize *) ((gchar *) src + fld->offset);
					break;
				case CFT_STRING:
					str_strncpy((gchar *) dst + fld->offset, (gchar *) src + fld->offset, fld->field.string.size);
					break;
				case CFT_COLOR:
					*(GdkColor *) ((gchar *) dst + fld->offset) = *(GdkColor *) ((gchar *) src + fld->offset);
					break;
			}
		}
	}
}

/* 1999-04-05 -	Copy <src> into the given <cmc>'s base instance. The given source instance had
**		better be legit (i.e., created with the same cmc).
*/
void cmc_instance_copy_to_base(CmdCfg *cmc, gpointer src)
{
	if((cmc != NULL) && (src != NULL))
		cmc_instance_copy(cmc, cmc->base, src);
}

/* 1999-04-05 -	Copy <cmc>'s base instance into <dst>. */
void cmc_instance_copy_from_base(CmdCfg *cmc, gpointer dst)
{
	if((cmc != NULL) && (dst != NULL))
		cmc_instance_copy(cmc, dst, cmc->base);
}

/* 1999-04-05 -	Build a container widget containing the widgetry for configuring each field. */
GtkWidget * cmc_instance_build(CmdCfg *cmc, gpointer instance)
{
	GtkWidget	*table = NULL;

	if((cmc != NULL) && (instance != NULL))
	{
		GList		*iter;
		gint		row = 0;

		table = gtk_table_new(g_list_length(cmc->fields) - 1, 2, FALSE);
		for(iter = cmc->fields; iter != NULL; iter = g_list_next(iter))
		{
			if(strcmp(((CField *) iter->data)->name, "modified") == 0)
				continue;
			field_build(cmc, iter->data, instance, table, row++);
		}
	}
	return table;
}

/* 1999-04-05 -	Get the 'modified' state of given instance. */
gboolean cmc_instance_get_modified(CmdCfg *cmc, gpointer inst)
{
	return get_modified(cmc, inst);
}

/* 1999-04-05 -	Destroy an instance created by cmc_instance_new() above. Don't call this on
**		a static instance.
*/
void cmc_instance_destroy(gpointer inst)
{
	if(inst != NULL)
		g_free(inst);
}
