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