1 /* demo-Gtk-conf.c --- implements the dynamic configuration dialogs.
2 * xscreensaver, Copyright (c) 2001 Jamie Zawinski <jwz@jwz.org>
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
17 #if defined(HAVE_GTK) && defined(HAVE_XML) /* whole file */
29 #include <parser.h> /* XML */
33 #include "demo-Gtk-conf.h"
35 #if (LIBXML_VERSION >= 20000) /* illiteracy finally fixed */
36 # define childs children
37 # define root children
40 extern const char *blurb (void);
43 static gboolean debug_p = FALSE;
65 char *id; /* widget name */
66 char *label; /* heading label, or null */
68 /* command, fake, description, fakepreview, string, file
70 char *string; /* file name, description, whatever. */
74 char *low_label; /* label for the left side */
75 char *high_label; /* label for the right side */
76 float low; /* minimum value */
77 float high; /* maximum value */
78 float value; /* default value */
79 gboolean integer_p; /* whether the range is integral, or real */
80 char *arg; /* command-line option to set (substitute "%") */
81 gboolean invert_p; /* whether to flip the value and pretend the
82 range goes from hi-low instead of low-hi. */
84 /* boolean, select-option
86 char *arg_set; /* command-line option to set for "yes", or null */
87 char *arg_unset; /* command-line option to set for "no", or null */
88 char *test; /* #### no idea - enablement? */
103 static parameter *make_select_option (const char *file, xmlNodePtr);
104 static void make_parameter_widget (const char *filename,
105 parameter *, GtkWidget *, int *);
106 static void browse_button_cb (GtkButton *button, gpointer user_data);
109 /* Frees the parameter object and all strings and sub-parameters.
110 Does not destroy the widget, if any.
113 free_parameter (parameter *p)
116 if (p->id) free (p->id);
117 if (p->label) free (p->label);
118 if (p->string) free (p->string);
119 if (p->low_label) free (p->low_label);
120 if (p->high_label) free (p->high_label);
121 if (p->arg) free (p->arg);
122 if (p->arg_set) free (p->arg_set);
123 if (p->arg_unset) free (p->arg_unset);
124 if (p->test) free (p->test);
126 for (rest = p->options; rest; rest = rest->next)
128 free_parameter ((parameter *) rest->data);
130 for (rest = p->enable; rest; rest = rest->next)
132 free ((char *) rest->data);
135 if (p->enable) g_list_free (p->enable);
137 memset (p, ~0, sizeof(*p));
142 /* Debugging: dumps out a `parameter' structure.
146 describe_parameter (FILE *out, parameter *p)
151 case COMMAND: fprintf (out, "command"); break;
152 case FAKE: fprintf (out, "fake"); break;
153 case DESCRIPTION: fprintf (out, "_description"); break;
154 case FAKEPREVIEW: fprintf (out, "fakepreview"); break;
155 case STRING: fprintf (out, "string"); break;
156 case FILENAME: fprintf (out, "filename"); break;
157 case SLIDER: fprintf (out, "number type=\"slider\""); break;
158 case SPINBUTTON: fprintf (out, "number type=\"spinbutton\""); break;
159 case BOOLEAN: fprintf (out, "boolean"); break;
160 case SELECT: fprintf (out, "select"); break;
161 default: abort(); break;
163 if (p->id) fprintf (out, " id=\"%s\"", p->id);
164 if (p->label) fprintf (out, " _label=\"%s\"", p->label);
165 if (p->string && p->type != DESCRIPTION)
166 fprintf (out, " string=\"%s\"", p->string);
167 if (p->low_label) fprintf (out, " _low-label=\"%s\"", p->low_label);
168 if (p->high_label) fprintf (out, " _high-label=\"%s\"", p->high_label);
169 if (p->low) fprintf (out, " low=\"%.2f\"", p->low);
170 if (p->high) fprintf (out, " high=\"%.2f\"", p->high);
171 if (p->value) fprintf (out, " default=\"%.2f\"", p->value);
172 if (p->arg) fprintf (out, " arg=\"%s\"", p->arg);
173 if (p->invert_p) fprintf (out, " convert=\"invert\"");
174 if (p->arg_set) fprintf (out, " arg-set=\"%s\"", p->arg_set);
175 if (p->arg_unset) fprintf (out, " arg-unset=\"%s\"", p->arg_unset);
176 if (p->test) fprintf (out, " test=\"%s\"", p->test);
177 fprintf (out, ">\n");
179 if (p->type == SELECT)
182 for (opt = p->options; opt; opt = opt->next)
184 parameter *o = (parameter *) opt->data;
185 if (o->type != SELECT_OPTION) abort();
186 fprintf (out, " <option");
187 if (o->id) fprintf (out, " id=\"%s\"", o->id);
188 if (o->label) fprintf (out, " _label=\"%s\"", o->label);
189 if (o->arg_set) fprintf (out, " arg-set=\"%s\"", o->arg_set);
190 if (o->arg_unset) fprintf (out, " arg-unset=\"%s\"", o->arg_unset);
191 if (o->test) fprintf (out, " test=\"%s\"", o->test);
195 fprintf (out, " enable=\"");
196 for (e = o->enable; e; e = e->next)
197 fprintf (out, "%s%s", (char *) e->data, (e->next ? "," : ""));
200 fprintf (out, ">\n");
202 fprintf (out, "</select>\n");
204 else if (p->type == DESCRIPTION)
207 fprintf (out, " %s\n", p->string);
208 fprintf (out, "</_description>\n");
214 /* Like xmlGetProp() but parses a float out of the string.
215 If the number was expressed as a float and not an integer
216 (that is, the string contained a decimal point) then
217 `floatp' is set to TRUE. Otherwise, it is unchanged.
220 xml_get_float (xmlNodePtr node, const char *name, gboolean *floatpP)
222 const char *s = xmlGetProp (node, name);
225 if (!s || 1 != sscanf (s, "%f %c", &f, &c))
229 if (strchr (s, '.')) *floatpP = TRUE;
235 static void sanity_check_parameter (const char *filename,
236 const char *node_name,
239 /* Allocates and returns a new `parameter' object based on the
240 properties in the given XML node. Returns 0 if there's nothing
241 to create (comment, or unknown tag.)
244 make_parameter (const char *filename, xmlNodePtr node)
247 const char *name = node->name;
249 gboolean floatp = FALSE;
251 if (node->type == XML_COMMENT_NODE)
254 p = calloc (1, sizeof(*p));
257 else if (!strcmp (name, "command")) p->type = COMMAND;
258 else if (!strcmp (name, "fullcommand")) p->type = COMMAND;
259 else if (!strcmp (name, "_description")) p->type = DESCRIPTION;
260 else if (!strcmp (name, "fakepreview")) p->type = FAKEPREVIEW;
261 else if (!strcmp (name, "fake")) p->type = FAKE;
262 else if (!strcmp (name, "boolean")) p->type = BOOLEAN;
263 else if (!strcmp (name, "string")) p->type = STRING;
264 else if (!strcmp (name, "file")) p->type = FILENAME;
265 else if (!strcmp (name, "number")) p->type = SPINBUTTON;
266 else if (!strcmp (name, "select")) p->type = SELECT;
270 fprintf (stderr, "%s: WARNING: %s: unknown tag: \"%s\"\n",
271 blurb(), filename, name);
276 if (p->type == SPINBUTTON)
278 const char *type = xmlGetProp (node, "type");
279 if (!type || !strcmp (type, "spinbutton")) p->type = SPINBUTTON;
280 else if (!strcmp (type, "slider")) p->type = SLIDER;
284 fprintf (stderr, "%s: WARNING: %s: unknown %s type: \"%s\"\n",
285 blurb(), filename, name, type);
290 else if (p->type == DESCRIPTION)
293 node->childs->type == XML_TEXT_NODE &&
295 p->string = strdup (node->childs->content);
298 p->id = xmlGetProp (node, "id");
299 p->label = xmlGetProp (node, "_label");
300 p->low_label = xmlGetProp (node, "_low-label");
301 p->high_label = xmlGetProp (node, "_high-label");
302 p->low = xml_get_float (node, "low", &floatp);
303 p->high = xml_get_float (node, "high", &floatp);
304 p->value = xml_get_float (node, "default", &floatp);
305 p->integer_p = !floatp;
306 convert = xmlGetProp (node, "convert");
307 p->invert_p = (convert && !strcmp (convert, "invert"));
308 p->arg = xmlGetProp (node, "arg");
309 p->arg_set = xmlGetProp (node, "arg-set");
310 p->arg_unset = xmlGetProp (node, "arg-unset");
311 p->test = xmlGetProp (node, "test");
313 /* Check for missing decimal point */
316 (p->high != p->low) &&
317 (p->high - p->low) <= 1)
319 "%s: WARNING: %s: %s: range [%.1f, %.1f] shouldn't be integral!\n",
320 blurb(), filename, p->id,
323 if (p->type == SELECT)
326 for (kids = node->childs; kids; kids = kids->next)
328 parameter *s = make_select_option (filename, kids);
330 p->options = g_list_append (p->options, s);
334 sanity_check_parameter (filename, name, p);
340 /* Allocates and returns a new SELECT_OPTION `parameter' object based
341 on the properties in the given XML node. Returns 0 if there's nothing
342 to create (comment, or unknown tag.)
345 make_select_option (const char *filename, xmlNodePtr node)
347 if (node->type == XML_COMMENT_NODE)
349 else if (node->type != XML_ELEMENT_NODE)
353 "%s: WARNING: %s: %s: unexpected child tag type %d\n",
354 blurb(), filename, node->name, (int)node->type);
357 else if (strcmp (node->name, "option"))
361 "%s: WARNING: %s: %s: child not an option tag: \"%s\"\n",
362 blurb(), filename, node->name, node->name);
367 parameter *s = calloc (1, sizeof(*s));
370 s->type = SELECT_OPTION;
371 s->id = xmlGetProp (node, "id");
372 s->label = xmlGetProp (node, "_label");
373 s->arg_set = xmlGetProp (node, "arg-set");
374 s->arg_unset = xmlGetProp (node, "arg-unset");
375 s->test = xmlGetProp (node, "test");
376 enable = xmlGetProp (node, "enable");
380 enable = strdup (enable);
381 e = strtok (enable, ", ");
384 s->enable = g_list_append (s->enable, strdup (e));
385 e = strtok (0, ", ");
390 sanity_check_parameter (filename, node->name, s);
396 /* Rudimentary check to make sure someone hasn't typed "arg-set="
397 when they should have typed "arg=", etc.
400 sanity_check_parameter (const char *filename, const char *node_name,
418 memset (&allowed, 0, sizeof (allowed));
419 memset (&require, 0, sizeof (require));
430 allowed.string = TRUE;
437 allowed.label = TRUE;
438 require.label = TRUE;
445 allowed.label = TRUE;
452 allowed.label = TRUE;
453 allowed.low_label = TRUE;
454 allowed.high_label = TRUE;
458 /* require.low = TRUE; -- may be 0 */
460 /* require.high = TRUE; -- may be 0 */
461 allowed.value = TRUE;
462 /* require.value = TRUE; -- may be 0 */
463 allowed.invert_p = TRUE;
468 allowed.label = TRUE;
472 /* require.low = TRUE; -- may be 0 */
474 /* require.high = TRUE; -- may be 0 */
475 allowed.value = TRUE;
476 /* require.value = TRUE; -- may be 0 */
477 allowed.invert_p = TRUE;
482 allowed.label = TRUE;
483 allowed.arg_set = TRUE;
484 allowed.arg_unset = TRUE;
492 allowed.label = TRUE;
493 require.label = TRUE;
494 allowed.arg_set = TRUE;
502 fprintf (stderr, "%s: %s: " STR " in <%s%s id=\"%s\">\n", \
503 blurb(), filename, node_name, \
504 (!strcmp(node_name, "number") \
505 ? (p->type == SPINBUTTON ? " type=spinbutton" : " type=slider")\
507 (p->id ? p->id : ""))
508 # define CHECK(SLOT,NAME) \
509 if (p->SLOT && !allowed.SLOT) \
510 WARN ("\"" NAME "\" is not a valid option"); \
511 if (!p->SLOT && require.SLOT) \
512 WARN ("\"" NAME "\" is required")
515 CHECK (label, "_label");
516 CHECK (string, "(body text)");
517 CHECK (low_label, "_low-label");
518 CHECK (high_label, "_high-label");
520 CHECK (high, "high");
521 CHECK (value, "default");
523 CHECK (invert_p, "convert");
524 CHECK (arg_set, "arg-set");
525 CHECK (arg_unset, "arg-unset");
532 /* Helper for make_parameters()
535 make_parameters_1 (const char *filename, xmlNodePtr node,
536 GtkWidget *parent, int *row)
540 for (; node; node = node->next)
542 const char *name = node->name;
543 if (!strcmp (name, "hgroup") ||
544 !strcmp (name, "vgroup"))
546 GtkWidget *box = (*name == 'h'
547 ? gtk_hbox_new (FALSE, 0)
548 : gtk_vbox_new (FALSE, 0));
550 gtk_widget_show (box);
553 gtk_table_attach (GTK_TABLE (parent), box, 0, 3, *row, *row + 1,
556 gtk_box_pack_start (GTK_BOX (parent), box, FALSE, FALSE, 0);
561 list2 = make_parameters_1 (filename, node->childs, box, 0);
563 list = g_list_concat (list, list2);
567 parameter *p = make_parameter (filename, node);
570 list = g_list_append (list, p);
571 make_parameter_widget (filename, p, parent, row);
579 /* Calls make_parameter() and make_parameter_widget() on each relevant
580 tag in the XML tree. Also handles the "hgroup" and "vgroup" flags.
581 Returns a GList of `parameter' objects.
584 make_parameters (const char *filename, xmlNodePtr node, GtkWidget *parent)
587 for (; node; node = node->next)
589 if (node->type == XML_ELEMENT_NODE &&
590 !strcmp (node->name, "screensaver"))
591 return make_parameters_1 (filename, node->childs, parent, &row);
598 invert_range (gfloat low, gfloat high, gfloat value)
600 gfloat range = high-low;
601 gfloat off = value-low;
602 return (low + (range - off));
606 static GtkAdjustment *
607 make_adjustment (const char *filename, parameter *p)
609 float range = (p->high - p->low);
610 float value = (p->invert_p
611 ? invert_range (p->low, p->high, p->value)
613 gfloat si = (p->high - p->low) / 100;
614 gfloat pi = (p->high - p->low) / 10;
616 if (p->value < p->low || p->value > p->high)
618 if (debug_p && p->integer_p)
619 fprintf (stderr, "%s: WARNING: %s: %d is not in range [%d, %d]\n",
621 (int) p->value, (int) p->low, (int) p->high);
624 "%s: WARNING: %s: %.2f is not in range [%.2f, %.2f]\n",
625 blurb(), filename, p->value, p->low, p->high);
626 value = (value < p->low ? p->low : p->high);
629 else if (debug_p && p->value < 1000 && p->high >= 10000)
633 "%s: WARNING: %s: %d is suspicious for range [%d, %d]\n",
635 (int) p->value, (int) p->low, (int) p->high);
638 "%s: WARNING: %s: %.2f is suspicious for range [%.2f, %.2f]\n",
639 blurb(), filename, p->value, p->low, p->high);
645 si = (int) (si + 0.5);
646 pi = (int) (pi + 0.5);
650 if (range <= 500) si = 1;
652 return GTK_ADJUSTMENT (gtk_adjustment_new (value, p->low, p->high,
658 /* Given a `parameter' struct, allocates an appropriate GtkWidget for it,
659 and stores it in `p->widget'.
660 `row' is used for keeping track of our position during table layout.
661 `parent' must be a GtkTable or a GtkBox.
664 make_parameter_widget (const char *filename,
665 parameter *p, GtkWidget *parent, int *row)
667 const char *label = p->label;
668 if (p->widget) return;
676 GtkWidget *w = gtk_label_new (label);
677 gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
678 gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
681 gtk_table_attach (GTK_TABLE (parent), w, 0, 1, *row, *row + 1,
684 gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
687 p->widget = gtk_entry_new ();
689 gtk_entry_set_text (GTK_ENTRY (p->widget), p->string);
691 gtk_table_attach (GTK_TABLE (parent), p->widget, 1, 3,
695 gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
700 GtkWidget *L = gtk_label_new (label ? label : "");
701 GtkWidget *entry = gtk_entry_new ();
702 GtkWidget *button = gtk_button_new_with_label ("Browse...");
703 gtk_widget_show (entry);
704 gtk_widget_show (button);
707 gtk_signal_connect (GTK_OBJECT (button),
708 "clicked", GTK_SIGNAL_FUNC (browse_button_cb),
711 gtk_label_set_justify (GTK_LABEL (L), GTK_JUSTIFY_RIGHT);
712 gtk_misc_set_alignment (GTK_MISC (L), 1.0, 0.5);
716 gtk_entry_set_text (GTK_ENTRY (entry), p->string);
720 gtk_table_attach (GTK_TABLE (parent), L, 0, 1,
723 gtk_table_attach (GTK_TABLE (parent), entry, 1, 2,
725 GTK_EXPAND | GTK_FILL, 0, 0, 0);
726 gtk_table_attach (GTK_TABLE (parent), button, 2, 3,
732 gtk_box_pack_start (GTK_BOX (parent), L, FALSE, FALSE, 4);
733 gtk_box_pack_start (GTK_BOX (parent), entry, TRUE, TRUE, 4);
734 gtk_box_pack_start (GTK_BOX (parent), button, FALSE, FALSE, 4);
740 GtkAdjustment *adj = make_adjustment (filename, p);
741 GtkWidget *scale = gtk_hscale_new (adj);
742 GtkWidget *labelw = 0;
746 labelw = gtk_label_new (label);
747 gtk_label_set_justify (GTK_LABEL (labelw), GTK_JUSTIFY_LEFT);
748 gtk_misc_set_alignment (GTK_MISC (labelw), 0.0, 0.5);
749 gtk_widget_show (labelw);
752 if (GTK_IS_VBOX (parent))
754 /* If we're inside a vbox, we need to put an hbox in it, to get
755 the low/high labels to be to the left/right of the slider.
757 GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
759 /* But if we have a label, put that above the slider's hbox. */
762 gtk_box_pack_start (GTK_BOX (parent), labelw, FALSE, TRUE, 2);
766 gtk_box_pack_start (GTK_BOX (parent), hbox, TRUE, TRUE, 6);
767 gtk_widget_show (hbox);
775 gtk_table_attach (GTK_TABLE (parent), labelw,
776 0, 3, *row, *row + 1,
777 GTK_EXPAND | GTK_FILL, 0, 0, 0);
782 if (GTK_IS_HBOX (parent))
784 GtkWidget *box = gtk_vbox_new (FALSE, 0);
785 gtk_box_pack_start (GTK_BOX (parent), box, FALSE, TRUE, 0);
786 gtk_widget_show (box);
787 gtk_box_pack_start (GTK_BOX (box), labelw, FALSE, TRUE, 4);
789 box = gtk_hbox_new (FALSE, 0);
790 gtk_widget_show (box);
791 gtk_box_pack_start (GTK_BOX (parent), box, TRUE, TRUE, 0);
795 gtk_box_pack_start (GTK_BOX (parent), labelw,
802 GtkWidget *w = gtk_label_new (p->low_label);
803 gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
804 gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
807 gtk_table_attach (GTK_TABLE (parent), w, 0, 1, *row, *row + 1,
810 gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
813 gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_BOTTOM);
814 gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);
815 gtk_scale_set_digits (GTK_SCALE (scale), (p->integer_p ? 0 : 2));
817 gtk_table_attach (GTK_TABLE (parent), scale, 1, 2,
819 GTK_EXPAND | GTK_FILL, 0, 0, 0);
821 gtk_box_pack_start (GTK_BOX (parent), scale, TRUE, TRUE, 4);
823 gtk_widget_show (scale);
827 GtkWidget *w = gtk_label_new (p->high_label);
828 gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT);
829 gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
832 gtk_table_attach (GTK_TABLE (parent), w, 2, 3, *row, *row + 1,
835 gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
843 GtkAdjustment *adj = make_adjustment (filename, p);
844 GtkWidget *spin = gtk_spin_button_new (adj, 15, 0);
845 gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE);
846 gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (spin), TRUE);
847 gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), adj->value);
851 GtkWidget *w = gtk_label_new (label);
852 gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
853 gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
856 gtk_table_attach (GTK_TABLE (parent), w, 0, 1, *row, *row + 1,
859 gtk_box_pack_start (GTK_BOX (parent), w, TRUE, TRUE, 4);
862 gtk_widget_show (spin);
865 GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
866 gtk_widget_show (hbox);
867 gtk_table_attach (GTK_TABLE (parent), hbox, 1, 3,
869 GTK_EXPAND | GTK_FILL, 0, 8, 0);
870 gtk_box_pack_start (GTK_BOX (hbox), spin, FALSE, FALSE, 0);
873 gtk_box_pack_start (GTK_BOX (parent), spin, FALSE, FALSE, 4);
880 p->widget = gtk_check_button_new_with_label (label);
882 gtk_table_attach (GTK_TABLE (parent), p->widget, 0, 3,
884 GTK_EXPAND | GTK_FILL, 0, 0, 0);
886 gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
891 GtkWidget *opt = gtk_option_menu_new ();
892 GtkWidget *menu = gtk_menu_new ();
897 GtkWidget *w = gtk_label_new (label);
898 gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT);
899 gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
901 gtk_table_attach (GTK_TABLE (parent), w, 0, 3, *row, *row + 1,
902 GTK_EXPAND | GTK_FILL, 0, 0, 0);
906 for (opts = p->options; opts; opts = opts->next)
908 parameter *s = (parameter *) opts->data;
909 GtkWidget *i = gtk_menu_item_new_with_label (s->label);
911 gtk_menu_append (GTK_MENU (menu), i);
914 gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu);
917 gtk_table_attach (GTK_TABLE (parent), p->widget, 0, 3,
919 GTK_EXPAND | GTK_FILL, 0, 0, 0);
921 gtk_box_pack_start (GTK_BOX (parent), p->widget, TRUE, TRUE, 4);
936 gtk_widget_set_name (p->widget, p->id);
937 gtk_widget_show (p->widget);
945 Absurdly, there is no GTK file entry widget, only a GNOME one,
946 so in order to avoid depending on GNOME in this code, we have
950 /* cancel button on GtkFileSelection: user_data unused */
952 file_sel_cancel (GtkWidget *button, gpointer user_data)
954 GtkWidget *dialog = button;
955 while (dialog->parent)
956 dialog = dialog->parent;
957 gtk_widget_destroy (dialog);
960 /* ok button on GtkFileSelection: user_data is the corresponding GtkEntry */
962 file_sel_ok (GtkWidget *button, gpointer user_data)
964 GtkWidget *entry = GTK_WIDGET (user_data);
965 GtkWidget *dialog = button;
967 while (dialog->parent)
968 dialog = dialog->parent;
969 gtk_widget_hide (dialog);
971 path = gtk_file_selection_get_filename (GTK_FILE_SELECTION (dialog));
972 /* apparently one doesn't free `path' */
974 gtk_entry_set_text (GTK_ENTRY (entry), path);
975 gtk_entry_set_position (GTK_ENTRY (entry), strlen (path));
977 gtk_widget_destroy (dialog);
980 /* WM close on GtkFileSelection: user_data unused */
982 file_sel_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
984 file_sel_cancel (widget, user_data);
987 /* "Browse" button: user_data is the corresponding GtkEntry */
989 browse_button_cb (GtkButton *button, gpointer user_data)
991 GtkWidget *entry = GTK_WIDGET (user_data);
992 char *text = gtk_entry_get_text (GTK_ENTRY (entry));
993 GtkFileSelection *selector =
994 GTK_FILE_SELECTION (gtk_file_selection_new ("Select file."));
996 gtk_file_selection_set_filename (selector, text);
997 gtk_signal_connect (GTK_OBJECT (selector->ok_button),
998 "clicked", GTK_SIGNAL_FUNC (file_sel_ok),
1000 gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
1001 "clicked", GTK_SIGNAL_FUNC (file_sel_cancel),
1003 gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
1004 GTK_SIGNAL_FUNC (file_sel_close),
1007 gtk_window_set_modal (GTK_WINDOW (selector), TRUE);
1008 gtk_widget_show (GTK_WIDGET (selector));
1012 /* Converting to and from command-lines
1016 /* Returns a copy of string that has been quoted according to shell rules:
1017 it may have been wrapped in "" and had some characters backslashed; or
1018 it may be unchanged.
1021 shell_quotify (const char *string)
1023 char *string2 = (char *) malloc ((strlen (string) * 2) + 10);
1026 int need_quotes = 0;
1031 for (in = string; *in; in++)
1042 else if (*in <= ' ' ||
1068 return strdup (string);
1071 /* Modify the string in place to remove wrapping double-quotes
1072 and interior backslashes.
1075 de_stringify (char *s)
1078 if (q != '\'' && q != '\"' && q != '`')
1080 memmove (s, s+1, strlen (s)+1);
1081 while (*s && *s != q)
1084 memmove (s, s+1, strlen (s)+1);
1087 if (*s != q) abort();
1092 /* Substitutes a shell-quotified version of `value' into `p->arg' at
1093 the place where the `%' character appeared.
1096 format_switch (parameter *p, const char *value)
1101 if (!fmt || !value) return 0;
1102 v2 = shell_quotify (value);
1103 result = (char *) malloc (strlen (fmt) + strlen (v2) + 10);
1120 /* Maps a `parameter' to a command-line switch.
1121 Returns 0 if it can't, or if the parameter has the default value.
1124 parameter_to_switch (parameter *p)
1130 return strdup (p->arg);
1136 if (!p->widget) return 0;
1138 const char *s = gtk_entry_get_text (GTK_ENTRY (p->widget));
1140 if (!strcmp ((s ? s : ""),
1141 (p->string ? p->string : "")))
1142 v = 0; /* same as default */
1144 v = format_switch (p, s);
1146 /* don't free `s' */
1151 if (!p->widget) return 0;
1153 GtkAdjustment *adj =
1155 ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
1156 : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
1159 float value = (p->invert_p
1160 ? invert_range (adj->lower, adj->upper, adj->value)
1163 if (value == p->value) /* same as default */
1167 sprintf (buf, "%d", (int) (value + 0.5));
1169 sprintf (buf, "%.4f", value);
1171 s1 = strchr (buf, '.');
1174 char *s2 = s1 + strlen(s1) - 1;
1175 while (s2 > s1 && *s2 == '0') /* lose trailing zeroes */
1177 if (s2 >= s1 && *s2 == '.') /* lose trailing decimal */
1180 return format_switch (p, buf);
1183 if (!p->widget) return 0;
1185 GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget);
1186 const char *s = (gtk_toggle_button_get_active (b)
1195 if (!p->widget) return 0;
1197 GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget);
1198 GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
1199 GtkWidget *selected = gtk_menu_get_active (menu);
1200 GList *kids = gtk_container_children (GTK_CONTAINER (menu));
1201 int menu_elt = g_list_index (kids, (gpointer) selected);
1202 GList *ol = g_list_nth (p->options, menu_elt);
1203 parameter *o = (ol ? (parameter *) ol->data : 0);
1206 if (o->type != SELECT_OPTION) abort();
1221 /* Maps a GList of `parameter' objects to a complete command-line string.
1222 All arguments will be properly quoted.
1225 parameters_to_cmd_line (GList *parms)
1227 int L = g_list_length (parms);
1229 char **strs = (char **) calloc (sizeof (*parms), L);
1234 for (i = 0; parms; parms = parms->next, i++)
1236 char *s = parameter_to_switch ((parameter *) parms->data);
1238 LL += (s ? strlen(s) : 0) + 1;
1241 result = (char *) malloc (LL + 10);
1243 for (i = 0; i < L; i++)
1246 strcpy (out, strs[i]);
1247 out += strlen (out);
1252 while (out > result && out[-1] == ' ') /* strip trailing spaces */
1260 /* Returns a GList of the tokens the string, using shell syntax;
1261 Quoted strings are handled as a single token.
1264 tokenize_command_line (const char *cmd)
1267 const char *s = cmd;
1272 for (; isspace(*s); s++); /* skip whitespace */
1275 if (*s == '\'' || *s == '\"' || *s == '`')
1279 while (*s && *s != q) /* skip to matching quote */
1281 if (*s == '\\' && s[1]) /* allowing backslash quoting */
1299 ss = (char *) malloc ((s - start) + 1);
1300 strncpy (ss, start, s-start);
1302 if (*ss == '\'' || *ss == '\"' || *ss == '`')
1304 result = g_list_append (result, ss);
1311 static void parameter_set_switch (parameter *, gpointer value);
1312 static gboolean parse_command_line_into_parameters_1 (const char *filename,
1319 /* Parses the command line, and flushes those options down into
1320 the `parameter' structs in the list.
1323 parse_command_line_into_parameters (const char *filename,
1324 const char *cmd, GList *parms)
1326 GList *tokens = tokenize_command_line (cmd);
1328 for (rest = tokens; rest; rest = rest->next)
1330 char *option = rest->data;
1333 if (option[0] != '-')
1336 fprintf (stderr, "%s: WARNING: %s: not a switch: \"%s\"\n",
1337 blurb(), filename, option);
1343 if (rest->next) /* pop off the arg to this option */
1345 char *s = (char *) rest->next->data;
1346 /* the next token is the next switch iff it matches "-[a-z]".
1347 (To avoid losing on "-x -3.1".)
1349 if (s && (s[0] != '-' || !isalpha(s[1])))
1352 rest->next->data = 0;
1357 parse_command_line_into_parameters_1 (filename, parms,
1359 if (value) free (value);
1363 g_list_free (tokens);
1368 compare_opts (const char *option, const char *value,
1369 const char *template)
1371 int ol = strlen (option);
1374 if (strncmp (option, template, ol))
1377 if (template[ol] != (value ? ' ' : 0))
1380 /* At this point, we have a match against "option".
1381 If template contains a %, we're done.
1382 Else, compare against "value" too.
1384 c = strchr (template, '%');
1389 return (template[ol] == 0);
1390 if (strcmp (template + ol + 1, value))
1398 parse_command_line_into_parameters_1 (const char *filename,
1405 parameter *match = 0;
1409 for (p = parms; p; p = p->next)
1411 parameter *pp = (parameter *) p->data;
1414 if (pp->type == SELECT)
1416 if (parse_command_line_into_parameters_1 (filename,
1427 if (compare_opts (option, value, pp->arg))
1433 else if (pp->arg_set)
1435 if (compare_opts (option, value, pp->arg_set))
1441 else if (pp->arg_unset)
1443 if (compare_opts (option, value, pp->arg_unset))
1458 if (debug_p && !parent)
1459 fprintf (stderr, "%s: WARNING: %s: no match for %s %s\n",
1460 blurb(), filename, option, (value ? value : ""));
1464 switch (match->type)
1470 if (which != -1) abort();
1471 parameter_set_switch (match, (gpointer) value);
1474 if (which != 0 && which != 1) abort();
1475 parameter_set_switch (match, (gpointer) which);
1478 if (which != 1) abort();
1479 parameter_set_switch (parent, (gpointer) index);
1488 /* Set the parameter's value.
1489 For STRING, FILENAME, SLIDER, and SPINBUTTON, `value' is a char*.
1490 For BOOLEAN and SELECT, `value' is an int.
1493 parameter_set_switch (parameter *p, gpointer value)
1495 if (p->type == SELECT_OPTION) abort();
1496 if (!p->widget) return;
1502 gtk_entry_set_text (GTK_ENTRY (p->widget), (char *) value);
1508 GtkAdjustment *adj =
1510 ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
1511 : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
1515 if (1 == sscanf ((char *) value, "%f %c", &f, &c))
1518 f = invert_range (adj->lower, adj->upper, f);
1519 gtk_adjustment_set_value (adj, f);
1525 GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget);
1526 gtk_toggle_button_set_active (b, (int) value);
1531 gtk_option_menu_set_history (GTK_OPTION_MENU (p->widget),
1542 restore_defaults (const char *progname, GList *parms)
1544 for (; parms; parms = parms->next)
1546 parameter *p = (parameter *) parms->data;
1547 if (!p->widget) continue;
1553 gtk_entry_set_text (GTK_ENTRY (p->widget),
1554 (p->string ? p->string : ""));
1560 GtkAdjustment *adj =
1562 ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
1563 : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
1564 float value = (p->invert_p
1565 ? invert_range (p->low, p->high, p->value)
1567 gtk_adjustment_set_value (adj, value);
1572 /* A toggle button should be on by default if it inserts
1573 nothing into the command line when on. E.g., it should
1574 be on if `arg_set' is null.
1576 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (p->widget),
1577 (!p->arg_set || !*p->arg_set));
1582 GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget);
1587 for (opts = p->options, index = 0; opts;
1588 opts = opts->next, index++)
1590 parameter *s = (parameter *) opts->data;
1591 /* The default menu item is the first one with
1592 no `arg_set' field. */
1600 gtk_option_menu_set_history (GTK_OPTION_MENU (opt), selected);
1611 /* Documentation strings
1615 get_description (GList *parms)
1618 for (; parms; parms = parms->next)
1620 parameter *p = (parameter *) parms->data;
1621 if (p->type == DESCRIPTION)
1628 if (!doc || !doc->string)
1632 char *d = strdup (doc->string);
1634 for (s = d; *s; s++)
1637 if (s[1] == '\n') /* blank line: leave it */
1639 else if (s[1] == ' ' || s[1] == '\t')
1640 s++; /* next line is indented: leave newline */
1642 s[0] = ' '; /* delete newline to un-fold this line */
1645 /* strip off leading whitespace on first line only */
1646 for (s = d; *s && (*s == ' ' || *s == '\t'); s++)
1648 while (*s == '\n') /* strip leading newlines */
1651 memmove (d, s, strlen(s)+1);
1653 /* strip off trailing whitespace and newlines */
1656 while (L && isspace(d[L-1]))
1665 /* External interface.
1669 load_configurator_1 (const char *program, const char *arguments,
1672 const char *dir = HACK_CONFIGURATION_PATH;
1673 int L = strlen (dir);
1674 char *file = (char *) malloc (L + strlen (program) + 10);
1677 conf_data *data = (conf_data *) calloc (1, sizeof(*data));
1680 if (file[L-1] != '/')
1682 strcpy (file+L, program);
1684 for (s = file+L; *s; s++)
1685 if (*s == '/' || *s == ' ')
1687 else if (isupper (*s))
1690 strcat (file+L, ".xml");
1692 f = fopen (file, "r");
1695 int res, size = 1024;
1697 xmlParserCtxtPtr ctxt;
1703 fprintf (stderr, "%s: reading %s...\n", blurb(), file);
1705 res = fread (chars, 1, 4, f);
1706 if (res <= 0) return 0;
1708 ctxt = xmlCreatePushParserCtxt(NULL, NULL, chars, res, file);
1709 while ((res = fread(chars, 1, size, f)) > 0)
1710 xmlParseChunk (ctxt, chars, res, 0);
1711 xmlParseChunk (ctxt, chars, 0, 1);
1713 xmlFreeParserCtxt (ctxt);
1716 /* Parsed the XML file. Now make some widgets. */
1718 table = gtk_table_new (1, 3, FALSE);
1719 gtk_table_set_row_spacings (GTK_TABLE (table), 4);
1720 gtk_table_set_col_spacings (GTK_TABLE (table), 4);
1721 gtk_container_set_border_width (GTK_CONTAINER (table), 8);
1722 gtk_widget_show (table);
1724 parms = make_parameters (file, doc->root, table);
1728 restore_defaults (program, parms);
1729 if (arguments && *arguments)
1730 parse_command_line_into_parameters (program, arguments, parms);
1732 data->widget = table;
1733 data->parameters = parms;
1734 data->description = get_description (parms);
1741 fprintf (stderr, "%s: %s does not exist.\n", blurb(), file);
1743 p = calloc (1, sizeof(*p));
1745 p->arg = strdup (arguments);
1747 data->parameters = g_list_append (0, (gpointer) p);
1750 data->progname = strdup (program);
1755 split_command_line (const char *full_command_line,
1756 char **prog_ret, char **args_ret)
1758 char *line = strdup (full_command_line);
1771 while (isspace (*s)) s++;
1774 else if (*s == '=') /* if the leading word contains an "=", skip it. */
1776 while (*s && !isspace (*s)) s++;
1777 while (isspace (*s)) s++;
1784 *prog_ret = strdup (prog);
1785 *args_ret = strdup (args);
1791 load_configurator (const char *full_command_line, gboolean verbose_p)
1796 split_command_line (full_command_line, &prog, &args);
1797 cd = load_configurator_1 (prog, args, verbose_p);
1806 get_configurator_command_line (conf_data *data)
1808 char *args = parameters_to_cmd_line (data->parameters);
1809 char *result = (char *) malloc (strlen (data->progname) +
1811 strcpy (result, data->progname);
1812 strcat (result, " ");
1813 strcat (result, args);
1820 set_configurator_command_line (conf_data *data, const char *full_command_line)
1824 split_command_line (full_command_line, &prog, &args);
1825 if (data->progname) free (data->progname);
1826 data->progname = prog;
1827 restore_defaults (prog, data->parameters);
1828 parse_command_line_into_parameters (prog, args, data->parameters);
1833 free_conf_data (conf_data *data)
1835 if (data->parameters)
1838 for (rest = data->parameters; rest; rest = rest->next)
1840 free_parameter ((parameter *) rest->data);
1843 g_list_free (data->parameters);
1844 data->parameters = 0;
1848 gtk_widget_destroy (data->widget);
1851 free (data->progname);;
1853 memset (data, ~0, sizeof(*data));
1858 #endif /* HAVE_GTK && HAVE_XML -- whole file */