http://slackware.bholcomb.com/slackware/slackware-11.0/source/xap/xscreensaver/xscree...
[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
753   if (p->value < p->low || p->value > p->high)
754     {
755       if (debug_p && p->integer_p)
756         fprintf (stderr, "%s: WARNING: %s: %d is not in range [%d, %d]\n",
757                  blurb(), filename,
758                  (int) p->value, (int) p->low, (int) p->high);
759       else if (debug_p)
760         fprintf (stderr,
761                  "%s: WARNING: %s: %.2f is not in range [%.2f, %.2f]\n",
762                  blurb(), filename, p->value, p->low, p->high);
763       value = (value < p->low ? p->low : p->high);
764     }
765 #if 0
766   else if (debug_p && p->value < 1000 && p->high >= 10000)
767     {
768       if (p->integer_p)
769         fprintf (stderr,
770                  "%s: WARNING: %s: %d is suspicious for range [%d, %d]\n",
771                  blurb(), filename,
772                  (int) p->value, (int) p->low, (int) p->high);
773       else
774         fprintf (stderr,
775                "%s: WARNING: %s: %.2f is suspicious for range [%.2f, %.2f]\n",
776                  blurb(), filename, p->value, p->low, p->high);
777     }
778 #endif /* 0 */
779
780   si = (int) (si + 0.5);
781   pi = (int) (pi + 0.5);
782   if (si < 1) si = 1;
783   if (pi < 1) pi = 1;
784
785   if (range <= 500) si = 1;
786
787   return GTK_ADJUSTMENT (gtk_adjustment_new (value,
788                                              p->low,
789                                              p->high + 1,
790                                              si, pi, 1));
791 }
792
793
794
795 /* Given a `parameter' struct, allocates an appropriate GtkWidget for it,
796    and stores it in `p->widget'.
797    `row' is used for keeping track of our position during table layout.
798    `parent' must be a GtkTable or a GtkBox.
799  */
800 static void
801 make_parameter_widget (const char *filename,
802                        parameter *p, GtkWidget *parent, int *row)
803 {
804   const char *label = (char *) p->label;
805   if (p->widget) return;
806
807   switch (p->type)
808     {
809     case STRING:
810       {
811         if (label)
812           {
813             GtkWidget *w = gtk_label_new (_(label));
814             gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
815             gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
816             gtk_widget_show (w);
817             if (row)
818               gtk_table_attach (GTK_TABLE (parent), w, 0, 1, *row, *row + 1,
819                                 GTK_FILL, 0, 0, 0);
820             else
821               gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
822           }
823
824         p->widget = gtk_entry_new ();
825         if (p->string)
826           gtk_entry_set_text (GTK_ENTRY (p->widget), (char *) p->string);
827         if (row)
828           gtk_table_attach (GTK_TABLE (parent), p->widget, 1, 3,
829                             *row, *row + 1,
830                             GTK_FILL, 0, 0, 0);
831         else
832           gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
833         break;
834       }
835     case FILENAME:
836       {
837         GtkWidget *L = gtk_label_new (label ? _(label) : "");
838         GtkWidget *entry = gtk_entry_new ();
839         GtkWidget *button = gtk_button_new_with_label (_("Browse..."));
840         gtk_widget_show (entry);
841         gtk_widget_show (button);
842         p->widget = entry;
843
844         gtk_signal_connect (GTK_OBJECT (button),
845                             "clicked", GTK_SIGNAL_FUNC (browse_button_cb),
846                             (gpointer) entry);
847
848         gtk_label_set_justify (GTK_LABEL (L), GTK_JUSTIFY_RIGHT);
849         gtk_misc_set_alignment (GTK_MISC (L), 1.0, 0.5);
850         gtk_widget_show (L);
851
852         if (p->string)
853           gtk_entry_set_text (GTK_ENTRY (entry), (char *) p->string);
854
855         if (row)
856           {
857             gtk_table_attach (GTK_TABLE (parent), L, 0, 1,
858                               *row, *row + 1,
859                               GTK_FILL, 0, 0, 0);
860             gtk_table_attach (GTK_TABLE (parent), entry, 1, 2,
861                               *row, *row + 1,
862                               GTK_EXPAND | GTK_FILL, 0, 0, 0);
863             gtk_table_attach (GTK_TABLE (parent), button, 2, 3,
864                               *row, *row + 1,
865                               0, 0, 0, 0);
866           }
867         else
868           {
869             gtk_box_pack_start (GTK_BOX (parent), L,      FALSE, FALSE, 4);
870             gtk_box_pack_start (GTK_BOX (parent), entry,  TRUE,  TRUE,  4);
871             gtk_box_pack_start (GTK_BOX (parent), button, FALSE, FALSE, 4);
872           }
873         break;
874       }
875     case SLIDER:
876       {
877         GtkAdjustment *adj = make_adjustment (filename, p);
878         GtkWidget *scale = gtk_hscale_new (adj);
879         GtkWidget *labelw = 0;
880
881         if (label)
882           {
883             labelw = gtk_label_new (_(label));
884             gtk_label_set_justify (GTK_LABEL (labelw), GTK_JUSTIFY_LEFT);
885             gtk_misc_set_alignment (GTK_MISC (labelw), 0.0, 0.5);
886             gtk_widget_show (labelw);
887           }
888
889         if (GTK_IS_VBOX (parent))
890           {
891             /* If we're inside a vbox, we need to put an hbox in it, to get
892                the low/high labels to be to the left/right of the slider.
893              */
894             GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
895
896             /* But if we have a label, put that above the slider's hbox. */
897             if (labelw)
898               {
899                 gtk_box_pack_start (GTK_BOX (parent), labelw, FALSE, TRUE, 2);
900                 labelw = 0;
901               }
902
903             gtk_box_pack_start (GTK_BOX (parent), hbox, TRUE, TRUE, 6);
904             gtk_widget_show (hbox);
905             parent = hbox;
906           }
907
908         if (labelw)
909           {
910             if (row)
911               {
912                 gtk_table_attach (GTK_TABLE (parent), labelw,
913                                   0, 3, *row, *row + 1,
914                                   GTK_EXPAND | GTK_FILL, 0, 0, 0);
915                 (*row)++;
916               }
917             else
918               {
919                 if (GTK_IS_HBOX (parent))
920                   {
921                     GtkWidget *box = gtk_vbox_new (FALSE, 0);
922                     gtk_box_pack_start (GTK_BOX (parent), box, FALSE, TRUE, 0);
923                     gtk_widget_show (box);
924                     gtk_box_pack_start (GTK_BOX (box), labelw, FALSE, TRUE, 4);
925                     parent = box;
926                     box = gtk_hbox_new (FALSE, 0);
927                     gtk_widget_show (box);
928                     gtk_box_pack_start (GTK_BOX (parent), box, TRUE, TRUE, 0);
929                     parent = box;
930                   }
931                 else
932                   gtk_box_pack_start (GTK_BOX (parent), labelw,
933                                       FALSE, TRUE, 0);
934               }
935           }
936
937         if (p->low_label)
938           {
939             GtkWidget *w = gtk_label_new (_((char *) p->low_label));
940             gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
941             gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
942             gtk_widget_show (w);
943             if (row)
944               gtk_table_attach (GTK_TABLE (parent), w, 0, 1, *row, *row + 1,
945                                 GTK_FILL, 0, 0, 0);
946             else
947               gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
948           }
949
950         gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_BOTTOM);
951         gtk_scale_set_draw_value (GTK_SCALE (scale), debug_p);
952         gtk_scale_set_digits (GTK_SCALE (scale), (p->integer_p ? 0 : 2));
953         if (row)
954           gtk_table_attach (GTK_TABLE (parent), scale, 1, 2,
955                             *row, *row + 1,
956                             GTK_EXPAND | GTK_FILL, 0, 0, 0);
957         else
958           gtk_box_pack_start (GTK_BOX (parent), scale, TRUE, TRUE, 4);
959
960         gtk_widget_show (scale);
961
962         if (p->high_label)
963           {
964             GtkWidget *w = gtk_label_new (_((char *) p->high_label));
965             gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT);
966             gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
967             gtk_widget_show (w);
968             if (row)
969               gtk_table_attach (GTK_TABLE (parent), w, 2, 3, *row, *row + 1,
970                                 GTK_FILL, 0, 0, 0);
971             else
972               gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
973           }
974
975         p->widget = scale;
976         break;
977       }
978     case SPINBUTTON:
979       {
980         GtkAdjustment *adj = make_adjustment (filename, p);
981         GtkWidget *spin = gtk_spin_button_new (adj, 15, 0);
982         gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE);
983         gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (spin), TRUE);
984         gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), adj->value);
985
986         if (label)
987           {
988             GtkWidget *w = gtk_label_new (_(label));
989             gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
990             gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
991             gtk_widget_show (w);
992             if (row)
993               gtk_table_attach (GTK_TABLE (parent), w, 0, 1, *row, *row + 1,
994                                 GTK_FILL, 0, 0, 0);
995             else
996               gtk_box_pack_start (GTK_BOX (parent), w, TRUE, TRUE, 4);
997           }
998
999         gtk_widget_show (spin);
1000         if (row)
1001           {
1002             GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
1003             gtk_widget_show (hbox);
1004             gtk_table_attach (GTK_TABLE (parent), hbox, 1, 3,
1005                               *row, *row + 1,
1006                               GTK_EXPAND | GTK_FILL, 0, 8, 0);
1007             gtk_box_pack_start (GTK_BOX (hbox), spin, FALSE, FALSE, 0);
1008           }
1009         else
1010           gtk_box_pack_start (GTK_BOX (parent), spin, FALSE, FALSE, 4);
1011
1012         p->widget = spin;
1013         break;
1014       }
1015     case BOOLEAN:
1016       {
1017         p->widget = gtk_check_button_new_with_label (_(label));
1018         if (row)
1019           gtk_table_attach (GTK_TABLE (parent), p->widget, 0, 3,
1020                             *row, *row + 1,
1021                             GTK_EXPAND | GTK_FILL, 0, 0, 0);
1022         else
1023           gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
1024         break;
1025       }
1026     case SELECT:
1027       {
1028         GtkWidget *opt = gtk_option_menu_new ();
1029         GtkWidget *menu = gtk_menu_new ();
1030         GList *opts;
1031
1032         if (label && row)
1033           {
1034             GtkWidget *w = gtk_label_new (_(label));
1035             gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT);
1036             gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
1037             gtk_widget_show (w);
1038             gtk_table_attach (GTK_TABLE (parent), w, 0, 3, *row, *row + 1,
1039                               GTK_EXPAND | GTK_FILL, 0, 0, 0);
1040             (*row)++;
1041           }
1042
1043         for (opts = p->options; opts; opts = opts->next)
1044           {
1045             parameter *s = (parameter *) opts->data;
1046             GtkWidget *i = gtk_menu_item_new_with_label (_((char *) s->label));
1047             gtk_widget_show (i);
1048             gtk_menu_append (GTK_MENU (menu), i);
1049           }
1050
1051         gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu);
1052         p->widget = opt;
1053         if (row)
1054           gtk_table_attach (GTK_TABLE (parent), p->widget, 0, 3,
1055                             *row, *row + 1,
1056                             GTK_EXPAND | GTK_FILL, 0, 0, 0);
1057         else
1058           gtk_box_pack_start (GTK_BOX (parent), p->widget, TRUE, TRUE, 4);
1059         break;
1060       }
1061
1062     case COMMAND:
1063     case FAKE:
1064     case DESCRIPTION:
1065     case FAKEPREVIEW:
1066       break;
1067     default:
1068       abort();
1069     }
1070
1071   if (p->widget)
1072     {
1073       gtk_widget_set_name (p->widget, (char *) p->id);
1074       gtk_widget_show (p->widget);
1075       if (row)
1076         (*row)++;
1077     }
1078 }
1079
1080 \f
1081 /* File selection.
1082    Absurdly, there is no GTK file entry widget, only a GNOME one,
1083    so in order to avoid depending on GNOME in this code, we have
1084    to do it ourselves.
1085  */
1086
1087 /* cancel button on GtkFileSelection: user_data unused */
1088 static void
1089 file_sel_cancel (GtkWidget *button, gpointer user_data)
1090 {
1091   GtkWidget *dialog = button;
1092   while (dialog->parent)
1093     dialog = dialog->parent;
1094   gtk_widget_destroy (dialog);
1095 }
1096
1097 /* ok button on GtkFileSelection: user_data is the corresponding GtkEntry */
1098 static void
1099 file_sel_ok (GtkWidget *button, gpointer user_data)
1100 {
1101   GtkWidget *entry = GTK_WIDGET (user_data);
1102   GtkWidget *dialog = button;
1103   const char *path;
1104   while (dialog->parent)
1105     dialog = dialog->parent;
1106   gtk_widget_hide (dialog);
1107
1108   path = gtk_file_selection_get_filename (GTK_FILE_SELECTION (dialog));
1109   /* apparently one doesn't free `path' */
1110
1111   gtk_entry_set_text (GTK_ENTRY (entry), path);
1112   gtk_entry_set_position (GTK_ENTRY (entry), strlen (path));
1113
1114   gtk_widget_destroy (dialog);
1115 }
1116
1117 /* WM close on GtkFileSelection: user_data unused */
1118 static void
1119 file_sel_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
1120 {
1121   file_sel_cancel (widget, user_data);
1122 }
1123
1124 /* "Browse" button: user_data is the corresponding GtkEntry */
1125 static void
1126 browse_button_cb (GtkButton *button, gpointer user_data)
1127 {
1128   GtkWidget *entry = GTK_WIDGET (user_data);
1129   const char *text = gtk_entry_get_text (GTK_ENTRY (entry));
1130   GtkFileSelection *selector =
1131     GTK_FILE_SELECTION (gtk_file_selection_new (_("Select file.")));
1132
1133   gtk_file_selection_set_filename (selector, text);
1134   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
1135                       "clicked", GTK_SIGNAL_FUNC (file_sel_ok),
1136                       (gpointer) entry);
1137   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
1138                       "clicked", GTK_SIGNAL_FUNC (file_sel_cancel),
1139                       (gpointer) entry);
1140   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
1141                       GTK_SIGNAL_FUNC (file_sel_close),
1142                       (gpointer) entry);
1143
1144   gtk_window_set_modal (GTK_WINDOW (selector), TRUE);
1145   gtk_widget_show (GTK_WIDGET (selector));
1146 }
1147
1148 \f
1149 /* Converting to and from command-lines
1150  */
1151
1152
1153 /* Returns a copy of string that has been quoted according to shell rules:
1154    it may have been wrapped in "" and had some characters backslashed; or
1155    it may be unchanged.
1156  */
1157 static char *
1158 shell_quotify (const char *string)
1159 {
1160   char *string2 = (char *) malloc ((strlen (string) * 2) + 10);
1161   const char *in;
1162   char *out;
1163   int need_quotes = 0;
1164   int in_length = 0;
1165
1166   out = string2;
1167   *out++ = '"';
1168   for (in = string; *in; in++)
1169     {
1170       in_length++;
1171       if (*in == '!' ||
1172           *in == '"' ||
1173           *in == '$')
1174         {
1175           need_quotes = 1;
1176           *out++ = '\\';
1177           *out++ = *in;
1178         }
1179       else if (*in <= ' ' ||
1180                *in >= 127 ||
1181                *in == '\'' ||
1182                *in == '#' ||
1183                *in == '%' ||
1184                *in == '&' ||
1185                *in == '(' ||
1186                *in == ')' ||
1187                *in == '*')
1188         {
1189           need_quotes = 1;
1190           *out++ = *in;
1191         }
1192       else
1193         *out++ = *in;
1194     }
1195   *out++ = '"';
1196   *out = 0;
1197
1198   if (in_length == 0)
1199     need_quotes = 1;
1200
1201   if (need_quotes)
1202     return (string2);
1203
1204   free (string2);
1205   return strdup (string);
1206 }
1207
1208 /* Modify the string in place to remove wrapping double-quotes
1209    and interior backslashes. 
1210  */
1211 static void
1212 de_stringify (char *s)
1213 {
1214   char q = s[0];
1215   if (q != '\'' && q != '\"' && q != '`')
1216     abort();
1217   memmove (s, s+1, strlen (s)+1);
1218   while (*s && *s != q)
1219     {
1220       if (*s == '\\')
1221         memmove (s, s+1, strlen (s)+1);
1222       s++;
1223     }
1224   if (*s != q) abort();
1225   *s = 0;
1226 }
1227
1228
1229 /* Substitutes a shell-quotified version of `value' into `p->arg' at
1230    the place where the `%' character appeared.
1231  */
1232 static char *
1233 format_switch (parameter *p, const char *value)
1234 {
1235   char *fmt = (char *) p->arg;
1236   char *v2;
1237   char *result, *s;
1238   if (!fmt || !value) return 0;
1239   v2 = shell_quotify (value);
1240   result = (char *) malloc (strlen (fmt) + strlen (v2) + 10);
1241   s = result;
1242   for (; *fmt; fmt++)
1243     if (*fmt != '%')
1244       *s++ = *fmt;
1245     else
1246       {
1247         strcpy (s, v2);
1248         s += strlen (s);
1249       }
1250   *s = 0;
1251
1252   free (v2);
1253   return result;
1254 }
1255
1256
1257 /* Maps a `parameter' to a command-line switch.
1258    Returns 0 if it can't, or if the parameter has the default value.
1259  */
1260 static char *
1261 parameter_to_switch (parameter *p)
1262 {
1263   switch (p->type)
1264     {
1265     case COMMAND:
1266       if (p->arg)
1267         return strdup ((char *) p->arg);
1268       else
1269         return 0;
1270       break;
1271     case STRING:
1272     case FILENAME:
1273       if (!p->widget) return 0;
1274       {
1275         const char *s = gtk_entry_get_text (GTK_ENTRY (p->widget));
1276         char *v;
1277         if (!strcmp ((s ? s : ""),
1278                      (p->string ? (char *) p->string : "")))
1279           v = 0;  /* same as default */
1280         else
1281           v = format_switch (p, s);
1282
1283         /* don't free `s' */
1284         return v;
1285       }
1286     case SLIDER:
1287     case SPINBUTTON:
1288       if (!p->widget) return 0;
1289       {
1290         GtkAdjustment *adj =
1291           (p->type == SLIDER
1292            ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
1293            : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
1294         char buf[255];
1295         char *s1;
1296         float value = (p->invert_p
1297                        ? invert_range (adj->lower, adj->upper, adj->value) - 1
1298                        : adj->value);
1299
1300         if (value == p->value)  /* same as default */
1301           return 0;
1302
1303         if (p->integer_p)
1304           sprintf (buf, "%d", (int) (value + (value > 0 ? 0.5 : -0.5)));
1305         else
1306           sprintf (buf, "%.4f", value);
1307           
1308         s1 = strchr (buf, '.');
1309         if (s1)
1310           {
1311             char *s2 = s1 + strlen(s1) - 1;
1312             while (s2 > s1 && *s2 == '0')       /* lose trailing zeroes */
1313               *s2-- = 0;
1314             if (s2 >= s1 && *s2 == '.')         /* lose trailing decimal */
1315               *s2-- = 0;
1316           }
1317         return format_switch (p, buf);
1318       }
1319     case BOOLEAN:
1320       if (!p->widget) return 0;
1321       {
1322         GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget);
1323         const char *s = (gtk_toggle_button_get_active (b)
1324                          ? (char *) p->arg_set
1325                          : (char *) p->arg_unset);
1326         if (s)
1327           return strdup (s);
1328         else
1329           return 0;
1330       }
1331     case SELECT:
1332       if (!p->widget) return 0;
1333       {
1334         GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget);
1335         GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
1336         GtkWidget *selected = gtk_menu_get_active (menu);
1337         GList *kids = gtk_container_children (GTK_CONTAINER (menu));
1338         int menu_elt = g_list_index (kids, (gpointer) selected);
1339         GList *ol = g_list_nth (p->options, menu_elt);
1340         parameter *o = (ol ? (parameter *) ol->data : 0);
1341         const char *s;
1342         if (!o) abort();
1343         if (o->type != SELECT_OPTION) abort();
1344         s = (char *) o->arg_set;
1345         if (s)
1346           return strdup (s);
1347         else
1348           return 0;
1349       }
1350     default:
1351       if (p->widget)
1352         abort();
1353       else
1354         return 0;
1355     }
1356 }
1357
1358 /* Maps a GList of `parameter' objects to a complete command-line string.
1359    All arguments will be properly quoted.
1360  */
1361 static char *
1362 parameters_to_cmd_line (GList *parms)
1363 {
1364   int L = g_list_length (parms);
1365   int LL = 0;
1366   char **strs = (char **) calloc (sizeof (*parms), L);
1367   char *result;
1368   char *out;
1369   int i;
1370
1371   for (i = 0; parms; parms = parms->next, i++)
1372     {
1373       char *s = parameter_to_switch ((parameter *) parms->data);
1374       strs[i] = s;
1375       LL += (s ? strlen(s) : 0) + 1;
1376     }
1377
1378   result = (char *) malloc (LL + 10);
1379   out = result;
1380   for (i = 0; i < L; i++)
1381     if (strs[i])
1382       {
1383         strcpy (out, strs[i]);
1384         out += strlen (out);
1385         *out++ = ' ';
1386         free (strs[i]);
1387       }
1388   *out = 0;
1389   while (out > result && out[-1] == ' ')  /* strip trailing spaces */
1390     *(--out) = 0;
1391   free (strs);
1392
1393   return result;
1394 }
1395
1396
1397 /* Returns a GList of the tokens the string, using shell syntax;
1398    Quoted strings are handled as a single token.
1399  */
1400 static GList *
1401 tokenize_command_line (const char *cmd)
1402 {
1403   GList *result = 0;
1404   const char *s = cmd;
1405   while (*s)
1406     {
1407       const char *start;
1408       char *ss;
1409       for (; isspace(*s); s++);         /* skip whitespace */
1410
1411       start = s;
1412       if (*s == '\'' || *s == '\"' || *s == '`')
1413         {
1414           char q = *s;
1415           s++;
1416           while (*s && *s != q)         /* skip to matching quote */
1417             {
1418               if (*s == '\\' && s[1])   /* allowing backslash quoting */
1419                 s++;
1420               s++;
1421             }
1422           s++;
1423         }
1424       else
1425         {
1426           while (*s &&
1427                  (! (isspace(*s) ||
1428                      *s == '\'' ||
1429                      *s == '\"' ||
1430                      *s == '`')))
1431             s++;
1432         }
1433
1434       if (s > start)
1435         {
1436           ss = (char *) malloc ((s - start) + 1);
1437           strncpy (ss, start, s-start);
1438           ss[s-start] = 0;
1439           if (*ss == '\'' || *ss == '\"' || *ss == '`')
1440             de_stringify (ss);
1441           result = g_list_append (result, ss);
1442         }
1443     }
1444
1445   return result;
1446 }
1447
1448 static void parameter_set_switch (parameter *, gpointer value);
1449 static gboolean parse_command_line_into_parameters_1 (const char *filename,
1450                                                       GList *parms,
1451                                                       const char *option,
1452                                                       const char *value,
1453                                                       parameter *parent);
1454
1455
1456 /* Parses the command line, and flushes those options down into
1457    the `parameter' structs in the list.
1458  */
1459 static void
1460 parse_command_line_into_parameters (const char *filename,
1461                                     const char *cmd, GList *parms)
1462 {
1463   GList *tokens = tokenize_command_line (cmd);
1464   GList *rest;
1465   for (rest = tokens; rest; rest = rest->next)
1466     {
1467       char *option = rest->data;
1468       rest->data = 0;
1469
1470       if (option[0] != '-' && option[0] != '+')
1471         {
1472           if (debug_p)
1473             fprintf (stderr, "%s: WARNING: %s: not a switch: \"%s\"\n",
1474                      blurb(), filename, option);
1475         }
1476       else
1477         {
1478           char *value = 0;
1479
1480           if (rest->next)   /* pop off the arg to this option */
1481             {
1482               char *s = (char *) rest->next->data;
1483               /* the next token is the next switch iff it matches "-[a-z]".
1484                  (To avoid losing on "-x -3.1".)
1485                */
1486               if (s && (s[0] != '-' || !isalpha(s[1])))
1487                 {
1488                   value = s;
1489                   rest->next->data = 0;
1490                   rest = rest->next;
1491                 }
1492             }
1493
1494           parse_command_line_into_parameters_1 (filename, parms,
1495                                                 option, value, 0);
1496           if (value) free (value);
1497           free (option);
1498         }
1499     }
1500   g_list_free (tokens);
1501 }
1502
1503
1504 static gboolean
1505 compare_opts (const char *option, const char *value,
1506               const char *template)
1507 {
1508   int ol = strlen (option);
1509   char *c;
1510
1511   if (strncmp (option, template, ol))
1512     return FALSE;
1513
1514   if (template[ol] != (value ? ' ' : 0))
1515     return FALSE;
1516
1517   /* At this point, we have a match against "option".
1518      If template contains a %, we're done.
1519      Else, compare against "value" too.
1520    */
1521   c = strchr (template, '%');
1522   if (c)
1523     return TRUE;
1524
1525   if (!value)
1526     return (template[ol] == 0);
1527   if (strcmp (template + ol + 1, value))
1528     return FALSE;
1529
1530   return TRUE;
1531 }
1532
1533
1534 static gboolean
1535 parse_command_line_into_parameters_1 (const char *filename,
1536                                       GList *parms,
1537                                       const char *option,
1538                                       const char *value,
1539                                       parameter *parent)
1540 {
1541   GList *p;
1542   parameter *match = 0;
1543   gint which = -1;
1544   gint index = 0;
1545
1546   for (p = parms; p; p = p->next)
1547     {
1548       parameter *pp = (parameter *) p->data;
1549       which = -99;
1550
1551       if (pp->type == SELECT)
1552         {
1553           if (parse_command_line_into_parameters_1 (filename,
1554                                                     pp->options,
1555                                                     option, value,
1556                                                     pp))
1557             {
1558               which = -2;
1559               match = pp;
1560             }
1561         }
1562       else if (pp->arg)
1563         {
1564           if (compare_opts (option, value, (char *) pp->arg))
1565             {
1566               which = -1;
1567               match = pp;
1568             }
1569         }
1570       else if (pp->arg_set)
1571         {
1572           if (compare_opts (option, value, (char *) pp->arg_set))
1573             {
1574               which = 1;
1575               match = pp;
1576             }
1577         }
1578       else if (pp->arg_unset)
1579         {
1580           if (compare_opts (option, value, (char *) pp->arg_unset))
1581             {
1582               which = 0;
1583               match = pp;
1584             }
1585         }
1586
1587       if (match)
1588         break;
1589
1590       index++;
1591     }
1592
1593   if (!match)
1594     {
1595       if (debug_p && !parent)
1596         fprintf (stderr, "%s: WARNING: %s: no match for %s %s\n",
1597                  blurb(), filename, option, (value ? value : ""));
1598       return FALSE;
1599     }
1600
1601   switch (match->type)
1602     {
1603     case STRING:
1604     case FILENAME:
1605     case SLIDER:
1606     case SPINBUTTON:
1607       if (which != -1) abort();
1608       parameter_set_switch (match, (gpointer) value);
1609       break;
1610     case BOOLEAN:
1611       if (which != 0 && which != 1) abort();
1612       parameter_set_switch (match, GINT_TO_POINTER(which));
1613       break;
1614     case SELECT_OPTION:
1615       if (which != 1) abort();
1616       parameter_set_switch (parent, GINT_TO_POINTER(index));
1617       break;
1618     default:
1619       break;
1620     }
1621   return TRUE;
1622 }
1623
1624
1625 /* Set the parameter's value.
1626    For STRING, FILENAME, SLIDER, and SPINBUTTON, `value' is a char*.
1627    For BOOLEAN and SELECT, `value' is an int.
1628  */
1629 static void
1630 parameter_set_switch (parameter *p, gpointer value)
1631 {
1632   if (p->type == SELECT_OPTION) abort();
1633   if (!p->widget) return;
1634   switch (p->type)
1635     {
1636     case STRING:
1637     case FILENAME:
1638       {
1639         gtk_entry_set_text (GTK_ENTRY (p->widget), (char *) value);
1640         break;
1641       }
1642     case SLIDER:
1643     case SPINBUTTON:
1644       {
1645         GtkAdjustment *adj =
1646           (p->type == SLIDER
1647            ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
1648            : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
1649         float f;
1650         char c;
1651
1652         if (1 == sscanf ((char *) value, "%f %c", &f, &c))
1653           {
1654             if (p->invert_p)
1655               f = invert_range (adj->lower, adj->upper, f) - 1;
1656             gtk_adjustment_set_value (adj, f);
1657           }
1658         break;
1659       }
1660     case BOOLEAN:
1661       {
1662         GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget);
1663         gtk_toggle_button_set_active (b, GPOINTER_TO_INT(value));
1664         break;
1665       }
1666     case SELECT:
1667       {
1668         gtk_option_menu_set_history (GTK_OPTION_MENU (p->widget),
1669                                      GPOINTER_TO_INT(value));
1670         break;
1671       }
1672     default:
1673       abort();
1674     }
1675 }
1676
1677
1678 static void
1679 restore_defaults (const char *progname, GList *parms)
1680 {
1681   for (; parms; parms = parms->next)
1682     {
1683       parameter *p = (parameter *) parms->data;
1684       if (!p->widget) continue;
1685       switch (p->type)
1686         {
1687         case STRING:
1688         case FILENAME:
1689           {
1690             gtk_entry_set_text (GTK_ENTRY (p->widget),
1691                                 (p->string ? (char *) p->string : ""));
1692             break;
1693           }
1694         case SLIDER:
1695         case SPINBUTTON:
1696           {
1697             GtkAdjustment *adj =
1698               (p->type == SLIDER
1699                ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
1700                : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
1701             float value = (p->invert_p
1702                            ? invert_range (p->low, p->high, p->value)
1703                            : p->value);
1704             gtk_adjustment_set_value (adj, value);
1705             break;
1706           }
1707         case BOOLEAN:
1708           {
1709             /* A toggle button should be on by default if it inserts
1710                nothing into the command line when on.  E.g., it should
1711                be on if `arg_set' is null.
1712              */
1713             gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (p->widget),
1714                                           (!p->arg_set || !*p->arg_set));
1715             break;
1716           }
1717         case SELECT:
1718           {
1719             GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget);
1720             GList *opts;
1721             int selected = 0;
1722             int index;
1723
1724             for (opts = p->options, index = 0; opts;
1725                  opts = opts->next, index++)
1726               {
1727                 parameter *s = (parameter *) opts->data;
1728                 /* The default menu item is the first one with
1729                    no `arg_set' field. */
1730                 if (!s->arg_set)
1731                   {
1732                     selected = index;
1733                     break;
1734                   }
1735               }
1736
1737             gtk_option_menu_set_history (GTK_OPTION_MENU (opt), selected);
1738             break;
1739           }
1740         default:
1741           abort();
1742         }
1743     }
1744 }
1745
1746
1747 \f
1748 /* Documentation strings
1749  */
1750
1751 static char *
1752 get_description (GList *parms, gboolean verbose_p)
1753 {
1754   parameter *doc = 0;
1755   for (; parms; parms = parms->next)
1756     {
1757       parameter *p = (parameter *) parms->data;
1758       if (p->type == DESCRIPTION)
1759         {
1760           doc = p;
1761           break;
1762         }
1763     }
1764
1765   if (!doc || !doc->string)
1766     return 0;
1767   else
1768     {
1769       char *d = strdup ((char *) doc->string);
1770       char *s;
1771       char *p;
1772       for (s = d; *s; s++)
1773         if (s[0] == '\n')
1774           {
1775             if (s[1] == '\n')      /* blank line: leave it */
1776               s++;
1777             else if (s[1] == ' ' || s[1] == '\t')
1778               s++;                 /* next line is indented: leave newline */
1779             else
1780               s[0] = ' ';          /* delete newline to un-fold this line */
1781           }
1782
1783       /* strip off leading whitespace on first line only */
1784       for (s = d; *s && (*s == ' ' || *s == '\t'); s++)
1785         ;
1786       while (*s == '\n')   /* strip leading newlines */
1787         s++;
1788       if (s != d)
1789         memmove (d, s, strlen(s)+1);
1790
1791       /* strip off trailing whitespace and newlines */
1792       {
1793         int L = strlen(d);
1794         while (L && isspace(d[L-1]))
1795           d[--L] = 0;
1796       }
1797
1798       /* strip off duplicated whitespaces */
1799       for (s = d; *s; s++)
1800           if (s[0] == ' ')
1801           {
1802             p = s+1;
1803             while (*s == ' ')
1804               s++;
1805             if (*p && (s != p))
1806               memmove (p, s, strlen(s)+1);
1807           }
1808
1809 #if 0
1810       if (verbose_p)
1811         {
1812           fprintf (stderr, "%s: text read   is \"%s\"\n", blurb(),doc->string);
1813           fprintf (stderr, "%s: description is \"%s\"\n", blurb(), d);
1814           fprintf (stderr, "%s: translation is \"%s\"\n", blurb(), _(d));
1815         }
1816 #endif /* 0 */
1817
1818       return (d);
1819     }
1820 }
1821
1822 \f
1823 /* External interface.
1824  */
1825
1826 static conf_data *
1827 load_configurator_1 (const char *program, const char *arguments,
1828                      gboolean verbose_p)
1829 {
1830   const char *dir = hack_configuration_path;
1831   char *base_program;
1832   int L = strlen (dir);
1833   char *file;
1834   char *s;
1835   FILE *f;
1836   conf_data *data;
1837
1838   if (L == 0) return 0;
1839
1840   base_program = strrchr(program, '/');
1841   if (base_program) base_program++;
1842   if (!base_program) base_program = (char *) program;
1843
1844   file = (char *) malloc (L + strlen (base_program) + 10);
1845   data = (conf_data *) calloc (1, sizeof(*data));
1846
1847   strcpy (file, dir);
1848   if (file[L-1] != '/')
1849     file[L++] = '/';
1850   strcpy (file+L, base_program);
1851
1852   for (s = file+L; *s; s++)
1853     if (*s == '/' || *s == ' ')
1854       *s = '_';
1855     else if (isupper (*s))
1856       *s = tolower (*s);
1857
1858   strcat (file+L, ".xml");
1859
1860   f = fopen (file, "r");
1861   if (f)
1862     {
1863       int res, size = 1024;
1864       char chars[1024];
1865       xmlParserCtxtPtr ctxt;
1866       xmlDocPtr doc = 0;
1867       GtkWidget *table;
1868       GList *parms;
1869
1870       if (verbose_p)
1871         fprintf (stderr, "%s: reading %s...\n", blurb(), file);
1872
1873       res = fread (chars, 1, 4, f);
1874       if (res <= 0)
1875         {
1876           free (data);
1877           data = 0;
1878           goto DONE;
1879         }
1880
1881       ctxt = xmlCreatePushParserCtxt(NULL, NULL, chars, res, file);
1882       while ((res = fread(chars, 1, size, f)) > 0)
1883         xmlParseChunk (ctxt, chars, res, 0);
1884       xmlParseChunk (ctxt, chars, 0, 1);
1885       doc = ctxt->myDoc;
1886       xmlFreeParserCtxt (ctxt);
1887       fclose (f);
1888
1889       /* Parsed the XML file.  Now make some widgets. */
1890
1891       table = gtk_table_new (1, 3, FALSE);
1892       gtk_table_set_row_spacings (GTK_TABLE (table), 4);
1893       gtk_table_set_col_spacings (GTK_TABLE (table), 4);
1894       gtk_container_set_border_width (GTK_CONTAINER (table), 8);
1895       gtk_widget_show (table);
1896
1897       parms = make_parameters (file, doc->xmlRootNode, table);
1898       sanity_check_parameters (file, parms);
1899
1900       xmlFreeDoc (doc);
1901
1902       restore_defaults (program, parms);
1903       if (arguments && *arguments)
1904         parse_command_line_into_parameters (program, arguments, parms);
1905
1906       data->widget = table;
1907       data->parameters = parms;
1908       data->description = get_description (parms, verbose_p);
1909     }
1910   else
1911     {
1912       parameter *p;
1913
1914       if (verbose_p)
1915         fprintf (stderr, "%s: %s does not exist.\n", blurb(), file);
1916
1917       p = calloc (1, sizeof(*p));
1918       p->type = COMMAND;
1919       p->arg = (xmlChar *) strdup (arguments);
1920
1921       data->parameters = g_list_append (0, (gpointer) p);
1922     }
1923
1924   data->progname = strdup (program);
1925
1926  DONE:
1927   free (file);
1928   return data;
1929 }
1930
1931 static void
1932 split_command_line (const char *full_command_line,
1933                     char **prog_ret, char **args_ret)
1934 {
1935   char *line = strdup (full_command_line);
1936   char *prog;
1937   char *args;
1938   char *s;
1939
1940   prog = line;
1941   s = line;
1942   while (*s)
1943     {
1944       if (isspace (*s))
1945         {
1946           *s = 0;
1947           s++;
1948           while (isspace (*s)) s++;
1949           break;
1950         }
1951       else if (*s == '=')  /* if the leading word contains an "=", skip it. */
1952         {
1953           while (*s && !isspace (*s)) s++;
1954           while (isspace (*s)) s++;
1955           prog = s;
1956         }
1957       s++;
1958     }
1959   args = s;
1960
1961   *prog_ret = strdup (prog);
1962   *args_ret = strdup (args);
1963   free (line);
1964 }
1965
1966
1967 conf_data *
1968 load_configurator (const char *full_command_line, gboolean verbose_p)
1969 {
1970   char *prog;
1971   char *args;
1972   conf_data *cd;
1973   debug_p = verbose_p;
1974   split_command_line (full_command_line, &prog, &args);
1975   cd = load_configurator_1 (prog, args, verbose_p);
1976   free (prog);
1977   free (args);
1978   return cd;
1979 }
1980
1981
1982
1983 char *
1984 get_configurator_command_line (conf_data *data)
1985 {
1986   char *args = parameters_to_cmd_line (data->parameters);
1987   char *result = (char *) malloc (strlen (data->progname) +
1988                                   strlen (args) + 2);
1989   strcpy (result, data->progname);
1990   strcat (result, " ");
1991   strcat (result, args);
1992   free (args);
1993   return result;
1994 }
1995
1996
1997 void
1998 set_configurator_command_line (conf_data *data, const char *full_command_line)
1999 {
2000   char *prog;
2001   char *args;
2002   split_command_line (full_command_line, &prog, &args);
2003   if (data->progname) free (data->progname);
2004   data->progname = prog;
2005   restore_defaults (prog, data->parameters);
2006   parse_command_line_into_parameters (prog, args, data->parameters);
2007   free (args);
2008 }
2009
2010 void
2011 free_conf_data (conf_data *data)
2012 {
2013   if (data->parameters)
2014     {
2015       GList *rest;
2016       for (rest = data->parameters; rest; rest = rest->next)
2017         {
2018           free_parameter ((parameter *) rest->data);
2019           rest->data = 0;
2020         }
2021       g_list_free (data->parameters);
2022       data->parameters = 0;
2023     }
2024
2025   if (data->widget)
2026     gtk_widget_destroy (data->widget);
2027
2028   if (data->progname)
2029     free (data->progname);
2030   if (data->description)
2031     free (data->description);
2032
2033   memset (data, ~0, sizeof(*data));
2034   free (data);
2035 }
2036
2037
2038 #endif /* HAVE_GTK && HAVE_XML -- whole file */