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