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