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