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