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