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