6416e6ce99906f243e579471acf41e1cd1d18cb5
[xscreensaver] / driver / demo-Gtk-conf.c
1 /* demo-Gtk-conf.c --- implements the dynamic configuration dialogs.
2  * xscreensaver, Copyright (c) 2001, 2003, 2004 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #if defined(HAVE_GTK) && defined(HAVE_XML)   /* whole file */
18
19 #include <xscreensaver-intl.h>
20
21 #include <stdlib.h>
22
23 #ifdef HAVE_UNISTD_H
24 # include <unistd.h>
25 #endif
26
27 #include <stdio.h>
28 #include <string.h>
29 #include <ctype.h>
30
31 /*
32  * Both of these workarounds can be removed when support for ancient
33  * libxml versions is dropped.  versions 1.8.11 and 2.3.4 provide the
34  * correct fixes.
35  */
36
37 /* 
38  * Older libxml polluted the global headerspace, while libxml2 fixed
39  * this.  To support both old and recent libxmls, we have this
40  * workaround.
41  */
42 #ifdef HAVE_OLD_XML_HEADERS
43 # include <parser.h>
44 #else /* ! HAVE_OLD_XML_HEADERS */
45 # include <libxml/parser.h> 
46 #endif /* HAVE_OLD_XML_HEADERS */
47
48 /* 
49  * handle non-native spelling mistakes in earlier versions and provide
50  * the source-compat fix for this that may not be in older versions.
51  */
52 #ifndef xmlChildrenNode
53 # if LIBXML_VERSION >= 20000
54 #  define xmlChildrenNode children
55 #  define xmlRootNode children
56 # else
57 #  define xmlChildrenNode childs
58 #  define xmlRootNode root
59 # endif /* LIBXML_VERSION */
60 #endif /* xmlChildrenNode */
61
62 #include <gtk/gtk.h>
63
64 #include "demo-Gtk-conf.h"
65
66  
67 extern const char *blurb (void);
68
69
70 const char *hack_configuration_path = HACK_CONFIGURATION_PATH;
71
72 static gboolean debug_p = FALSE;
73
74
75 typedef enum {
76   COMMAND,
77   FAKE,
78   DESCRIPTION,
79   FAKEPREVIEW,
80   STRING,
81   FILENAME,
82   SLIDER,
83   SPINBUTTON,
84   BOOLEAN,
85   SELECT,
86   SELECT_OPTION
87 } parameter_type;
88
89
90 typedef struct {
91
92   parameter_type type;
93
94   xmlChar *id;          /* widget name */
95   xmlChar *label;       /* heading label, or null */
96
97   /* command, fake, description, fakepreview, string, file
98    */
99   xmlChar *string;      /* file name, description, whatever. */
100
101   /* slider, spinbutton
102    */
103   xmlChar *low_label;   /* label for the left side */
104   xmlChar *high_label;  /* label for the right side */
105   float low;            /* minimum value */
106   float high;           /* maximum value */
107   float value;          /* default value */
108   gboolean integer_p;   /* whether the range is integral, or real */
109   xmlChar *arg;         /* command-line option to set (substitute "%") */
110   gboolean invert_p;    /* whether to flip the value and pretend the
111                            range goes from hi-low instead of low-hi. */
112
113   /* boolean, select-option
114    */
115   xmlChar *arg_set;     /* command-line option to set for "yes", or null */
116   xmlChar *arg_unset;   /* command-line option to set for "no", or null */
117   xmlChar *test;        /* #### no idea - enablement? */
118
119   /* select
120    */
121   GList *options;
122
123   /* select_option
124    */
125   GList *enable;
126
127   GtkWidget *widget;
128
129 } parameter;
130
131
132 static parameter *make_select_option (const char *file, xmlNodePtr);
133 static void make_parameter_widget (const char *filename,
134                                    parameter *, GtkWidget *, int *);
135 static void browse_button_cb (GtkButton *button, gpointer user_data);
136
137
138 /* Frees the parameter object and all strings and sub-parameters.
139    Does not destroy the widget, if any.
140  */
141 static void
142 free_parameter (parameter *p)
143 {
144   GList *rest;
145   if (p->id)         free (p->id);
146   if (p->label)      free (p->label);
147   if (p->string)     free (p->string);
148   if (p->low_label)  free (p->low_label);
149   if (p->high_label) free (p->high_label);
150   if (p->arg)        free (p->arg);
151   if (p->arg_set)    free (p->arg_set);
152   if (p->arg_unset)  free (p->arg_unset);
153   if (p->test)       free (p->test);
154
155   for (rest = p->options; rest; rest = rest->next)
156     if (rest->data)
157       free_parameter ((parameter *) rest->data);
158
159   for (rest = p->enable; rest; rest = rest->next)
160     {
161       free ((char *) rest->data);
162       rest->data = 0;
163     }
164   if (p->enable) g_list_free (p->enable);
165
166   memset (p, ~0, sizeof(*p));
167   free (p);
168 }
169
170
171 /* Debugging: dumps out a `parameter' structure.
172  */
173 #if 0
174 void
175 describe_parameter (FILE *out, parameter *p)
176 {
177   fprintf (out, "<");
178   switch (p->type)
179     {
180     case COMMAND:     fprintf (out, "command");      break;
181     case FAKE:        fprintf (out, "fake");         break;
182     case DESCRIPTION: fprintf (out, "_description"); break;
183     case FAKEPREVIEW: fprintf (out, "fakepreview");  break;
184     case STRING:      fprintf (out, "string");       break;
185     case FILENAME:    fprintf (out, "filename");     break;
186     case SLIDER:      fprintf (out, "number type=\"slider\"");     break;
187     case SPINBUTTON:  fprintf (out, "number type=\"spinbutton\""); break;
188     case BOOLEAN:     fprintf (out, "boolean");      break;
189     case SELECT:      fprintf (out, "select");       break;
190     default: abort(); break;
191     }
192   if (p->id)         fprintf (out, " id=\"%s\"",            p->id);
193   if (p->label)      fprintf (out, " _label=\"%s\"",        p->label);
194   if (p->string && p->type != DESCRIPTION)
195                      fprintf (out, " string=\"%s\"",        p->string);
196   if (p->low_label)  fprintf (out, " _low-label=\"%s\"",    p->low_label);
197   if (p->high_label) fprintf (out, " _high-label=\"%s\"",   p->high_label);
198   if (p->low)        fprintf (out, " low=\"%.2f\"",         p->low);
199   if (p->high)       fprintf (out, " high=\"%.2f\"",        p->high);
200   if (p->value)      fprintf (out, " default=\"%.2f\"",     p->value);
201   if (p->arg)        fprintf (out, " arg=\"%s\"",           p->arg);
202   if (p->invert_p)   fprintf (out, " convert=\"invert\"");
203   if (p->arg_set)    fprintf (out, " arg-set=\"%s\"",       p->arg_set);
204   if (p->arg_unset)  fprintf (out, " arg-unset=\"%s\"",     p->arg_unset);
205   if (p->test)       fprintf (out, " test=\"%s\"",          p->test);
206   fprintf (out, ">\n");
207
208   if (p->type == SELECT)
209     {
210       GList *opt;
211       for (opt = p->options; opt; opt = opt->next)
212         {
213           parameter *o = (parameter *) opt->data;
214           if (o->type != SELECT_OPTION) abort();
215           fprintf (out, "  <option");
216           if (o->id)        fprintf (out, " id=\"%s\"",        o->id);
217           if (o->label)     fprintf (out, " _label=\"%s\"",    o->label);
218           if (o->arg_set)   fprintf (out, " arg-set=\"%s\"",   o->arg_set);
219           if (o->arg_unset) fprintf (out, " arg-unset=\"%s\"", o->arg_unset);
220           if (o->test)      fprintf (out, " test=\"%s\"",      o->test);
221           if (o->enable)
222             {
223               GList *e;
224               fprintf (out, " enable=\"");
225               for (e = o->enable; e; e = e->next)
226                 fprintf (out, "%s%s", (char *) e->data, (e->next ? "," : ""));
227               fprintf (out, "\"");
228             }
229           fprintf (out, ">\n");
230         }
231       fprintf (out, "</select>\n");
232     }
233   else if (p->type == DESCRIPTION)
234     {
235       if (p->string)
236         fprintf (out, "  %s\n", p->string);
237       fprintf (out, "</_description>\n");
238     }
239 }
240 #endif /* 0 */
241
242
243 /* Like xmlGetProp() but parses a float out of the string.
244    If the number was expressed as a float and not an integer
245    (that is, the string contained a decimal point) then
246    `floatp' is set to TRUE.  Otherwise, it is unchanged.
247  */
248 static float
249 xml_get_float (xmlNodePtr node, const xmlChar *name, gboolean *floatpP)
250 {
251   const char *s = (char *) xmlGetProp (node, name);
252   float f;
253   char c;
254   if (!s || 1 != sscanf (s, "%f %c", &f, &c))
255     return 0;
256   else
257     {
258       if (strchr (s, '.')) *floatpP = TRUE;
259       return f;
260     }
261 }
262
263
264 static void sanity_check_parameter (const char *filename,
265                                     const xmlChar *node_name,
266                                     parameter *p);
267
268 /* Allocates and returns a new `parameter' object based on the
269    properties in the given XML node.  Returns 0 if there's nothing
270    to create (comment, or unknown tag.)
271  */
272 static parameter *
273 make_parameter (const char *filename, xmlNodePtr node)
274 {
275   parameter *p;
276   const char *name = (char *) node->name;
277   const char *convert;
278   gboolean floatp = FALSE;
279
280   if (node->type == XML_COMMENT_NODE)
281     return 0;
282
283   p = calloc (1, sizeof(*p));
284
285   if (!name) abort();
286   else if (!strcmp (name, "command"))      p->type = COMMAND;
287   else if (!strcmp (name, "fullcommand"))  p->type = COMMAND;
288   else if (!strcmp (name, "_description")) p->type = DESCRIPTION;
289   else if (!strcmp (name, "fakepreview"))  p->type = FAKEPREVIEW;
290   else if (!strcmp (name, "fake"))         p->type = FAKE;
291   else if (!strcmp (name, "boolean"))      p->type = BOOLEAN;
292   else if (!strcmp (name, "string"))       p->type = STRING;
293   else if (!strcmp (name, "file"))         p->type = FILENAME;
294   else if (!strcmp (name, "number"))       p->type = SPINBUTTON;
295   else if (!strcmp (name, "select"))       p->type = SELECT;
296   else
297     {
298       if (debug_p)
299         fprintf (stderr, "%s: WARNING: %s: unknown tag: \"%s\"\n",
300                  blurb(), filename, name);
301       free (p);
302       return 0;
303     }
304
305   if (p->type == SPINBUTTON)
306     {
307       const char *type = (char *) xmlGetProp (node, (xmlChar *) "type");
308       if (!type || !strcmp (type, "spinbutton")) p->type = SPINBUTTON;
309       else if (!strcmp (type, "slider"))         p->type = SLIDER;
310       else
311         {
312           if (debug_p)
313             fprintf (stderr, "%s: WARNING: %s: unknown %s type: \"%s\"\n",
314                      blurb(), filename, name, type);
315           free (p);
316           return 0;
317         }
318     }
319   else if (p->type == DESCRIPTION)
320     {
321       if (node->xmlChildrenNode &&
322           node->xmlChildrenNode->type == XML_TEXT_NODE &&
323           !node->xmlChildrenNode->next)
324         p->string = (xmlChar *)
325           strdup ((char *) node->xmlChildrenNode->content);
326     }
327
328   p->id         = xmlGetProp (node, (xmlChar *) "id");
329   p->label      = xmlGetProp (node, (xmlChar *) "_label");
330   p->low_label  = xmlGetProp (node, (xmlChar *) "_low-label");
331   p->high_label = xmlGetProp (node, (xmlChar *) "_high-label");
332   p->low        = xml_get_float (node, (xmlChar *) "low",     &floatp);
333   p->high       = xml_get_float (node, (xmlChar *) "high",    &floatp);
334   p->value      = xml_get_float (node, (xmlChar *) "default", &floatp);
335   p->integer_p  = !floatp;
336   convert       = (char *) xmlGetProp (node, (xmlChar *) "convert");
337   p->invert_p   = (convert && !strcmp (convert, "invert"));
338   p->arg        = xmlGetProp (node, (xmlChar *) "arg");
339   p->arg_set    = xmlGetProp (node, (xmlChar *) "arg-set");
340   p->arg_unset  = xmlGetProp (node, (xmlChar *) "arg-unset");
341   p->test       = xmlGetProp (node, (xmlChar *) "test");
342
343   /* Check for missing decimal point */
344   if (debug_p &&
345       p->integer_p &&
346       (p->high != p->low) &&
347       (p->high - p->low) <= 1)
348     fprintf (stderr,
349             "%s: WARNING: %s: %s: range [%.1f, %.1f] shouldn't be integral!\n",
350              blurb(), filename, p->id,
351              p->low, p->high);
352
353   if (p->type == SELECT)
354     {
355       xmlNodePtr kids;
356       for (kids = node->xmlChildrenNode; kids; kids = kids->next)
357         {
358           parameter *s = make_select_option (filename, kids);
359           if (s)
360             p->options = g_list_append (p->options, s);
361         }
362     }
363
364   sanity_check_parameter (filename, (const xmlChar *) name, p);
365
366   return p;
367 }
368
369
370 /* Allocates and returns a new SELECT_OPTION `parameter' object based
371    on the properties in the given XML node.  Returns 0 if there's nothing
372    to create (comment, or unknown tag.)
373  */
374 static parameter *
375 make_select_option (const char *filename, xmlNodePtr node)
376 {
377   if (node->type == XML_COMMENT_NODE)
378     return 0;
379   else if (node->type != XML_ELEMENT_NODE)
380     {
381       if (debug_p)
382         fprintf (stderr,
383                  "%s: WARNING: %s: %s: unexpected child tag type %d\n",
384                  blurb(), filename, node->name, (int)node->type);
385       return 0;
386     }
387   else if (strcmp ((char *) node->name, "option"))
388     {
389       if (debug_p)
390         fprintf (stderr,
391                  "%s: WARNING: %s: %s: child not an option tag: \"%s\"\n",
392                  blurb(), filename, node->name, node->name);
393       return 0;
394     }
395   else
396     {
397       parameter *s = calloc (1, sizeof(*s));
398       char *enable, *e;
399
400       s->type       = SELECT_OPTION;
401       s->id         = xmlGetProp (node, (xmlChar *) "id");
402       s->label      = xmlGetProp (node, (xmlChar *) "_label");
403       s->arg_set    = xmlGetProp (node, (xmlChar *) "arg-set");
404       s->arg_unset  = xmlGetProp (node, (xmlChar *) "arg-unset");
405       s->test       = xmlGetProp (node, (xmlChar *) "test");
406       enable = (char*)xmlGetProp (node, (xmlChar *) "enable");
407
408       if (enable)
409         {
410           enable = strdup (enable);
411           e = strtok (enable, ", ");
412           while (e)
413             {
414               s->enable = g_list_append (s->enable, strdup (e));
415               e = strtok (0, ", ");
416             }
417           free (enable);
418         }
419
420       sanity_check_parameter (filename, node->name, s);
421       return s;
422     }
423 }
424
425
426 /* Rudimentary check to make sure someone hasn't typed "arg-set="
427    when they should have typed "arg=", etc.
428  */
429 static void
430 sanity_check_parameter (const char *filename, const xmlChar *node_name,
431                         parameter *p)
432 {
433   struct {
434     gboolean id;
435     gboolean label;
436     gboolean string;
437     gboolean low_label;
438     gboolean high_label;
439     gboolean low;
440     gboolean high;
441     gboolean value;
442     gboolean arg;
443     gboolean invert_p;
444     gboolean arg_set;
445     gboolean arg_unset;
446   } allowed, require;
447
448   memset (&allowed, 0, sizeof (allowed));
449   memset (&require, 0, sizeof (require));
450
451   switch (p->type)
452     {
453     case COMMAND:
454       allowed.arg = TRUE;
455       require.arg = TRUE;
456       break;
457     case FAKE:
458       break;
459     case DESCRIPTION:
460       allowed.string = TRUE;
461       break;
462     case FAKEPREVIEW:
463       break;
464     case STRING:
465       allowed.id = TRUE;
466       require.id = TRUE;
467       allowed.label = TRUE;
468       require.label = TRUE;
469       allowed.arg = TRUE;
470       require.arg = TRUE;
471       break;
472     case FILENAME:
473       allowed.id = TRUE;
474       require.id = TRUE;
475       allowed.label = TRUE;
476       allowed.arg = TRUE;
477       require.arg = TRUE;
478       break;
479     case SLIDER:
480       allowed.id = TRUE;
481       require.id = TRUE;
482       allowed.label = TRUE;
483       allowed.low_label = TRUE;
484       allowed.high_label = TRUE;
485       allowed.arg = TRUE;
486       require.arg = TRUE;
487       allowed.low = TRUE;
488       /* require.low = TRUE; -- may be 0 */
489       allowed.high = TRUE;
490       /* require.high = TRUE; -- may be 0 */
491       allowed.value = TRUE;
492       /* require.value = TRUE; -- may be 0 */
493       allowed.invert_p = TRUE;
494       break;
495     case SPINBUTTON:
496       allowed.id = TRUE;
497       require.id = TRUE;
498       allowed.label = TRUE;
499       allowed.arg = TRUE;
500       require.arg = TRUE;
501       allowed.low = TRUE;
502       /* require.low = TRUE; -- may be 0 */
503       allowed.high = TRUE;
504       /* require.high = TRUE; -- may be 0 */
505       allowed.value = TRUE;
506       /* require.value = TRUE; -- may be 0 */
507       allowed.invert_p = TRUE;
508       break;
509     case BOOLEAN:
510       allowed.id = TRUE;
511       require.id = TRUE;
512       allowed.label = TRUE;
513       allowed.arg_set = TRUE;
514       allowed.arg_unset = TRUE;
515       break;
516     case SELECT:
517       allowed.id = TRUE;
518       require.id = TRUE;
519       break;
520     case SELECT_OPTION:
521       allowed.id = TRUE;
522       allowed.label = TRUE;
523       require.label = TRUE;
524       allowed.arg_set = TRUE;
525       break;
526     default:
527       abort();
528       break;
529     }
530
531 # define WARN(STR) \
532    fprintf (stderr, "%s: %s: " STR " in <%s%s id=\"%s\">\n", \
533               blurb(), filename, node_name, \
534               (!strcmp((char *) node_name, "number") \
535                ? (p->type == SPINBUTTON ? " type=spinbutton" : " type=slider")\
536                : ""), \
537               (p->id ? (char *) p->id : ""))
538 # define CHECK(SLOT,NAME) \
539    if (p->SLOT && !allowed.SLOT) \
540      WARN ("\"" NAME "\" is not a valid option"); \
541    if (!p->SLOT && require.SLOT) \
542      WARN ("\"" NAME "\" is required")
543
544   CHECK (id,         "id");
545   CHECK (label,      "_label");
546   CHECK (string,     "(body text)");
547   CHECK (low_label,  "_low-label");
548   CHECK (high_label, "_high-label");
549   CHECK (low,        "low");
550   CHECK (high,       "high");
551   CHECK (value,      "default");
552   CHECK (arg,        "arg");
553   CHECK (invert_p,   "convert");
554   CHECK (arg_set,    "arg-set");
555   CHECK (arg_unset,  "arg-unset");
556 # undef CHECK
557 # undef WARN
558 }
559
560
561
562 /* Helper for make_parameters()
563  */
564 static GList *
565 make_parameters_1 (const char *filename, xmlNodePtr node,
566                    GtkWidget *parent, int *row)
567 {
568   GList *list = 0;
569
570   for (; node; node = node->next)
571     {
572       const char *name = (char *) node->name;
573       if (!strcmp (name, "hgroup") ||
574           !strcmp (name, "vgroup"))
575         {
576           GtkWidget *box = (*name == 'h'
577                             ? gtk_hbox_new (FALSE, 0)
578                             : gtk_vbox_new (FALSE, 0));
579           GList *list2;
580           gtk_widget_show (box);
581
582           if (row)
583             gtk_table_attach (GTK_TABLE (parent), box, 0, 3, *row, *row + 1,
584                               0, 0, 0, 0);
585           else
586             gtk_box_pack_start (GTK_BOX (parent), box, FALSE, FALSE, 0);
587
588           if (row)
589             (*row)++;
590
591           list2 = make_parameters_1 (filename, node->xmlChildrenNode, box, 0);
592           if (list2)
593             list = g_list_concat (list, list2);
594         }
595       else
596         {
597           parameter *p = make_parameter (filename, node);
598           if (p)
599             {
600               list = g_list_append (list, p);
601               make_parameter_widget (filename, p, parent, row);
602             }
603         }
604     }
605   return list;
606 }
607
608
609 /* Calls make_parameter() and make_parameter_widget() on each relevant
610    tag in the XML tree.  Also handles the "hgroup" and "vgroup" flags.
611    Returns a GList of `parameter' objects.
612  */
613 static GList *
614 make_parameters (const char *filename, xmlNodePtr node, GtkWidget *parent)
615 {
616   int row = 0;
617   for (; node; node = node->next)
618     {
619       if (node->type == XML_ELEMENT_NODE &&
620           !strcmp ((char *) node->name, "screensaver"))
621         return make_parameters_1 (filename, node->xmlChildrenNode, parent, &row);
622     }
623   return 0;
624 }
625
626
627 static gfloat
628 invert_range (gfloat low, gfloat high, gfloat value)
629 {
630   gfloat range = high-low;
631   gfloat off = value-low;
632   return (low + (range - off));
633 }
634
635
636 static GtkAdjustment *
637 make_adjustment (const char *filename, parameter *p)
638 {
639   float range = (p->high - p->low);
640   float value = (p->invert_p
641                  ? invert_range (p->low, p->high, p->value)
642                  : p->value);
643   gfloat si = (p->high - p->low) / 100;
644   gfloat pi = (p->high - p->low) / 10;
645
646   if (p->value < p->low || p->value > p->high)
647     {
648       if (debug_p && p->integer_p)
649         fprintf (stderr, "%s: WARNING: %s: %d is not in range [%d, %d]\n",
650                  blurb(), filename,
651                  (int) p->value, (int) p->low, (int) p->high);
652       else if (debug_p)
653         fprintf (stderr,
654                  "%s: WARNING: %s: %.2f is not in range [%.2f, %.2f]\n",
655                  blurb(), filename, p->value, p->low, p->high);
656       value = (value < p->low ? p->low : p->high);
657     }
658 #if 0
659   else if (debug_p && p->value < 1000 && p->high >= 10000)
660     {
661       if (p->integer_p)
662         fprintf (stderr,
663                  "%s: WARNING: %s: %d is suspicious for range [%d, %d]\n",
664                  blurb(), filename,
665                  (int) p->value, (int) p->low, (int) p->high);
666       else
667         fprintf (stderr,
668                "%s: WARNING: %s: %.2f is suspicious for range [%.2f, %.2f]\n",
669                  blurb(), filename, p->value, p->low, p->high);
670     }
671 #endif /* 0 */
672
673   si = (int) (si + 0.5);
674   pi = (int) (pi + 0.5);
675   if (si < 1) si = 1;
676   if (pi < 1) pi = 1;
677
678   if (range <= 500) si = 1;
679
680   return GTK_ADJUSTMENT (gtk_adjustment_new (value,
681                                              p->low,
682                                              p->high + 1,
683                                              si, pi, 1));
684 }
685
686
687
688 /* Given a `parameter' struct, allocates an appropriate GtkWidget for it,
689    and stores it in `p->widget'.
690    `row' is used for keeping track of our position during table layout.
691    `parent' must be a GtkTable or a GtkBox.
692  */
693 static void
694 make_parameter_widget (const char *filename,
695                        parameter *p, GtkWidget *parent, int *row)
696 {
697   const char *label = (char *) p->label;
698   if (p->widget) return;
699
700   switch (p->type)
701     {
702     case STRING:
703       {
704         if (label)
705           {
706             GtkWidget *w = gtk_label_new (_(label));
707             gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
708             gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
709             gtk_widget_show (w);
710             if (row)
711               gtk_table_attach (GTK_TABLE (parent), w, 0, 1, *row, *row + 1,
712                                 GTK_FILL, 0, 0, 0);
713             else
714               gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
715           }
716
717         p->widget = gtk_entry_new ();
718         if (p->string)
719           gtk_entry_set_text (GTK_ENTRY (p->widget), (char *) p->string);
720         if (row)
721           gtk_table_attach (GTK_TABLE (parent), p->widget, 1, 3,
722                             *row, *row + 1,
723                             GTK_FILL, 0, 0, 0);
724         else
725           gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
726         break;
727       }
728     case FILENAME:
729       {
730         GtkWidget *L = gtk_label_new (label ? _(label) : "");
731         GtkWidget *entry = gtk_entry_new ();
732         GtkWidget *button = gtk_button_new_with_label (_("Browse..."));
733         gtk_widget_show (entry);
734         gtk_widget_show (button);
735         p->widget = entry;
736
737         gtk_signal_connect (GTK_OBJECT (button),
738                             "clicked", GTK_SIGNAL_FUNC (browse_button_cb),
739                             (gpointer) entry);
740
741         gtk_label_set_justify (GTK_LABEL (L), GTK_JUSTIFY_RIGHT);
742         gtk_misc_set_alignment (GTK_MISC (L), 1.0, 0.5);
743         gtk_widget_show (L);
744
745         if (p->string)
746           gtk_entry_set_text (GTK_ENTRY (entry), (char *) p->string);
747
748         if (row)
749           {
750             gtk_table_attach (GTK_TABLE (parent), L, 0, 1,
751                               *row, *row + 1,
752                               GTK_FILL, 0, 0, 0);
753             gtk_table_attach (GTK_TABLE (parent), entry, 1, 2,
754                               *row, *row + 1,
755                               GTK_EXPAND | GTK_FILL, 0, 0, 0);
756             gtk_table_attach (GTK_TABLE (parent), button, 2, 3,
757                               *row, *row + 1,
758                               0, 0, 0, 0);
759           }
760         else
761           {
762             gtk_box_pack_start (GTK_BOX (parent), L,      FALSE, FALSE, 4);
763             gtk_box_pack_start (GTK_BOX (parent), entry,  TRUE,  TRUE,  4);
764             gtk_box_pack_start (GTK_BOX (parent), button, FALSE, FALSE, 4);
765           }
766         break;
767       }
768     case SLIDER:
769       {
770         GtkAdjustment *adj = make_adjustment (filename, p);
771         GtkWidget *scale = gtk_hscale_new (adj);
772         GtkWidget *labelw = 0;
773
774         if (label)
775           {
776             labelw = gtk_label_new (_(label));
777             gtk_label_set_justify (GTK_LABEL (labelw), GTK_JUSTIFY_LEFT);
778             gtk_misc_set_alignment (GTK_MISC (labelw), 0.0, 0.5);
779             gtk_widget_show (labelw);
780           }
781
782         if (GTK_IS_VBOX (parent))
783           {
784             /* If we're inside a vbox, we need to put an hbox in it, to get
785                the low/high labels to be to the left/right of the slider.
786              */
787             GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
788
789             /* But if we have a label, put that above the slider's hbox. */
790             if (labelw)
791               {
792                 gtk_box_pack_start (GTK_BOX (parent), labelw, FALSE, TRUE, 2);
793                 labelw = 0;
794               }
795
796             gtk_box_pack_start (GTK_BOX (parent), hbox, TRUE, TRUE, 6);
797             gtk_widget_show (hbox);
798             parent = hbox;
799           }
800
801         if (labelw)
802           {
803             if (row)
804               {
805                 gtk_table_attach (GTK_TABLE (parent), labelw,
806                                   0, 3, *row, *row + 1,
807                                   GTK_EXPAND | GTK_FILL, 0, 0, 0);
808                 (*row)++;
809               }
810             else
811               {
812                 if (GTK_IS_HBOX (parent))
813                   {
814                     GtkWidget *box = gtk_vbox_new (FALSE, 0);
815                     gtk_box_pack_start (GTK_BOX (parent), box, FALSE, TRUE, 0);
816                     gtk_widget_show (box);
817                     gtk_box_pack_start (GTK_BOX (box), labelw, FALSE, TRUE, 4);
818                     parent = box;
819                     box = gtk_hbox_new (FALSE, 0);
820                     gtk_widget_show (box);
821                     gtk_box_pack_start (GTK_BOX (parent), box, TRUE, TRUE, 0);
822                     parent = box;
823                   }
824                 else
825                   gtk_box_pack_start (GTK_BOX (parent), labelw,
826                                       FALSE, TRUE, 0);
827               }
828           }
829
830         if (p->low_label)
831           {
832             GtkWidget *w = gtk_label_new (_((char *) p->low_label));
833             gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
834             gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
835             gtk_widget_show (w);
836             if (row)
837               gtk_table_attach (GTK_TABLE (parent), w, 0, 1, *row, *row + 1,
838                                 GTK_FILL, 0, 0, 0);
839             else
840               gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
841           }
842
843         gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_BOTTOM);
844         gtk_scale_set_draw_value (GTK_SCALE (scale), debug_p);
845         gtk_scale_set_digits (GTK_SCALE (scale), (p->integer_p ? 0 : 2));
846         if (row)
847           gtk_table_attach (GTK_TABLE (parent), scale, 1, 2,
848                             *row, *row + 1,
849                             GTK_EXPAND | GTK_FILL, 0, 0, 0);
850         else
851           gtk_box_pack_start (GTK_BOX (parent), scale, TRUE, TRUE, 4);
852
853         gtk_widget_show (scale);
854
855         if (p->high_label)
856           {
857             GtkWidget *w = gtk_label_new (_((char *) p->high_label));
858             gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT);
859             gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
860             gtk_widget_show (w);
861             if (row)
862               gtk_table_attach (GTK_TABLE (parent), w, 2, 3, *row, *row + 1,
863                                 GTK_FILL, 0, 0, 0);
864             else
865               gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
866           }
867
868         p->widget = scale;
869         break;
870       }
871     case SPINBUTTON:
872       {
873         GtkAdjustment *adj = make_adjustment (filename, p);
874         GtkWidget *spin = gtk_spin_button_new (adj, 15, 0);
875         gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE);
876         gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (spin), TRUE);
877         gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), adj->value);
878
879         if (label)
880           {
881             GtkWidget *w = gtk_label_new (_(label));
882             gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
883             gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
884             gtk_widget_show (w);
885             if (row)
886               gtk_table_attach (GTK_TABLE (parent), w, 0, 1, *row, *row + 1,
887                                 GTK_FILL, 0, 0, 0);
888             else
889               gtk_box_pack_start (GTK_BOX (parent), w, TRUE, TRUE, 4);
890           }
891
892         gtk_widget_show (spin);
893         if (row)
894           {
895             GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
896             gtk_widget_show (hbox);
897             gtk_table_attach (GTK_TABLE (parent), hbox, 1, 3,
898                               *row, *row + 1,
899                               GTK_EXPAND | GTK_FILL, 0, 8, 0);
900             gtk_box_pack_start (GTK_BOX (hbox), spin, FALSE, FALSE, 0);
901           }
902         else
903           gtk_box_pack_start (GTK_BOX (parent), spin, FALSE, FALSE, 4);
904
905         p->widget = spin;
906         break;
907       }
908     case BOOLEAN:
909       {
910         p->widget = gtk_check_button_new_with_label (_(label));
911         if (row)
912           gtk_table_attach (GTK_TABLE (parent), p->widget, 0, 3,
913                             *row, *row + 1,
914                             GTK_EXPAND | GTK_FILL, 0, 0, 0);
915         else
916           gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
917         break;
918       }
919     case SELECT:
920       {
921         GtkWidget *opt = gtk_option_menu_new ();
922         GtkWidget *menu = gtk_menu_new ();
923         GList *opts;
924
925         if (label && row)
926           {
927             GtkWidget *w = gtk_label_new (_(label));
928             gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT);
929             gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
930             gtk_widget_show (w);
931             gtk_table_attach (GTK_TABLE (parent), w, 0, 3, *row, *row + 1,
932                               GTK_EXPAND | GTK_FILL, 0, 0, 0);
933             (*row)++;
934           }
935
936         for (opts = p->options; opts; opts = opts->next)
937           {
938             parameter *s = (parameter *) opts->data;
939             GtkWidget *i = gtk_menu_item_new_with_label (_((char *) s->label));
940             gtk_widget_show (i);
941             gtk_menu_append (GTK_MENU (menu), i);
942           }
943
944         gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu);
945         p->widget = opt;
946         if (row)
947           gtk_table_attach (GTK_TABLE (parent), p->widget, 0, 3,
948                             *row, *row + 1,
949                             GTK_EXPAND | GTK_FILL, 0, 0, 0);
950         else
951           gtk_box_pack_start (GTK_BOX (parent), p->widget, TRUE, TRUE, 4);
952         break;
953       }
954
955     case COMMAND:
956     case FAKE:
957     case DESCRIPTION:
958     case FAKEPREVIEW:
959       break;
960     default:
961       abort();
962     }
963
964   if (p->widget)
965     {
966       gtk_widget_set_name (p->widget, (char *) p->id);
967       gtk_widget_show (p->widget);
968       if (row)
969         (*row)++;
970     }
971 }
972
973 \f
974 /* File selection.
975    Absurdly, there is no GTK file entry widget, only a GNOME one,
976    so in order to avoid depending on GNOME in this code, we have
977    to do it ourselves.
978  */
979
980 /* cancel button on GtkFileSelection: user_data unused */
981 static void
982 file_sel_cancel (GtkWidget *button, gpointer user_data)
983 {
984   GtkWidget *dialog = button;
985   while (dialog->parent)
986     dialog = dialog->parent;
987   gtk_widget_destroy (dialog);
988 }
989
990 /* ok button on GtkFileSelection: user_data is the corresponding GtkEntry */
991 static void
992 file_sel_ok (GtkWidget *button, gpointer user_data)
993 {
994   GtkWidget *entry = GTK_WIDGET (user_data);
995   GtkWidget *dialog = button;
996   const char *path;
997   while (dialog->parent)
998     dialog = dialog->parent;
999   gtk_widget_hide (dialog);
1000
1001   path = gtk_file_selection_get_filename (GTK_FILE_SELECTION (dialog));
1002   /* apparently one doesn't free `path' */
1003
1004   gtk_entry_set_text (GTK_ENTRY (entry), path);
1005   gtk_entry_set_position (GTK_ENTRY (entry), strlen (path));
1006
1007   gtk_widget_destroy (dialog);
1008 }
1009
1010 /* WM close on GtkFileSelection: user_data unused */
1011 static void
1012 file_sel_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
1013 {
1014   file_sel_cancel (widget, user_data);
1015 }
1016
1017 /* "Browse" button: user_data is the corresponding GtkEntry */
1018 static void
1019 browse_button_cb (GtkButton *button, gpointer user_data)
1020 {
1021   GtkWidget *entry = GTK_WIDGET (user_data);
1022   const char *text = gtk_entry_get_text (GTK_ENTRY (entry));
1023   GtkFileSelection *selector =
1024     GTK_FILE_SELECTION (gtk_file_selection_new (_("Select file.")));
1025
1026   gtk_file_selection_set_filename (selector, text);
1027   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
1028                       "clicked", GTK_SIGNAL_FUNC (file_sel_ok),
1029                       (gpointer) entry);
1030   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
1031                       "clicked", GTK_SIGNAL_FUNC (file_sel_cancel),
1032                       (gpointer) entry);
1033   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
1034                       GTK_SIGNAL_FUNC (file_sel_close),
1035                       (gpointer) entry);
1036
1037   gtk_window_set_modal (GTK_WINDOW (selector), TRUE);
1038   gtk_widget_show (GTK_WIDGET (selector));
1039 }
1040
1041 \f
1042 /* Converting to and from command-lines
1043  */
1044
1045
1046 /* Returns a copy of string that has been quoted according to shell rules:
1047    it may have been wrapped in "" and had some characters backslashed; or
1048    it may be unchanged.
1049  */
1050 static char *
1051 shell_quotify (const char *string)
1052 {
1053   char *string2 = (char *) malloc ((strlen (string) * 2) + 10);
1054   const char *in;
1055   char *out;
1056   int need_quotes = 0;
1057   int in_length = 0;
1058
1059   out = string2;
1060   *out++ = '"';
1061   for (in = string; *in; in++)
1062     {
1063       in_length++;
1064       if (*in == '!' ||
1065           *in == '"' ||
1066           *in == '$')
1067         {
1068           need_quotes = 1;
1069           *out++ = '\\';
1070           *out++ = *in;
1071         }
1072       else if (*in <= ' ' ||
1073                *in >= 127 ||
1074                *in == '\'' ||
1075                *in == '#' ||
1076                *in == '%' ||
1077                *in == '&' ||
1078                *in == '(' ||
1079                *in == ')' ||
1080                *in == '*')
1081         {
1082           need_quotes = 1;
1083           *out++ = *in;
1084         }
1085       else
1086         *out++ = *in;
1087     }
1088   *out++ = '"';
1089   *out = 0;
1090
1091   if (in_length == 0)
1092     need_quotes = 1;
1093
1094   if (need_quotes)
1095     return (string2);
1096
1097   free (string2);
1098   return strdup (string);
1099 }
1100
1101 /* Modify the string in place to remove wrapping double-quotes
1102    and interior backslashes. 
1103  */
1104 static void
1105 de_stringify (char *s)
1106 {
1107   char q = s[0];
1108   if (q != '\'' && q != '\"' && q != '`')
1109     abort();
1110   memmove (s, s+1, strlen (s)+1);
1111   while (*s && *s != q)
1112     {
1113       if (*s == '\\')
1114         memmove (s, s+1, strlen (s)+1);
1115       s++;
1116     }
1117   if (*s != q) abort();
1118   *s = 0;
1119 }
1120
1121
1122 /* Substitutes a shell-quotified version of `value' into `p->arg' at
1123    the place where the `%' character appeared.
1124  */
1125 static char *
1126 format_switch (parameter *p, const char *value)
1127 {
1128   char *fmt = (char *) p->arg;
1129   char *v2;
1130   char *result, *s;
1131   if (!fmt || !value) return 0;
1132   v2 = shell_quotify (value);
1133   result = (char *) malloc (strlen (fmt) + strlen (v2) + 10);
1134   s = result;
1135   for (; *fmt; fmt++)
1136     if (*fmt != '%')
1137       *s++ = *fmt;
1138     else
1139       {
1140         strcpy (s, v2);
1141         s += strlen (s);
1142       }
1143   *s = 0;
1144
1145   free (v2);
1146   return result;
1147 }
1148
1149
1150 /* Maps a `parameter' to a command-line switch.
1151    Returns 0 if it can't, or if the parameter has the default value.
1152  */
1153 static char *
1154 parameter_to_switch (parameter *p)
1155 {
1156   switch (p->type)
1157     {
1158     case COMMAND:
1159       if (p->arg)
1160         return strdup ((char *) p->arg);
1161       else
1162         return 0;
1163       break;
1164     case STRING:
1165     case FILENAME:
1166       if (!p->widget) return 0;
1167       {
1168         const char *s = gtk_entry_get_text (GTK_ENTRY (p->widget));
1169         char *v;
1170         if (!strcmp ((s ? s : ""),
1171                      (p->string ? (char *) p->string : "")))
1172           v = 0;  /* same as default */
1173         else
1174           v = format_switch (p, s);
1175
1176         /* don't free `s' */
1177         return v;
1178       }
1179     case SLIDER:
1180     case SPINBUTTON:
1181       if (!p->widget) return 0;
1182       {
1183         GtkAdjustment *adj =
1184           (p->type == SLIDER
1185            ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
1186            : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
1187         char buf[255];
1188         char *s1;
1189         float value = (p->invert_p
1190                        ? invert_range (adj->lower, adj->upper, adj->value) - 1
1191                        : adj->value);
1192
1193         if (value == p->value)  /* same as default */
1194           return 0;
1195
1196         if (p->integer_p)
1197           sprintf (buf, "%d", (int) (value + 0.5));
1198         else
1199           sprintf (buf, "%.4f", value);
1200           
1201         s1 = strchr (buf, '.');
1202         if (s1)
1203           {
1204             char *s2 = s1 + strlen(s1) - 1;
1205             while (s2 > s1 && *s2 == '0')       /* lose trailing zeroes */
1206               *s2-- = 0;
1207             if (s2 >= s1 && *s2 == '.')         /* lose trailing decimal */
1208               *s2-- = 0;
1209           }
1210         return format_switch (p, buf);
1211       }
1212     case BOOLEAN:
1213       if (!p->widget) return 0;
1214       {
1215         GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget);
1216         const char *s = (gtk_toggle_button_get_active (b)
1217                          ? (char *) p->arg_set
1218                          : (char *) p->arg_unset);
1219         if (s)
1220           return strdup (s);
1221         else
1222           return 0;
1223       }
1224     case SELECT:
1225       if (!p->widget) return 0;
1226       {
1227         GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget);
1228         GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
1229         GtkWidget *selected = gtk_menu_get_active (menu);
1230         GList *kids = gtk_container_children (GTK_CONTAINER (menu));
1231         int menu_elt = g_list_index (kids, (gpointer) selected);
1232         GList *ol = g_list_nth (p->options, menu_elt);
1233         parameter *o = (ol ? (parameter *) ol->data : 0);
1234         const char *s;
1235         if (!o) abort();
1236         if (o->type != SELECT_OPTION) abort();
1237         s = (char *) o->arg_set;
1238         if (s)
1239           return strdup (s);
1240         else
1241           return 0;
1242       }
1243     default:
1244       if (p->widget)
1245         abort();
1246       else
1247         return 0;
1248     }
1249 }
1250
1251 /* Maps a GList of `parameter' objects to a complete command-line string.
1252    All arguments will be properly quoted.
1253  */
1254 static char *
1255 parameters_to_cmd_line (GList *parms)
1256 {
1257   int L = g_list_length (parms);
1258   int LL = 0;
1259   char **strs = (char **) calloc (sizeof (*parms), L);
1260   char *result;
1261   char *out;
1262   int i;
1263
1264   for (i = 0; parms; parms = parms->next, i++)
1265     {
1266       char *s = parameter_to_switch ((parameter *) parms->data);
1267       strs[i] = s;
1268       LL += (s ? strlen(s) : 0) + 1;
1269     }
1270
1271   result = (char *) malloc (LL + 10);
1272   out = result;
1273   for (i = 0; i < L; i++)
1274     if (strs[i])
1275       {
1276         strcpy (out, strs[i]);
1277         out += strlen (out);
1278         *out++ = ' ';
1279         free (strs[i]);
1280       }
1281   *out = 0;
1282   while (out > result && out[-1] == ' ')  /* strip trailing spaces */
1283     *(--out) = 0;
1284   free (strs);
1285
1286   return result;
1287 }
1288
1289
1290 /* Returns a GList of the tokens the string, using shell syntax;
1291    Quoted strings are handled as a single token.
1292  */
1293 static GList *
1294 tokenize_command_line (const char *cmd)
1295 {
1296   GList *result = 0;
1297   const char *s = cmd;
1298   while (*s)
1299     {
1300       const char *start;
1301       char *ss;
1302       for (; isspace(*s); s++);         /* skip whitespace */
1303
1304       start = s;
1305       if (*s == '\'' || *s == '\"' || *s == '`')
1306         {
1307           char q = *s;
1308           s++;
1309           while (*s && *s != q)         /* skip to matching quote */
1310             {
1311               if (*s == '\\' && s[1])   /* allowing backslash quoting */
1312                 s++;
1313               s++;
1314             }
1315           s++;
1316         }
1317       else
1318         {
1319           while (*s &&
1320                  (! (isspace(*s) ||
1321                      *s == '\'' ||
1322                      *s == '\"' ||
1323                      *s == '`')))
1324             s++;
1325         }
1326
1327       if (s > start)
1328         {
1329           ss = (char *) malloc ((s - start) + 1);
1330           strncpy (ss, start, s-start);
1331           ss[s-start] = 0;
1332           if (*ss == '\'' || *ss == '\"' || *ss == '`')
1333             de_stringify (ss);
1334           result = g_list_append (result, ss);
1335         }
1336     }
1337
1338   return result;
1339 }
1340
1341 static void parameter_set_switch (parameter *, gpointer value);
1342 static gboolean parse_command_line_into_parameters_1 (const char *filename,
1343                                                       GList *parms,
1344                                                       const char *option,
1345                                                       const char *value,
1346                                                       parameter *parent);
1347
1348
1349 /* Parses the command line, and flushes those options down into
1350    the `parameter' structs in the list.
1351  */
1352 static void
1353 parse_command_line_into_parameters (const char *filename,
1354                                     const char *cmd, GList *parms)
1355 {
1356   GList *tokens = tokenize_command_line (cmd);
1357   GList *rest;
1358   for (rest = tokens; rest; rest = rest->next)
1359     {
1360       char *option = rest->data;
1361       rest->data = 0;
1362
1363       if (option[0] != '-')
1364         {
1365           if (debug_p)
1366             fprintf (stderr, "%s: WARNING: %s: not a switch: \"%s\"\n",
1367                      blurb(), filename, option);
1368         }
1369       else
1370         {
1371           char *value = 0;
1372
1373           if (rest->next)   /* pop off the arg to this option */
1374             {
1375               char *s = (char *) rest->next->data;
1376               /* the next token is the next switch iff it matches "-[a-z]".
1377                  (To avoid losing on "-x -3.1".)
1378                */
1379               if (s && (s[0] != '-' || !isalpha(s[1])))
1380                 {
1381                   value = s;
1382                   rest->next->data = 0;
1383                   rest = rest->next;
1384                 }
1385             }
1386
1387           parse_command_line_into_parameters_1 (filename, parms,
1388                                                 option, value, 0);
1389           if (value) free (value);
1390           free (option);
1391         }
1392     }
1393   g_list_free (tokens);
1394 }
1395
1396
1397 static gboolean
1398 compare_opts (const char *option, const char *value,
1399               const char *template)
1400 {
1401   int ol = strlen (option);
1402   char *c;
1403
1404   if (strncmp (option, template, ol))
1405     return FALSE;
1406
1407   if (template[ol] != (value ? ' ' : 0))
1408     return FALSE;
1409
1410   /* At this point, we have a match against "option".
1411      If template contains a %, we're done.
1412      Else, compare against "value" too.
1413    */
1414   c = strchr (template, '%');
1415   if (c)
1416     return TRUE;
1417
1418   if (!value)
1419     return (template[ol] == 0);
1420   if (strcmp (template + ol + 1, value))
1421     return FALSE;
1422
1423   return TRUE;
1424 }
1425
1426
1427 static gboolean
1428 parse_command_line_into_parameters_1 (const char *filename,
1429                                       GList *parms,
1430                                       const char *option,
1431                                       const char *value,
1432                                       parameter *parent)
1433 {
1434   GList *p;
1435   parameter *match = 0;
1436   gint which = -1;
1437   gint index = 0;
1438
1439   for (p = parms; p; p = p->next)
1440     {
1441       parameter *pp = (parameter *) p->data;
1442       which = -99;
1443
1444       if (pp->type == SELECT)
1445         {
1446           if (parse_command_line_into_parameters_1 (filename,
1447                                                     pp->options,
1448                                                     option, value,
1449                                                     pp))
1450             {
1451               which = -2;
1452               match = pp;
1453             }
1454         }
1455       else if (pp->arg)
1456         {
1457           if (compare_opts (option, value, (char *) pp->arg))
1458             {
1459               which = -1;
1460               match = pp;
1461             }
1462         }
1463       else if (pp->arg_set)
1464         {
1465           if (compare_opts (option, value, (char *) pp->arg_set))
1466             {
1467               which = 1;
1468               match = pp;
1469             }
1470         }
1471       else if (pp->arg_unset)
1472         {
1473           if (compare_opts (option, value, (char *) pp->arg_unset))
1474             {
1475               which = 0;
1476               match = pp;
1477             }
1478         }
1479
1480       if (match)
1481         break;
1482
1483       index++;
1484     }
1485
1486   if (!match)
1487     {
1488       if (debug_p && !parent)
1489         fprintf (stderr, "%s: WARNING: %s: no match for %s %s\n",
1490                  blurb(), filename, option, (value ? value : ""));
1491       return FALSE;
1492     }
1493
1494   switch (match->type)
1495     {
1496     case STRING:
1497     case FILENAME:
1498     case SLIDER:
1499     case SPINBUTTON:
1500       if (which != -1) abort();
1501       parameter_set_switch (match, (gpointer) value);
1502       break;
1503     case BOOLEAN:
1504       if (which != 0 && which != 1) abort();
1505       parameter_set_switch (match, GINT_TO_POINTER(which));
1506       break;
1507     case SELECT_OPTION:
1508       if (which != 1) abort();
1509       parameter_set_switch (parent, GINT_TO_POINTER(index));
1510       break;
1511     default:
1512       break;
1513     }
1514   return TRUE;
1515 }
1516
1517
1518 /* Set the parameter's value.
1519    For STRING, FILENAME, SLIDER, and SPINBUTTON, `value' is a char*.
1520    For BOOLEAN and SELECT, `value' is an int.
1521  */
1522 static void
1523 parameter_set_switch (parameter *p, gpointer value)
1524 {
1525   if (p->type == SELECT_OPTION) abort();
1526   if (!p->widget) return;
1527   switch (p->type)
1528     {
1529     case STRING:
1530     case FILENAME:
1531       {
1532         gtk_entry_set_text (GTK_ENTRY (p->widget), (char *) value);
1533         break;
1534       }
1535     case SLIDER:
1536     case SPINBUTTON:
1537       {
1538         GtkAdjustment *adj =
1539           (p->type == SLIDER
1540            ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
1541            : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
1542         float f;
1543         char c;
1544
1545         if (1 == sscanf ((char *) value, "%f %c", &f, &c))
1546           {
1547             if (p->invert_p)
1548               f = invert_range (adj->lower, adj->upper, f) - 1;
1549             gtk_adjustment_set_value (adj, f);
1550           }
1551         break;
1552       }
1553     case BOOLEAN:
1554       {
1555         GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget);
1556         gtk_toggle_button_set_active (b, GPOINTER_TO_INT(value));
1557         break;
1558       }
1559     case SELECT:
1560       {
1561         gtk_option_menu_set_history (GTK_OPTION_MENU (p->widget),
1562                                      GPOINTER_TO_INT(value));
1563         break;
1564       }
1565     default:
1566       abort();
1567     }
1568 }
1569
1570
1571 static void
1572 restore_defaults (const char *progname, GList *parms)
1573 {
1574   for (; parms; parms = parms->next)
1575     {
1576       parameter *p = (parameter *) parms->data;
1577       if (!p->widget) continue;
1578       switch (p->type)
1579         {
1580         case STRING:
1581         case FILENAME:
1582           {
1583             gtk_entry_set_text (GTK_ENTRY (p->widget),
1584                                 (p->string ? (char *) p->string : ""));
1585             break;
1586           }
1587         case SLIDER:
1588         case SPINBUTTON:
1589           {
1590             GtkAdjustment *adj =
1591               (p->type == SLIDER
1592                ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
1593                : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
1594             float value = (p->invert_p
1595                            ? invert_range (p->low, p->high, p->value)
1596                            : p->value);
1597             gtk_adjustment_set_value (adj, value);
1598             break;
1599           }
1600         case BOOLEAN:
1601           {
1602             /* A toggle button should be on by default if it inserts
1603                nothing into the command line when on.  E.g., it should
1604                be on if `arg_set' is null.
1605              */
1606             gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (p->widget),
1607                                           (!p->arg_set || !*p->arg_set));
1608             break;
1609           }
1610         case SELECT:
1611           {
1612             GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget);
1613             GList *opts;
1614             int selected = 0;
1615             int index;
1616
1617             for (opts = p->options, index = 0; opts;
1618                  opts = opts->next, index++)
1619               {
1620                 parameter *s = (parameter *) opts->data;
1621                 /* The default menu item is the first one with
1622                    no `arg_set' field. */
1623                 if (!s->arg_set)
1624                   {
1625                     selected = index;
1626                     break;
1627                   }
1628               }
1629
1630             gtk_option_menu_set_history (GTK_OPTION_MENU (opt), selected);
1631             break;
1632           }
1633         default:
1634           abort();
1635         }
1636     }
1637 }
1638
1639
1640 \f
1641 /* Documentation strings
1642  */
1643
1644 static char *
1645 get_description (GList *parms, gboolean verbose_p)
1646 {
1647   parameter *doc = 0;
1648   for (; parms; parms = parms->next)
1649     {
1650       parameter *p = (parameter *) parms->data;
1651       if (p->type == DESCRIPTION)
1652         {
1653           doc = p;
1654           break;
1655         }
1656     }
1657
1658   if (!doc || !doc->string)
1659     return 0;
1660   else
1661     {
1662       char *d = strdup ((char *) doc->string);
1663       char *s;
1664       char *p;
1665       for (s = d; *s; s++)
1666         if (s[0] == '\n')
1667           {
1668             if (s[1] == '\n')      /* blank line: leave it */
1669               s++;
1670             else if (s[1] == ' ' || s[1] == '\t')
1671               s++;                 /* next line is indented: leave newline */
1672             else
1673               s[0] = ' ';          /* delete newline to un-fold this line */
1674           }
1675
1676       /* strip off leading whitespace on first line only */
1677       for (s = d; *s && (*s == ' ' || *s == '\t'); s++)
1678         ;
1679       while (*s == '\n')   /* strip leading newlines */
1680         s++;
1681       if (s != d)
1682         memmove (d, s, strlen(s)+1);
1683
1684       /* strip off trailing whitespace and newlines */
1685       {
1686         int L = strlen(d);
1687         while (L && isspace(d[L-1]))
1688           d[--L] = 0;
1689       }
1690
1691       /* strip off duplicated whitespaces */
1692       for (s = d; *s; s++)
1693           if (s[0] == ' ')
1694           {
1695             p = s+1;
1696             while (*s == ' ')
1697               s++;
1698             if (*p && (s != p))
1699               memmove (p, s, strlen(s)+1);
1700           }
1701
1702 #if 0
1703       if (verbose_p)
1704         {
1705           fprintf (stderr, "%s: text read   is \"%s\"\n", blurb(),doc->string);
1706           fprintf (stderr, "%s: description is \"%s\"\n", blurb(), d);
1707           fprintf (stderr, "%s: translation is \"%s\"\n", blurb(), _(d));
1708         }
1709 #endif /* 0 */
1710
1711       return (d);
1712     }
1713 }
1714
1715 \f
1716 /* External interface.
1717  */
1718
1719 static conf_data *
1720 load_configurator_1 (const char *program, const char *arguments,
1721                      gboolean verbose_p)
1722 {
1723   const char *dir = hack_configuration_path;
1724   int L = strlen (dir);
1725   char *file;
1726   char *s;
1727   FILE *f;
1728   conf_data *data;
1729
1730   if (L == 0) return 0;
1731
1732   file = (char *) malloc (L + strlen (program) + 10);
1733   data = (conf_data *) calloc (1, sizeof(*data));
1734
1735   strcpy (file, dir);
1736   if (file[L-1] != '/')
1737     file[L++] = '/';
1738   strcpy (file+L, program);
1739
1740   for (s = file+L; *s; s++)
1741     if (*s == '/' || *s == ' ')
1742       *s = '_';
1743     else if (isupper (*s))
1744       *s = tolower (*s);
1745
1746   strcat (file+L, ".xml");
1747
1748   f = fopen (file, "r");
1749   if (f)
1750     {
1751       int res, size = 1024;
1752       char chars[1024];
1753       xmlParserCtxtPtr ctxt;
1754       xmlDocPtr doc = 0;
1755       GtkWidget *table;
1756       GList *parms;
1757
1758       if (verbose_p)
1759         fprintf (stderr, "%s: reading %s...\n", blurb(), file);
1760
1761       res = fread (chars, 1, 4, f);
1762       if (res <= 0)
1763         {
1764           free (data);
1765           data = 0;
1766           goto DONE;
1767         }
1768
1769       ctxt = xmlCreatePushParserCtxt(NULL, NULL, chars, res, file);
1770       while ((res = fread(chars, 1, size, f)) > 0)
1771         xmlParseChunk (ctxt, chars, res, 0);
1772       xmlParseChunk (ctxt, chars, 0, 1);
1773       doc = ctxt->myDoc;
1774       xmlFreeParserCtxt (ctxt);
1775       fclose (f);
1776
1777       /* Parsed the XML file.  Now make some widgets. */
1778
1779       table = gtk_table_new (1, 3, FALSE);
1780       gtk_table_set_row_spacings (GTK_TABLE (table), 4);
1781       gtk_table_set_col_spacings (GTK_TABLE (table), 4);
1782       gtk_container_set_border_width (GTK_CONTAINER (table), 8);
1783       gtk_widget_show (table);
1784
1785       parms = make_parameters (file, doc->xmlRootNode, table);
1786
1787       xmlFreeDoc (doc);
1788
1789       restore_defaults (program, parms);
1790       if (arguments && *arguments)
1791         parse_command_line_into_parameters (program, arguments, parms);
1792
1793       data->widget = table;
1794       data->parameters = parms;
1795       data->description = get_description (parms, verbose_p);
1796     }
1797   else
1798     {
1799       parameter *p;
1800
1801       if (verbose_p)
1802         fprintf (stderr, "%s: %s does not exist.\n", blurb(), file);
1803
1804       p = calloc (1, sizeof(*p));
1805       p->type = COMMAND;
1806       p->arg = (xmlChar *) strdup (arguments);
1807
1808       data->parameters = g_list_append (0, (gpointer) p);
1809     }
1810
1811   data->progname = strdup (program);
1812
1813  DONE:
1814   free (file);
1815   return data;
1816 }
1817
1818 static void
1819 split_command_line (const char *full_command_line,
1820                     char **prog_ret, char **args_ret)
1821 {
1822   char *line = strdup (full_command_line);
1823   char *prog;
1824   char *args;
1825   char *s;
1826
1827   prog = line;
1828   s = line;
1829   while (*s)
1830     {
1831       if (isspace (*s))
1832         {
1833           *s = 0;
1834           s++;
1835           while (isspace (*s)) s++;
1836           break;
1837         }
1838       else if (*s == '=')  /* if the leading word contains an "=", skip it. */
1839         {
1840           while (*s && !isspace (*s)) s++;
1841           while (isspace (*s)) s++;
1842           prog = s;
1843         }
1844       s++;
1845     }
1846   args = s;
1847
1848   *prog_ret = strdup (prog);
1849   *args_ret = strdup (args);
1850   free (line);
1851 }
1852
1853
1854 conf_data *
1855 load_configurator (const char *full_command_line, gboolean verbose_p)
1856 {
1857   char *prog;
1858   char *args;
1859   conf_data *cd;
1860   debug_p = verbose_p;
1861   split_command_line (full_command_line, &prog, &args);
1862   cd = load_configurator_1 (prog, args, verbose_p);
1863   free (prog);
1864   free (args);
1865   return cd;
1866 }
1867
1868
1869
1870 char *
1871 get_configurator_command_line (conf_data *data)
1872 {
1873   char *args = parameters_to_cmd_line (data->parameters);
1874   char *result = (char *) malloc (strlen (data->progname) +
1875                                   strlen (args) + 2);
1876   strcpy (result, data->progname);
1877   strcat (result, " ");
1878   strcat (result, args);
1879   free (args);
1880   return result;
1881 }
1882
1883
1884 void
1885 set_configurator_command_line (conf_data *data, const char *full_command_line)
1886 {
1887   char *prog;
1888   char *args;
1889   split_command_line (full_command_line, &prog, &args);
1890   if (data->progname) free (data->progname);
1891   data->progname = prog;
1892   restore_defaults (prog, data->parameters);
1893   parse_command_line_into_parameters (prog, args, data->parameters);
1894   free (args);
1895 }
1896
1897 void
1898 free_conf_data (conf_data *data)
1899 {
1900   if (data->parameters)
1901     {
1902       GList *rest;
1903       for (rest = data->parameters; rest; rest = rest->next)
1904         {
1905           free_parameter ((parameter *) rest->data);
1906           rest->data = 0;
1907         }
1908       g_list_free (data->parameters);
1909       data->parameters = 0;
1910     }
1911
1912   if (data->widget)
1913     gtk_widget_destroy (data->widget);
1914
1915   if (data->progname)
1916     free (data->progname);
1917   if (data->description)
1918     free (data->description);
1919
1920   memset (data, ~0, sizeof(*data));
1921   free (data);
1922 }
1923
1924
1925 #endif /* HAVE_GTK && HAVE_XML -- whole file */