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