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