1 /*
2 ** 1999-04-04 -	Backend support for command sequence configuration. This deals with the specific
3 **		configuration data for command sequences. This data is used by individual commands,
4 **		and loaded/saved/visualized by the cfg_cmdseq.c module.
5 ** BUG BUG BUG	Currently handles only "flat", simple data types. Goes a long way, though.
6 */
7 
8 #include "gentoo.h"
9 
10 #include <stdio.h>
11 #include <string.h>
12 
13 #include "color_dialog.h"
14 #include "dialog.h"
15 #include "guiutil.h"
16 #include "list_dialog.h"
17 #include "odmultibutton.h"
18 #include "sizeutil.h"
19 #include "strutil.h"
20 #include "xmlutil.h"
21 
22 #include "cmdseq_config.h"
23 
24 /* ----------------------------------------------------------------------------------------- */
25 
26 #define	CMC_FIELD_SIZE	(24)
27 
28 struct CmdCfg {
29 	gchar		name[CSQ_NAME_SIZE];
30 	gpointer	base;
31 	GList		*fields;
32 };
33 
34 typedef enum { CFT_INTEGER = 0, CFT_BOOLEAN, CFT_ENUM, CFT_SIZE, CFT_STRING, CFT_PATH } CFType;
35 
36 typedef struct {
37 	gint	min, max;
38 } CFInt;
39 
40 typedef struct {
41 	gsize	num;
42 	gchar	*label[4];		/* Static limits... They rule! */
43 	gchar	*def;
44 } CFEnum;
45 
46 typedef struct {
47 	gsize	size;
48 	gunichar	separator;	/* Used for CFT_PATH. */
49 } CFStr;
50 
51 typedef struct {
52 	gsize	min, max;
53 	gsize	step;			/* Minimum change. */
54 } CFSize;
55 
56 typedef struct {
57 	CFType	type;
58 	gchar	name[CMC_FIELD_SIZE];
59 	gchar	*desc;
60 	gsize	offset;
61 
62 	union {
63 	CFInt	integer;
64 	CFEnum	fenum;
65 	CFSize	size;
66 	CFStr	string;
67 	}	field;
68 } CField;
69 
70 /* ----------------------------------------------------------------------------------------- */
71 
72 /* This holds "registered" command configs; these are the ones that are stored in the config
73 ** file, and dealt with by the command options config page. For now, this will in fact be all
74 ** cmc's created. That will change if I ever add nesting support, though.
75 */
76 static GList	*cmdcfg_list = NULL;
77 
78 /* ----------------------------------------------------------------------------------------- */
79 
80 static void	field_destroy(CField *fld);
81 
82 /* ----------------------------------------------------------------------------------------- */
83 
84 /* 1999-04-04 -	Create a new, empty, command config descriptor to which fields can then be
85 **		added. Also, in most cases you'd want to register it. The <base_instance>
86 **		is the (typically static) base configuration data store.
87 */
cmc_config_new(const gchar * cmdname,gpointer base_instance)88 CmdCfg * cmc_config_new(const gchar *cmdname, gpointer base_instance)
89 {
90 	CmdCfg	*cmc;
91 
92 	cmc = g_malloc(sizeof *cmc);
93 	g_strlcpy(cmc->name, cmdname, sizeof cmc->name);
94 	cmc->base   = base_instance;
95 	cmc->fields = NULL;
96 
97 	return cmc;
98 }
99 
100 /* 1999-04-05 -	Just get the name of a given config descriptor. Typically, this will be the
101 **		name of the command whose config data is described by it.
102 */
cmc_config_get_name(CmdCfg * cmc)103 const gchar * cmc_config_get_name(CmdCfg *cmc)
104 {
105 	if(cmc != NULL)
106 		return cmc->name;
107 	return NULL;
108 }
109 
110 /* 1999-04-05 -	Save given <cmc>'s base instance to file <out>, in XML format. */
cmc_config_base_save(CmdCfg * cmc,FILE * out)111 void cmc_config_base_save(CmdCfg *cmc, FILE *out)
112 {
113 	if((cmc != NULL) && (out != NULL))
114 	{
115 		const GList	*iter;
116 		CField		*fld;
117 
118 		xml_put_node_open(out, cmc->name);
119 		for(iter = cmc->fields; iter != NULL; iter = g_list_next(iter))
120 		{
121 			fld = iter->data;
122 
123 			if(strcmp(fld->name, "modified") == 0)
124 				continue;
125 			switch(fld->type)
126 			{
127 				case CFT_INTEGER:
128 					xml_put_integer(out, fld->name, *(gint *) ((gchar *) cmc->base + fld->offset));
129 					break;
130 				case CFT_BOOLEAN:
131 					xml_put_boolean(out, fld->name, *(gboolean *) ((gchar *) cmc->base + fld->offset));
132 					break;
133 				case CFT_ENUM:
134 					xml_put_uinteger(out, fld->name, *(gint *) ((gchar *) cmc->base + fld->offset));
135 					break;
136 				case CFT_SIZE:
137 					xml_put_uinteger(out, fld->name, *(guint *) ((gchar *) cmc->base + fld->offset));
138 					break;
139 				case CFT_STRING:
140 				case CFT_PATH:
141 					xml_put_text(out, fld->name, (gchar *) cmc->base + fld->offset);
142 					break;
143 			}
144 		}
145 		xml_put_node_close(out, cmc->name);
146 	}
147 }
148 
149 /* 1999-04-05 -	Load (parse) data from <node> into the base instance of <cmc>. */
cmc_config_base_load(CmdCfg * cmc,const XmlNode * node)150 void cmc_config_base_load(CmdCfg *cmc, const XmlNode *node)
151 {
152 	if((cmc != NULL) && (node != NULL))
153 	{
154 		const GList	*iter;
155 		CField		*fld;
156 
157 		for(iter = cmc->fields; iter != NULL; iter = g_list_next(iter))
158 		{
159 			fld = iter->data;
160 
161 			if(strcmp(fld->name, "modified") == 0)
162 				continue;
163 			switch(fld->type)
164 			{
165 				case CFT_INTEGER:
166 					xml_get_integer(node, fld->name, (gint *) ((gchar *) cmc->base + fld->offset));
167 					break;
168 				case CFT_BOOLEAN:
169 					xml_get_boolean(node, fld->name, (gboolean *) ((gchar *) cmc->base + fld->offset));
170 					break;
171 				case CFT_ENUM:
172 					xml_get_uinteger(node, fld->name, (guint *) ((gchar *) cmc->base + fld->offset));
173 					break;
174 				case CFT_SIZE:
175 					{
176 						guint	tmp;
177 
178 						xml_get_uinteger(node, fld->name, &tmp);
179 						*(gsize *) ((gchar *) cmc->base + fld->offset) = tmp;
180 					}
181 					break;
182 				case CFT_STRING:
183 				case CFT_PATH:
184 					xml_get_text_copy(node, fld->name, (gchar *) cmc->base + fld->offset, fld->field.string.size);
185 					break;
186 			}
187 		}
188 	}
189 }
190 
191 /* 1999-04-05 -	Compare two command config namess; useful to keep them sorted. */
cmp_cmc_name(gconstpointer a,gconstpointer b)192 static gint cmp_cmc_name(gconstpointer a, gconstpointer b)
193 {
194 	return strcmp(((CmdCfg *) a)->name, ((CmdCfg *) b)->name);
195 }
196 
197 /* 1999-04-05 -	Register given <cmc>, so iterators and other things become aware of it. */
cmc_config_register(CmdCfg * cmc)198 void cmc_config_register(CmdCfg *cmc)
199 {
200 	if(cmc != NULL)
201 		cmdcfg_list = g_list_insert_sorted(cmdcfg_list, cmc, cmp_cmc_name);
202 }
203 
204 /* 1999-04-05 -	Call a user-defined function for each registered command config descriptor. */
cmc_config_registered_foreach(void (* func)(CmdCfg * cmc,gpointer user),gpointer user)205 void cmc_config_registered_foreach(void (*func)(CmdCfg *cmc, gpointer user), gpointer user)
206 {
207 	const GList	*iter;
208 
209 	for(iter = cmdcfg_list; iter != NULL; iter = g_list_next(iter))
210 		func(iter->data, user);
211 }
212 
213 /* 1999-04-05 -	Return the number of registered command config descriptors. */
cmc_config_registered_num(void)214 guint cmc_config_registered_num(void)
215 {
216 	return g_list_length(cmdcfg_list);
217 }
218 
219 /* 1999-04-05 -	Unregister a <cmc>. */
cmc_config_unregister(CmdCfg * cmc)220 void cmc_config_unregister(CmdCfg *cmc)
221 {
222 	if(cmc != NULL)
223 	{
224 		GList	*link;
225 
226 		if((link = g_list_find(cmdcfg_list, cmc)) != NULL)
227 		{
228 			cmdcfg_list = g_list_remove_link(cmdcfg_list, link);
229 			g_list_free_1(link);
230 		}
231 	}
232 }
233 
234 /* 1999-04-05 -	Free a single field. */
free_field(gpointer data,gpointer user)235 static void free_field(gpointer data, gpointer user)
236 {
237 	field_destroy(data);
238 }
239 
240 /* 1999-04-05 -	Destroy a command config descriptor. Also causes it to be unregistered. */
cmc_config_destroy(CmdCfg * cmc)241 void cmc_config_destroy(CmdCfg *cmc)
242 {
243 	if(cmc != NULL)
244 	{
245 		cmc_config_unregister(cmc);
246 		if(cmc->fields != NULL)
247 		{
248 			g_list_foreach(cmc->fields, free_field, NULL);
249 			g_list_free(cmc->fields);
250 		}
251 		g_free(cmc);
252 	}
253 }
254 
255 /* ----------------------------------------------------------------------------------------- */
256 
field_new(CFType type,const gchar * name,const gchar * desc,gsize offset)257 static CField * field_new(CFType type, const gchar *name, const gchar *desc, gsize offset)
258 {
259 	CField	*fld;
260 
261 	fld = g_malloc(sizeof *fld);
262 
263 	fld->type = type;
264 	if(name != NULL)
265 		g_strlcpy(fld->name, name, sizeof fld->name);
266 	if(desc != NULL)
267 		fld->desc = strdup(desc);
268 	else
269 		fld->desc = NULL;
270 	fld->offset = offset;
271 
272 	return fld;
273 }
274 
field_destroy(CField * fld)275 static void field_destroy(CField *fld)
276 {
277 	if(fld != NULL)
278 	{
279 		if(fld->desc != NULL)
280 			g_free(fld->desc);
281 		g_free(fld);
282 	}
283 }
284 
285 /* 1999-04-04 -	Compare fields, sorting them in increasing offset order. */
cmp_field(gconstpointer a,gconstpointer b)286 static gint cmp_field(gconstpointer a, gconstpointer b)
287 {
288 	return (((CField *) a)->offset - ((CField *) b)->offset);
289 }
290 
field_add(CmdCfg * cmc,CField * fld)291 static void field_add(CmdCfg *cmc, CField *fld)
292 {
293 	if((cmc != NULL) && (fld != NULL))
294 	{
295 		if((cmc->fields == NULL) && strcmp(fld->name, "modified") != 0)
296 			g_error("CMDCFG: First field should be boolean named 'modified' (%s)", cmc->name);
297 
298 		cmc->fields = g_list_insert_sorted(cmc->fields, fld, cmp_field);
299 	}
300 }
301 
cmc_field_add_integer(CmdCfg * cmc,const gchar * name,const gchar * desc,gsize offset,gint min,gint max)302 void cmc_field_add_integer(CmdCfg *cmc, const gchar *name, const gchar *desc, gsize offset, gint min, gint max)
303 {
304 	if(cmc != NULL)
305 	{
306 		CField	*fld = field_new(CFT_INTEGER, name, desc, offset);
307 
308 		fld->field.integer.min = min;
309 		fld->field.integer.max = max;
310 
311 		field_add(cmc, fld);
312 	}
313 }
314 
cmc_field_add_boolean(CmdCfg * cmc,const gchar * name,const gchar * desc,gsize offset)315 void cmc_field_add_boolean(CmdCfg *cmc, const gchar *name, const gchar *desc, gsize offset)
316 {
317 	if(cmc != NULL)
318 		field_add(cmc, field_new(CFT_BOOLEAN, name, desc, offset));
319 }
320 
321 /* 2003-10-23 -	Split a string "looking|like|this" into enum labels. */
cmc_field_add_enum(CmdCfg * cmc,const gchar * name,const gchar * desc,gsize offset,const gchar * def)322 void cmc_field_add_enum(CmdCfg *cmc, const gchar *name, const gchar *desc, gsize offset, const gchar *def)
323 {
324 	if(cmc != NULL)
325 	{
326 		gchar	*str;
327 		CField	*fld = field_new(CFT_ENUM, name, desc, offset);
328 
329 		fld->field.fenum.num = 0;
330 		fld->field.fenum.def = g_strdup(def);
331 		for(str = fld->field.fenum.def; *str;)
332 		{
333 			fld->field.fenum.label[fld->field.fenum.num++] = str;
334 			while(*str && *str != '|')
335 				str++;
336 			if(*str == '|')
337 			{
338 				*str = '\0';
339 				str++;
340 			}
341 		}
342 		field_add(cmc, fld);
343 	}
344 }
345 
cmc_field_add_size(CmdCfg * cmc,const gchar * name,const gchar * desc,gsize offset,gsize min,gsize max,gsize step)346 void cmc_field_add_size(CmdCfg *cmc, const gchar *name, const gchar *desc, gsize offset, gsize min, gsize max, gsize step)
347 {
348 	if(cmc != NULL)
349 	{
350 		CField	*fld = field_new(CFT_SIZE, name, desc, offset);
351 
352 		fld->field.size.min = min;
353 		fld->field.size.max = max + step;
354 		fld->field.size.step = step;
355 
356 		field_add(cmc, fld);
357 	}
358 }
359 
cmc_field_add_string(CmdCfg * cmc,const gchar * name,const gchar * desc,gsize offset,gsize size)360 void cmc_field_add_string(CmdCfg *cmc, const gchar *name, const gchar *desc, gsize offset, gsize size)
361 {
362 	if(cmc != NULL)
363 	{
364 		CField	*fld = field_new(CFT_STRING, name, desc, offset);
365 
366 		fld->field.string.size = size;
367 
368 		field_add(cmc, fld);
369 	}
370 }
371 
cmc_field_add_path(CmdCfg * cmc,const gchar * name,const gchar * desc,gsize offset,gsize size,gunichar separator)372 void cmc_field_add_path(CmdCfg *cmc, const gchar *name, const gchar *desc, gsize offset, gsize size, gunichar separator)
373 {
374 	if(cmc != NULL)
375 	{
376 		CField	*fld = field_new(CFT_PATH, name, desc, offset);
377 
378 		/* Sneaky: use the 'string' member for CFT_PATH, too. */
379 		fld->field.string.size = size;
380 		fld->field.string.separator = separator;
381 
382 		field_add(cmc, fld);
383 	}
384 }
385 
386 /* 1999-04-05 -	Find a named field. */
field_find(CmdCfg * cmc,const gchar * name)387 static CField * field_find(CmdCfg *cmc, const gchar *name)
388 {
389 	if((cmc != NULL) && (name != NULL))
390 	{
391 		const GList	*iter;
392 
393 		for(iter = cmc->fields; iter != NULL; iter = g_list_next(iter))
394 		{
395 			if(strcmp(((CField *) iter->data)->name, name) == 0)
396 				return iter->data;
397 		}
398 	}
399 	return NULL;
400 }
401 
402 /* ----------------------------------------------------------------------------------------- */
403 
404 /* 1999-04-05 -	Attach some data handy to have in event handlers. */
set_object_data(GObject * obj,CField * fld,gpointer instance)405 static void set_object_data(GObject *obj, CField *fld, gpointer instance)
406 {
407 	if(obj != NULL)
408 	{
409 		g_object_set_data(obj, "field", fld);
410 		g_object_set_data(obj, "instance", instance);
411 	}
412 }
413 
414 /* 1999-04-05 -	Retrieve the data set by set_object_data() above. */
get_object_data(GObject * obj,CField ** fld,gpointer * instance)415 static void get_object_data(GObject *obj, CField **fld, gpointer *instance)
416 {
417 	if(obj != NULL)
418 	{
419 		if(fld != NULL)
420 			*fld = g_object_get_data(obj, "field");
421 		if(instance != NULL)
422 			*instance = g_object_get_data(obj, "instance");
423 	}
424 }
425 
426 /* 1999-04-05 -	Set the mandatory 'modified' field to <value>. */
set_modified(CmdCfg * cmc,gpointer instance,gboolean value)427 static void set_modified(CmdCfg *cmc, gpointer instance, gboolean value)
428 {
429 	if((cmc != NULL) && (instance != NULL))
430 	{
431 		CField	*fld;
432 
433 		if((fld = field_find(cmc, "modified")) != NULL)
434 			*(gboolean *) ((gchar *) instance + fld->offset) = value;
435 	}
436 }
437 
438 /* 1999-04-05 -	Return the value of the mandatory 'modified' field. */
get_modified(CmdCfg * cmc,gpointer instance)439 static gboolean get_modified(CmdCfg *cmc, gpointer instance)
440 {
441 	if((cmc != NULL) && (instance != NULL))
442 	{
443 		CField	*fld;
444 
445 		if((fld = field_find(cmc, "modified")) != NULL)
446 			return *(gboolean *) ((gchar *) instance + fld->offset);
447 	}
448 	return FALSE;		/* A safe default? */
449 }
450 
451 /* 1999-04-05 -	User clicked a check button (boolean field). Update field value. */
evt_boolean_clicked(GtkWidget * wid,gpointer user)452 static gint evt_boolean_clicked(GtkWidget *wid, gpointer user)
453 {
454 	CmdCfg		*cmc = user;
455 	CField		*fld = NULL;
456 	gpointer	instance = NULL;
457 
458 	get_object_data(G_OBJECT(wid), &fld, &instance);
459 
460 	if((cmc != NULL) && (fld != NULL) && (instance != NULL))
461 	{
462 		*(gboolean *) ((gchar *) instance + fld->offset) = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid));
463 		set_modified(cmc, instance, TRUE);
464 	}
465 	return TRUE;
466 }
467 
evt_enum_changed(GtkWidget * wid,gpointer user)468 static gint evt_enum_changed(GtkWidget *wid, gpointer user)
469 {
470 	if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid)))
471 	{
472 		CmdCfg		*cmc = user;
473 		CField		*fld = NULL;
474 		gpointer	instance = NULL;
475 
476 		get_object_data(G_OBJECT(wid), &fld, &instance);
477 		if(cmc && fld && instance)
478 		{
479 			*(gint *) ((gchar *) instance + fld->offset) = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(wid), "value"));
480 			set_modified(cmc, instance, TRUE);
481 		}
482 	}
483 	return TRUE;
484 }
485 
486 /* 1999-04-05 -	A string has been modified, so we need to update the instance. */
evt_string_changed(GtkWidget * wid,gpointer user)487 static gint evt_string_changed(GtkWidget *wid, gpointer user)
488 {
489 	CmdCfg		*cmc = user;
490 	CField		*fld;
491 	gpointer	instance;
492 
493 	get_object_data(G_OBJECT(wid), &fld, &instance);
494 
495 	if((cmc != NULL) && (fld != NULL) && (instance != NULL))
496 	{
497 		const gchar	*text;
498 
499 		if((text = gtk_entry_get_text(GTK_ENTRY(wid))) != NULL)
500 			g_strlcpy((gchar *) instance + fld->offset, text, fld->field.string.size);
501 		set_modified(cmc, instance, TRUE);
502 	}
503 	return TRUE;
504 }
505 
evt_size_changed(GObject * obj,gpointer user)506 static gint evt_size_changed(GObject *obj, gpointer user)
507 {
508 	CmdCfg		*cmc = user;
509 	CField		*fld;
510 	gpointer	instance;
511 
512 	get_object_data(obj, &fld, &instance);
513 
514 	if((cmc != NULL) && (fld != NULL) && (instance != NULL))
515 	{
516 		gchar		buf[32];
517 		gsize		value = gtk_adjustment_get_value(GTK_ADJUSTMENT(obj));
518 		GtkWidget	*label = g_object_get_data(obj, "label");
519 
520 		value *= fld->field.size.step;
521 		if(fld->field.size.step >= 512)
522 			sze_put_offset(buf, sizeof buf, value, SZE_KB, 0, ',');
523 		else
524 			sze_put_offset(buf, sizeof buf, value, SZE_BYTES, 0, ',');
525 		gtk_label_set_text(GTK_LABEL(label), buf);
526 
527 		*(gsize *) ((gchar *) instance + fld->offset) = value;
528 		set_modified(cmc, instance, TRUE);
529 	}
530 
531 	return TRUE;
532 }
533 
evt_path_pick_clicked(GtkWidget * wid,gpointer user)534 static void evt_path_pick_clicked(GtkWidget *wid, gpointer user)
535 {
536 	CField		*fld;
537 	gpointer	instance;
538 	GtkEntry	*entry;
539 
540 	get_object_data(G_OBJECT(wid), &fld, &instance);
541 	ldl_dialog_sync_new_wait((gchar *) instance + fld->offset, fld->field.string.size, fld->field.string.separator, fld->desc);
542 	entry = GTK_ENTRY(g_object_get_data(G_OBJECT(wid), "entry"));
543 	gtk_entry_set_text(entry, (gchar *) instance + fld->offset);
544 }
545 
546 /* 2002-08-01 -	Build a neatly right-aligned label, and stuff it into <table>. */
label_build(const gchar * text,GtkWidget * grid,gint x,gint y)547 static void label_build(const gchar *text, GtkWidget *grid, gint x, gint y)
548 {
549 	GtkWidget	*lab;
550 
551 	lab = gtk_label_new(text);
552 	gtk_widget_set_halign(lab, GTK_ALIGN_END);
553 	gtk_widget_set_margin_end(lab, 5);
554 	gtk_grid_attach(GTK_GRID(grid), lab, x, y, 1, 1);
555 }
556 
557 /* 1999-04-05 -	Build config widgetry for given <fld>. This function is a bit schizophrenic, since it
558 **		does one of two things: if <grid> is non-NULL, the widgetry is packed into it, which
559 **		looks neat. If it is NULL, a new grid is created, packed, and returned.
560 */
field_build(CmdCfg * cmc,CField * fld,gpointer instance,GtkWidget * grid,gint row)561 static GtkWidget * field_build(CmdCfg *cmc, CField *fld, gpointer instance, GtkWidget *grid, gint row)
562 {
563 	if((cmc != NULL) && (fld != NULL) && (instance != NULL))
564 	{
565 		GtkWidget	*wid, *hbox, *label;
566 		GtkAdjustment	*adj;
567 
568 		if(grid == NULL)
569 		{
570 			grid = gtk_grid_new();
571 			row = 0;
572 		}
573 		switch(fld->type)
574 		{
575 			case CFT_INTEGER:
576 				label_build(fld->desc, grid, 0, row);
577 				wid = gtk_spin_button_new(NULL, 1, 0);
578 				set_object_data(G_OBJECT(wid), fld, instance);
579 				adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(wid));
580 				gtk_adjustment_clamp_page(adj, fld->field.integer.min, fld->field.integer.max);
581 				gtk_adjustment_set_value(adj, *(gint *) ((gchar *) instance + fld->offset));
582 				gtk_widget_set_hexpand(wid, TRUE);
583 				gtk_widget_set_halign(wid, GTK_ALIGN_FILL);
584 				gtk_grid_attach(GTK_GRID(grid), wid, 1, row, 1, 1);
585 				break;
586 			case CFT_BOOLEAN:
587 				wid = gtk_check_button_new_with_label(fld->desc);
588 				set_object_data(G_OBJECT(wid), fld, instance);
589 				gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(wid), *(gboolean *) ((gchar *) instance + fld->offset));
590 				g_signal_connect(G_OBJECT(wid), "clicked", G_CALLBACK(evt_boolean_clicked), cmc);
591 				gtk_widget_set_hexpand(wid, TRUE);
592 				gtk_widget_set_halign(wid, GTK_ALIGN_FILL);
593 				gtk_grid_attach(GTK_GRID(grid), wid, 0, row, 2, 1);
594 				break;
595 			case CFT_ENUM:
596 				wid = gtk_frame_new(fld->desc);
597 				{
598 					GtkWidget	*vbox, *radio[sizeof fld->field.fenum.label / sizeof *fld->field.fenum.label];
599 					gint		i, current = *(gint *) ((gchar *) instance + fld->offset);
600 
601 					vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
602 					gui_radio_group_new(fld->field.fenum.num, (const gchar **) fld->field.fenum.label, radio);
603 					for(i = 0; i < fld->field.fenum.num; i++)
604 					{
605 						set_object_data(G_OBJECT(radio[i]), fld, instance);
606 						g_object_set_data(G_OBJECT(radio[i]), "value", GINT_TO_POINTER(i));
607 						g_signal_connect(G_OBJECT(radio[i]), "toggled", G_CALLBACK(evt_enum_changed), cmc);
608 						gtk_box_pack_start(GTK_BOX(vbox), radio[i], FALSE, FALSE, 0);
609 						if(i == current)
610 							gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[i]), TRUE);
611 					}
612 					gtk_container_add(GTK_CONTAINER(wid), vbox);
613 				}
614 				gtk_widget_set_hexpand(wid, TRUE);
615 				gtk_widget_set_halign(wid, GTK_ALIGN_FILL);
616 				gtk_grid_attach(GTK_GRID(grid), wid, 0, row, 2, 1);
617 				break;
618 			case CFT_SIZE:
619 				label_build(fld->desc, grid, 0, row);
620 				hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
621 				adj = gtk_adjustment_new((gfloat) *(gint *) ((gchar *) instance + fld->offset) / fld->field.size.step,
622 							 fld->field.size.min / fld->field.size.step,
623 							 fld->field.size.max / fld->field.size.step,
624 							 1, 1, 0);
625 
626 				set_object_data(G_OBJECT(adj), fld, instance);
627 				wid = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, adj);
628 				gtk_scale_set_draw_value(GTK_SCALE(wid), FALSE);
629 				gtk_box_pack_start(GTK_BOX(hbox), wid, TRUE, TRUE, 0);
630 				label = gtk_label_new("");
631 				gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
632 				g_object_set_data(G_OBJECT(adj), "label", label);
633 				g_signal_connect(G_OBJECT(adj), "value_changed", G_CALLBACK(evt_size_changed), cmc);
634 				gtk_widget_set_hexpand(hbox, TRUE);
635 				gtk_widget_set_halign(hbox, GTK_ALIGN_FILL);
636 				gtk_grid_attach(GTK_GRID(grid), hbox, 1, row, 1, 1);
637 				break;
638 			case CFT_STRING:
639 				label_build(fld->desc, grid, 0, row);
640 				wid = gtk_entry_new();
641 				gtk_entry_set_max_length(GTK_ENTRY(wid), fld->field.string.size - 1);
642 				set_object_data(G_OBJECT(wid), fld, instance);
643 				gtk_entry_set_text(GTK_ENTRY(wid), (gchar *) instance + fld->offset);
644 				g_signal_connect(G_OBJECT(wid), "changed", G_CALLBACK(evt_string_changed), cmc);
645 				gtk_widget_set_hexpand(wid, TRUE);
646 				gtk_widget_set_halign(wid, GTK_ALIGN_FILL);
647 				gtk_grid_attach(GTK_GRID(grid), wid, 1, row, 1, 1);
648 				break;
649 			case CFT_PATH:
650 				{
651 					GtkWidget	*pick;
652 
653 					label_build(fld->desc, grid, 0, row);
654 					hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
655 					wid = gtk_entry_new();
656 					gtk_entry_set_max_length(GTK_ENTRY(wid), fld->field.string.size - 1);
657 					set_object_data(G_OBJECT(wid), fld, instance);
658 					gtk_entry_set_text(GTK_ENTRY(wid), (gchar *) instance + fld->offset);
659 					g_signal_connect(G_OBJECT(wid), "changed", G_CALLBACK(evt_string_changed), cmc);
660 					gtk_box_pack_start(GTK_BOX(hbox), wid, TRUE, TRUE, 0);
661 					pick = gui_details_button_new();
662 					set_object_data(G_OBJECT(pick), fld, instance);
663 					g_object_set_data(G_OBJECT(pick), "entry", wid);
664 					g_signal_connect(G_OBJECT(pick), "clicked", G_CALLBACK(evt_path_pick_clicked), cmc);
665 					gtk_box_pack_start(GTK_BOX(hbox), pick, FALSE, FALSE, 0);
666 					gtk_widget_set_hexpand(hbox, TRUE);
667 					gtk_widget_set_halign(hbox, GTK_ALIGN_FILL);
668 					gtk_grid_attach(GTK_GRID(grid), hbox, 1, row, 1, 1);
669 				}
670 				break;
671 		}
672 		return grid;
673 	}
674 	return NULL;
675 }
676 
677 /* 1999-04-05 -	Build configuration widgetry for field <name> in given <cmc>. */
cmc_field_build(CmdCfg * cmc,const gchar * name,gpointer instance)678 GtkWidget * cmc_field_build(CmdCfg *cmc, const gchar *name, gpointer instance)
679 {
680 	if((cmc != NULL) && (name != NULL))
681 	{
682 		CField	*fld;
683 
684 		if((fld = field_find(cmc, name)) != NULL)
685 			return field_build(cmc, fld, instance, NULL, 0);
686 	}
687 	return NULL;
688 }
689 
690 /* ----------------------------------------------------------------------------------------- */
691 
692 /* 1999-04-04 -	Remove a named field from given <cmc>. Rarely used, if ever. */
cmc_field_remove(CmdCfg * cmc,const gchar * name)693 void cmc_field_remove(CmdCfg *cmc, const gchar *name)
694 {
695 	if((cmc != NULL) && (name != NULL))
696 	{
697 		CField	*fld;
698 
699 		if((fld = field_find(cmc, name)) != NULL)
700 		{
701 			cmc->fields = g_list_remove(cmc->fields, fld);
702 			field_destroy(fld);
703 		}
704 	}
705 }
706 
707 /* ----------------------------------------------------------------------------------------- */
708 
709 /* 1999-04-05 -	Allocate memory to hold an instance of a command config structure, as described
710 **		by the fields list in <cmc>.
711 */
cmc_instance_new(CmdCfg * cmc)712 gpointer cmc_instance_new(CmdCfg *cmc)
713 {
714 	if(cmc != NULL)
715 	{
716 		const GList	*iter;
717 		gsize		size = 0, fsize;
718 		CField		*fld;
719 
720 		for(iter = cmc->fields; iter != NULL; iter = g_list_next(iter))
721 		{
722 			fld = iter->data;
723 
724 			fsize = fld->offset;
725 			switch(fld->type)
726 			{
727 				case CFT_INTEGER:
728 					fsize += sizeof (gint);
729 					break;
730 				case CFT_BOOLEAN:
731 					fsize += sizeof (gboolean);
732 					break;
733 				case CFT_ENUM:
734 					fsize += sizeof (gint);
735 					break;
736 				case CFT_SIZE:
737 					fsize += sizeof (gsize);
738 					break;
739 				case CFT_STRING:
740 				case CFT_PATH:
741 					fsize += fld->field.string.size;
742 					break;
743 			}
744 			if(fsize > size)
745 				size = fsize;
746 		}
747 		if(size)
748 			return g_malloc(size);
749 	}
750 	return NULL;
751 }
752 
753 /* 1999-04-05 -	Create a new instance initialized to the current contents of the base one.
754 **		This is infinitely more useful than an empty instance.
755 */
cmc_instance_new_from_base(CmdCfg * cmc)756 gpointer cmc_instance_new_from_base(CmdCfg *cmc)
757 {
758 	if(cmc != NULL)
759 	{
760 		gpointer	inst;
761 
762 		if((inst = cmc_instance_new(cmc)) != NULL)
763 		{
764 			cmc_instance_copy(cmc, inst, cmc->base);
765 			return inst;
766 		}
767 	}
768 	return NULL;
769 }
770 
771 /* 1999-04-05 -	Copy the instance <src> into <dst>. This would probably work as a straight
772 **		memory copy once the correct size of the instance is computed, but doing it
773 **		field-by-field seems somehow clearer, or something. There's no great rush.
774 */
cmc_instance_copy(CmdCfg * cmc,gpointer dst,gpointer src)775 void cmc_instance_copy(CmdCfg *cmc, gpointer dst, gpointer src)
776 {
777 	if((cmc != NULL) && (dst != NULL) && (src != NULL))
778 	{
779 		const GList	*iter;
780 		CField		*fld;
781 
782 		for(iter = cmc->fields; iter != NULL; iter = g_list_next(iter))
783 		{
784 			fld = iter->data;
785 
786 			switch(fld->type)
787 			{
788 				case CFT_INTEGER:
789 					*(gint *) ((gchar *) dst + fld->offset) = *(gint *) ((gchar *) src + fld->offset);
790 					break;
791 				case CFT_BOOLEAN:
792 					*(gboolean *) ((gchar *) dst + fld->offset) = *(gboolean *) ((gchar *) src + fld->offset);
793 					break;
794 				case CFT_ENUM:
795 					*(gint *) ((gchar *) dst + fld->offset) = *(gboolean *) ((gchar *) src + fld->offset);
796 					break;
797 				case CFT_SIZE:
798 					*(gsize *) ((gchar *) dst + fld->offset) = *(gsize *) ((gchar *) src + fld->offset);
799 					break;
800 				case CFT_STRING:
801 				case CFT_PATH:
802 					g_strlcpy((gchar *) dst + fld->offset, (gchar *) src + fld->offset, fld->field.string.size);
803 					break;
804 			}
805 		}
806 	}
807 }
808 
809 /* 1999-04-05 -	Copy <src> into the given <cmc>'s base instance. The given source instance had
810 **		better be legit (i.e., created with the same cmc).
811 */
cmc_instance_copy_to_base(CmdCfg * cmc,gpointer src)812 void cmc_instance_copy_to_base(CmdCfg *cmc, gpointer src)
813 {
814 	if((cmc != NULL) && (src != NULL))
815 		cmc_instance_copy(cmc, cmc->base, src);
816 }
817 
818 /* 1999-04-05 -	Copy <cmc>'s base instance into <dst>. */
cmc_instance_copy_from_base(CmdCfg * cmc,gpointer dst)819 void cmc_instance_copy_from_base(CmdCfg *cmc, gpointer dst)
820 {
821 	if((cmc != NULL) && (dst != NULL))
822 		cmc_instance_copy(cmc, dst, cmc->base);
823 }
824 
825 /* 1999-04-05 -	Build a container widget containing the widgetry for configuring each field. */
cmc_instance_build(CmdCfg * cmc,gpointer instance)826 GtkWidget * cmc_instance_build(CmdCfg *cmc, gpointer instance)
827 {
828 	GtkWidget	*grid = NULL;
829 
830 	if((cmc != NULL) && (instance != NULL))
831 	{
832 		const GList	*iter;
833 		gint		row = 0;
834 
835 		grid = gtk_grid_new();
836 		for(iter = cmc->fields; iter != NULL; iter = g_list_next(iter))
837 		{
838 			if(strcmp(((CField *) iter->data)->name, "modified") == 0)
839 				continue;
840 			field_build(cmc, iter->data, instance, grid, row++);
841 		}
842 	}
843 	return grid;
844 }
845 
846 /* 1999-04-05 -	Get the 'modified' state of given instance. */
cmc_instance_get_modified(CmdCfg * cmc,gpointer inst)847 gboolean cmc_instance_get_modified(CmdCfg *cmc, gpointer inst)
848 {
849 	return get_modified(cmc, inst);
850 }
851 
852 /* 1999-04-05 -	Destroy an instance created by cmc_instance_new() above. Don't call this on
853 **		a static instance.
854 */
cmc_instance_destroy(gpointer inst)855 void cmc_instance_destroy(gpointer inst)
856 {
857 	if(inst != NULL)
858 		g_free(inst);
859 }
860