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