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