1 /* demo-Gtk-conf.c --- implements the dynamic configuration dialogs.
2 * xscreensaver, Copyright (c) 2001-2014 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 */
19 #include <xscreensaver-intl.h>
32 * Both of these workarounds can be removed when support for ancient
33 * libxml versions is dropped. versions 1.8.11 and 2.3.4 provide the
38 * Older libxml polluted the global headerspace, while libxml2 fixed
39 * this. To support both old and recent libxmls, we have this
42 #ifdef HAVE_OLD_XML_HEADERS
44 #else /* ! HAVE_OLD_XML_HEADERS */
45 # include <libxml/parser.h>
46 #endif /* HAVE_OLD_XML_HEADERS */
49 * handle non-native spelling mistakes in earlier versions and provide
50 * the source-compat fix for this that may not be in older versions.
52 #ifndef xmlChildrenNode
53 # if LIBXML_VERSION >= 20000
54 # define xmlChildrenNode children
55 # define xmlRootNode children
57 # define xmlChildrenNode childs
58 # define xmlRootNode root
59 # endif /* LIBXML_VERSION */
60 #endif /* xmlChildrenNode */
64 #include "demo-Gtk-conf.h"
66 /* Deal with deprecation of direct access to struct fields on the way to GTK3
67 See http://live.gnome.org/GnomeGoals/UseGseal
69 #if GTK_CHECK_VERSION(2,14,0)
70 # define GET_PARENT(w) gtk_widget_get_parent (w)
71 # define GET_ADJ_VALUE(a) gtk_adjustment_get_value (a)
72 # define GET_ADJ_UPPER(a) gtk_adjustment_get_upper (a)
73 # define GET_ADJ_LOWER(a) gtk_adjustment_get_lower (a)
75 # define GET_PARENT(w) ((w)->parent)
76 # define GET_ADJ_VALUE(a) ((a)->value)
77 # define GET_ADJ_UPPER(a) ((a)->upper)
78 # define GET_ADJ_LOWER(a) ((a)->lower)
82 extern const char *blurb (void);
85 const char *hack_configuration_path = HACK_CONFIGURATION_PATH;
87 static gboolean debug_p = FALSE;
90 #define MIN_SLIDER_WIDTH 150
91 #define MIN_SPINBUTTON_WIDTH 48
92 #define MIN_LABEL_WIDTH 70
114 xmlChar *id; /* widget name */
115 xmlChar *label; /* heading label, or null */
117 /* command, fake, description, fakepreview, string, file
119 xmlChar *string; /* file name, description, whatever. */
121 /* slider, spinbutton
123 xmlChar *low_label; /* label for the left side */
124 xmlChar *high_label; /* label for the right side */
125 float low; /* minimum value */
126 float high; /* maximum value */
127 float value; /* default value */
128 gboolean integer_p; /* whether the range is integral, or real */
129 xmlChar *arg; /* command-line option to set (substitute "%") */
130 gboolean invert_p; /* whether to flip the value and pretend the
131 range goes from hi-low instead of low-hi. */
133 /* boolean, select-option
135 xmlChar *arg_set; /* command-line option to set for "yes", or null */
136 xmlChar *arg_unset; /* command-line option to set for "no", or null */
149 static parameter *make_select_option (const char *file, xmlNodePtr);
150 static void make_parameter_widget (const char *filename,
151 parameter *, GtkWidget *);
152 static void browse_button_cb (GtkButton *button, gpointer user_data);
155 /* Frees the parameter object and all strings and sub-parameters.
156 Does not destroy the widget, if any.
159 free_parameter (parameter *p)
162 if (p->id) free (p->id);
163 if (p->label) free (p->label);
164 if (p->string) free (p->string);
165 if (p->low_label) free (p->low_label);
166 if (p->high_label) free (p->high_label);
167 if (p->arg) free (p->arg);
168 if (p->arg_set) free (p->arg_set);
169 if (p->arg_unset) free (p->arg_unset);
171 for (rest = p->options; rest; rest = rest->next)
173 free_parameter ((parameter *) rest->data);
175 memset (p, ~0, sizeof(*p));
180 /* Debugging: dumps out a `parameter' structure.
184 describe_parameter (FILE *out, parameter *p)
189 case COMMAND: fprintf (out, "command"); break;
190 case FAKE: fprintf (out, "fake"); break;
191 case DESCRIPTION: fprintf (out, "_description"); break;
192 case FAKEPREVIEW: fprintf (out, "fakepreview"); break;
193 case STRING: fprintf (out, "string"); break;
194 case FILENAME: fprintf (out, "filename"); break;
195 case SLIDER: fprintf (out, "number type=\"slider\""); break;
196 case SPINBUTTON: fprintf (out, "number type=\"spinbutton\""); break;
197 case BOOLEAN: fprintf (out, "boolean"); break;
198 case SELECT: fprintf (out, "select"); break;
199 default: abort(); break;
201 if (p->id) fprintf (out, " id=\"%s\"", p->id);
202 if (p->label) fprintf (out, " _label=\"%s\"", p->label);
203 if (p->string && p->type != DESCRIPTION)
204 fprintf (out, " string=\"%s\"", p->string);
205 if (p->low_label) fprintf (out, " _low-label=\"%s\"", p->low_label);
206 if (p->high_label) fprintf (out, " _high-label=\"%s\"", p->high_label);
207 if (p->low) fprintf (out, " low=\"%.2f\"", p->low);
208 if (p->high) fprintf (out, " high=\"%.2f\"", p->high);
209 if (p->value) fprintf (out, " default=\"%.2f\"", p->value);
210 if (p->arg) fprintf (out, " arg=\"%s\"", p->arg);
211 if (p->invert_p) fprintf (out, " convert=\"invert\"");
212 if (p->arg_set) fprintf (out, " arg-set=\"%s\"", p->arg_set);
213 if (p->arg_unset) fprintf (out, " arg-unset=\"%s\"", p->arg_unset);
214 fprintf (out, ">\n");
216 if (p->type == SELECT)
219 for (opt = p->options; opt; opt = opt->next)
221 parameter *o = (parameter *) opt->data;
222 if (o->type != SELECT_OPTION) abort();
223 fprintf (out, " <option");
224 if (o->id) fprintf (out, " id=\"%s\"", o->id);
225 if (o->label) fprintf (out, " _label=\"%s\"", o->label);
226 if (o->arg_set) fprintf (out, " arg-set=\"%s\"", o->arg_set);
227 if (o->arg_unset) fprintf (out, " arg-unset=\"%s\"", o->arg_unset);
228 fprintf (out, ">\n");
230 fprintf (out, "</select>\n");
232 else if (p->type == DESCRIPTION)
235 fprintf (out, " %s\n", p->string);
236 fprintf (out, "</_description>\n");
242 /* Like xmlGetProp() but parses a float out of the string.
243 If the number was expressed as a float and not an integer
244 (that is, the string contained a decimal point) then
245 `floatp' is set to TRUE. Otherwise, it is unchanged.
248 xml_get_float (xmlNodePtr node, const xmlChar *name, gboolean *floatpP)
250 const char *s = (char *) xmlGetProp (node, name);
253 if (!s || 1 != sscanf (s, "%f %c", &f, &c))
257 if (strchr (s, '.')) *floatpP = TRUE;
263 static void sanity_check_parameter (const char *filename,
264 const xmlChar *node_name,
266 static void sanity_check_text_node (const char *filename,
267 const xmlNodePtr node);
268 static void sanity_check_menu_options (const char *filename,
269 const xmlChar *node_name,
272 /* Allocates and returns a new `parameter' object based on the
273 properties in the given XML node. Returns 0 if there's nothing
274 to create (comment, or unknown tag.)
277 make_parameter (const char *filename, xmlNodePtr node)
280 const char *name = (char *) node->name;
282 gboolean floatp = FALSE;
284 if (node->type == XML_COMMENT_NODE)
287 p = calloc (1, sizeof(*p));
290 else if (!strcmp (name, "command")) p->type = COMMAND;
291 else if (!strcmp (name, "fullcommand")) p->type = COMMAND;
292 else if (!strcmp (name, "_description")) p->type = DESCRIPTION;
293 else if (!strcmp (name, "fakepreview")) p->type = FAKEPREVIEW;
294 else if (!strcmp (name, "fake")) p->type = FAKE;
295 else if (!strcmp (name, "boolean")) p->type = BOOLEAN;
296 else if (!strcmp (name, "string")) p->type = STRING;
297 else if (!strcmp (name, "file")) p->type = FILENAME;
298 else if (!strcmp (name, "number")) p->type = SPINBUTTON;
299 else if (!strcmp (name, "select")) p->type = SELECT;
301 else if (!strcmp (name, "xscreensaver-text") || /* ignored in X11; */
302 !strcmp (name, "xscreensaver-image") || /* used in Cocoa. */
303 !strcmp (name, "xscreensaver-updater") ||
304 !strcmp (name, "video"))
309 else if (node->type == XML_TEXT_NODE)
311 sanity_check_text_node (filename, node);
318 fprintf (stderr, "%s: WARNING: %s: unknown tag: \"%s\"\n",
319 blurb(), filename, name);
324 if (p->type == SPINBUTTON)
326 const char *type = (char *) xmlGetProp (node, (xmlChar *) "type");
327 if (!type || !strcmp (type, "spinbutton")) p->type = SPINBUTTON;
328 else if (!strcmp (type, "slider")) p->type = SLIDER;
332 fprintf (stderr, "%s: WARNING: %s: unknown %s type: \"%s\"\n",
333 blurb(), filename, name, type);
338 else if (p->type == DESCRIPTION)
340 if (node->xmlChildrenNode &&
341 node->xmlChildrenNode->type == XML_TEXT_NODE &&
342 !node->xmlChildrenNode->next)
343 p->string = (xmlChar *)
344 strdup ((char *) node->xmlChildrenNode->content);
347 p->id = xmlGetProp (node, (xmlChar *) "id");
348 p->label = xmlGetProp (node, (xmlChar *) "_label");
349 p->low_label = xmlGetProp (node, (xmlChar *) "_low-label");
350 p->high_label = xmlGetProp (node, (xmlChar *) "_high-label");
351 p->low = xml_get_float (node, (xmlChar *) "low", &floatp);
352 p->high = xml_get_float (node, (xmlChar *) "high", &floatp);
353 p->value = xml_get_float (node, (xmlChar *) "default", &floatp);
354 p->integer_p = !floatp;
355 convert = (char *) xmlGetProp (node, (xmlChar *) "convert");
356 p->invert_p = (convert && !strcmp (convert, "invert"));
357 p->arg = xmlGetProp (node, (xmlChar *) "arg");
358 p->arg_set = xmlGetProp (node, (xmlChar *) "arg-set");
359 p->arg_unset = xmlGetProp (node, (xmlChar *) "arg-unset");
361 /* Check for missing decimal point */
364 (p->high != p->low) &&
365 (p->high - p->low) <= 1)
367 "%s: WARNING: %s: %s: range [%.1f, %.1f] shouldn't be integral!\n",
368 blurb(), filename, p->id,
371 if (p->type == SELECT)
374 for (kids = node->xmlChildrenNode; kids; kids = kids->next)
376 parameter *s = make_select_option (filename, kids);
378 p->options = g_list_append (p->options, s);
382 sanity_check_parameter (filename, (const xmlChar *) name, p);
388 /* Allocates and returns a new SELECT_OPTION `parameter' object based
389 on the properties in the given XML node. Returns 0 if there's nothing
390 to create (comment, or unknown tag.)
393 make_select_option (const char *filename, xmlNodePtr node)
395 if (node->type == XML_COMMENT_NODE)
397 else if (node->type == XML_TEXT_NODE)
399 sanity_check_text_node (filename, node);
402 else if (node->type != XML_ELEMENT_NODE)
406 "%s: WARNING: %s: %s: unexpected child tag type %d\n",
407 blurb(), filename, node->name, (int)node->type);
410 else if (strcmp ((char *) node->name, "option"))
414 "%s: WARNING: %s: %s: child not an option tag: \"%s\"\n",
415 blurb(), filename, node->name, node->name);
420 parameter *s = calloc (1, sizeof(*s));
422 s->type = SELECT_OPTION;
423 s->id = xmlGetProp (node, (xmlChar *) "id");
424 s->label = xmlGetProp (node, (xmlChar *) "_label");
425 s->arg_set = xmlGetProp (node, (xmlChar *) "arg-set");
426 s->arg_unset = xmlGetProp (node, (xmlChar *) "arg-unset");
428 sanity_check_parameter (filename, node->name, s);
434 /* Rudimentary check to make sure someone hasn't typed "arg-set="
435 when they should have typed "arg=", etc.
438 sanity_check_parameter (const char *filename, const xmlChar *node_name,
456 memset (&allowed, 0, sizeof (allowed));
457 memset (&require, 0, sizeof (require));
468 allowed.string = TRUE;
475 allowed.label = TRUE;
476 require.label = TRUE;
483 allowed.label = TRUE;
490 allowed.label = TRUE;
491 allowed.low_label = TRUE;
492 allowed.high_label = TRUE;
496 /* require.low = TRUE; -- may be 0 */
498 /* require.high = TRUE; -- may be 0 */
499 allowed.value = TRUE;
500 /* require.value = TRUE; -- may be 0 */
501 allowed.invert_p = TRUE;
506 allowed.label = TRUE;
510 /* require.low = TRUE; -- may be 0 */
512 /* require.high = TRUE; -- may be 0 */
513 allowed.value = TRUE;
514 /* require.value = TRUE; -- may be 0 */
515 allowed.invert_p = TRUE;
520 allowed.label = TRUE;
521 allowed.arg_set = TRUE;
522 allowed.arg_unset = TRUE;
530 allowed.label = TRUE;
531 require.label = TRUE;
532 allowed.arg_set = TRUE;
540 fprintf (stderr, "%s: %s: " STR " in <%s%s id=\"%s\">\n", \
541 blurb(), filename, node_name, \
542 (!strcmp((char *) node_name, "number") \
543 ? (p->type == SPINBUTTON ? " type=spinbutton" : " type=slider")\
545 (p->id ? (char *) p->id : ""))
546 # define CHECK(SLOT,NAME) \
547 if (p->SLOT && !allowed.SLOT) \
548 WARN ("\"" NAME "\" is not a valid option"); \
549 if (!p->SLOT && require.SLOT) \
550 WARN ("\"" NAME "\" is required")
553 CHECK (label, "_label");
554 CHECK (string, "(body text)");
555 CHECK (low_label, "_low-label");
556 CHECK (high_label, "_high-label");
558 CHECK (high, "high");
559 CHECK (value, "default");
561 CHECK (invert_p, "convert");
562 CHECK (arg_set, "arg-set");
563 CHECK (arg_unset, "arg-unset");
567 if (p->type == SELECT)
568 sanity_check_menu_options (filename, node_name, p);
573 sanity_check_menu_options (const char *filename, const xmlChar *node_name,
581 /* fprintf (stderr, "\n## %s\n", p->id);*/
582 for (opts = p->options; opts; opts = opts->next)
584 parameter *s = (parameter *) opts->data;
585 if (!s->arg_set) nulls++;
590 char *a = strdup ((char *) s->arg_set);
591 char *spc = strchr (a, ' ');
595 if (strcmp (a, prefix))
597 "%s: %s: both \"%s\" and \"%s\" used in <select id=\"%s\">\n",
598 blurb(), filename, prefix, a, p->id);
604 /* fprintf (stderr, "\n %s\n", s->arg_set);*/
607 if (prefix) free (prefix);
611 "%s: %s: more than one menu with no arg-set in <select id=\"%s\">\n",
612 blurb(), filename, p->id);
616 /* "text" nodes show up for all the non-tag text in the file, including
617 all the newlines between tags. Warn if there is text there that
621 sanity_check_text_node (const char *filename, const xmlNodePtr node)
623 const char *body = (const char *) node->content;
624 if (node->type != XML_TEXT_NODE) abort();
625 while (isspace (*body)) body++;
627 fprintf (stderr, "%s: WARNING: %s: random text present: \"%s\"\n",
628 blurb(), filename, body);
632 /* Returns a list of strings, every switch mentioned in the parameters.
633 The strings must be freed.
636 get_all_switches (const char *filename, GList *parms)
640 for (p = parms; p; p = p->next)
642 parameter *pp = (parameter *) p->data;
644 if (pp->type == SELECT)
646 GList *list2 = get_all_switches (filename, pp->options);
647 switches = g_list_concat (switches, list2);
649 if (pp->arg && *pp->arg)
650 switches = g_list_append (switches, strdup ((char *) pp->arg));
651 if (pp->arg_set && *pp->arg_set)
652 switches = g_list_append (switches, strdup ((char *) pp->arg_set));
653 if (pp->arg_unset && *pp->arg_unset)
654 switches = g_list_append (switches, strdup ((char *) pp->arg_unset));
660 /* Ensures that no switch is mentioned more than once.
663 sanity_check_parameters (const char *filename, GList *parms)
665 GList *list = get_all_switches (filename, parms);
667 for (p = list; p; p = p->next)
669 char *sw = (char *) p->data;
672 if (*sw != '-' && *sw != '+')
673 fprintf (stderr, "%s: %s: switch does not begin with hyphen \"%s\"\n",
674 blurb(), filename, sw);
676 for (p2 = p->next; p2; p2 = p2->next)
678 const char *sw2 = (const char *) p2->data;
679 if (!strcmp (sw, sw2))
680 fprintf (stderr, "%s: %s: duplicate switch \"%s\"\n",
681 blurb(), filename, sw);
690 /* Helper for make_parameters()
693 make_parameters_1 (const char *filename, xmlNodePtr node, GtkWidget *parent)
697 for (; node; node = node->next)
699 const char *name = (char *) node->name;
700 if (!strcmp (name, "hgroup") ||
701 !strcmp (name, "vgroup"))
703 GtkWidget *box = (*name == 'h'
704 ? gtk_hbox_new (FALSE, 0)
705 : gtk_vbox_new (FALSE, 0));
707 gtk_widget_show (box);
708 gtk_box_pack_start (GTK_BOX (parent), box, FALSE, FALSE, 0);
710 list2 = make_parameters_1 (filename, node->xmlChildrenNode, box);
712 list = g_list_concat (list, list2);
716 parameter *p = make_parameter (filename, node);
719 list = g_list_append (list, p);
720 make_parameter_widget (filename, p, parent);
728 /* Calls make_parameter() and make_parameter_widget() on each relevant
729 tag in the XML tree. Also handles the "hgroup" and "vgroup" flags.
730 Returns a GList of `parameter' objects.
733 make_parameters (const char *filename, xmlNodePtr node, GtkWidget *parent)
735 for (; node; node = node->next)
737 if (node->type == XML_ELEMENT_NODE &&
738 !strcmp ((char *) node->name, "screensaver"))
739 return make_parameters_1 (filename, node->xmlChildrenNode, parent);
746 invert_range (gfloat low, gfloat high, gfloat value)
748 gfloat range = high-low;
749 gfloat off = value-low;
750 return (low + (range - off));
754 static GtkAdjustment *
755 make_adjustment (const char *filename, parameter *p)
757 float range = (p->high - p->low);
758 float value = (p->invert_p
759 ? invert_range (p->low, p->high, p->value)
761 gfloat si = (p->high - p->low) / 100;
762 gfloat pi = (p->high - p->low) / 10;
763 gfloat page_size = ((p->type == SLIDER) ? 1 : 0);
765 if (p->value < p->low || p->value > p->high)
767 if (debug_p && p->integer_p)
768 fprintf (stderr, "%s: WARNING: %s: %d is not in range [%d, %d]\n",
770 (int) p->value, (int) p->low, (int) p->high);
773 "%s: WARNING: %s: %.2f is not in range [%.2f, %.2f]\n",
774 blurb(), filename, p->value, p->low, p->high);
775 value = (value < p->low ? p->low : p->high);
778 else if (debug_p && p->value < 1000 && p->high >= 10000)
782 "%s: WARNING: %s: %d is suspicious for range [%d, %d]\n",
784 (int) p->value, (int) p->low, (int) p->high);
787 "%s: WARNING: %s: %.2f is suspicious for range [%.2f, %.2f]\n",
788 blurb(), filename, p->value, p->low, p->high);
792 si = (int) (si + 0.5);
793 pi = (int) (pi + 0.5);
797 if (range <= 500) si = 1;
799 return GTK_ADJUSTMENT (gtk_adjustment_new (value,
808 set_widget_min_width (GtkWidget *w, int width)
811 gtk_widget_size_request (GTK_WIDGET (w), &req);
812 if (req.width < width)
813 gtk_widget_set_size_request (GTK_WIDGET (w), width, -1);
817 /* If we're inside a vbox, we need to put an hbox in it, or labels appear
818 on top instead of to the left, and things stretch to the full width of
822 insert_fake_hbox (GtkWidget *parent)
824 if (GTK_IS_VBOX (parent))
826 GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
827 gtk_box_pack_start (GTK_BOX (parent), hbox, FALSE, FALSE, 4);
828 gtk_widget_show (hbox);
836 link_atk_label_to_widget(GtkWidget *label, GtkWidget *widget)
838 AtkObject *atk_label = gtk_widget_get_accessible (label);
839 AtkObject *atk_widget = gtk_widget_get_accessible (widget);
841 atk_object_add_relationship (atk_label, ATK_RELATION_LABEL_FOR,
843 atk_object_add_relationship (atk_widget, ATK_RELATION_LABELLED_BY,
847 /* Given a `parameter' struct, allocates an appropriate GtkWidget for it,
848 and stores it in `p->widget'.
849 `parent' must be a GtkBox.
852 make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent)
854 const char *label = (char *) p->label;
855 if (p->widget) return;
861 GtkWidget *entry = gtk_entry_new ();
862 parent = insert_fake_hbox (parent);
865 GtkWidget *w = gtk_label_new (_(label));
866 link_atk_label_to_widget (w, entry);
867 gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
868 gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
869 set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH);
871 gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
876 gtk_entry_set_text (GTK_ENTRY (p->widget), (char *) p->string);
877 gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
882 GtkWidget *L = gtk_label_new (label ? _(label) : "");
883 GtkWidget *entry = gtk_entry_new ();
884 GtkWidget *button = gtk_button_new_with_label (_("Browse..."));
885 link_atk_label_to_widget (L, entry);
886 gtk_widget_show (entry);
887 gtk_widget_show (button);
890 gtk_signal_connect (GTK_OBJECT (button),
891 "clicked", GTK_SIGNAL_FUNC (browse_button_cb),
894 gtk_label_set_justify (GTK_LABEL (L), GTK_JUSTIFY_RIGHT);
895 gtk_misc_set_alignment (GTK_MISC (L), 1.0, 0.5);
896 set_widget_min_width (GTK_WIDGET (L), MIN_LABEL_WIDTH);
900 gtk_entry_set_text (GTK_ENTRY (entry), (char *) p->string);
902 parent = insert_fake_hbox (parent);
903 gtk_box_pack_start (GTK_BOX (parent), L, FALSE, FALSE, 4);
904 gtk_box_pack_start (GTK_BOX (parent), entry, TRUE, TRUE, 4);
905 gtk_box_pack_start (GTK_BOX (parent), button, FALSE, FALSE, 4);
910 GtkAdjustment *adj = make_adjustment (filename, p);
911 GtkWidget *scale = gtk_hscale_new (adj);
912 GtkWidget *labelw = 0;
916 labelw = gtk_label_new (_(label));
917 link_atk_label_to_widget (labelw, scale);
918 gtk_label_set_justify (GTK_LABEL (labelw), GTK_JUSTIFY_LEFT);
919 gtk_misc_set_alignment (GTK_MISC (labelw), 0.0, 0.5);
920 set_widget_min_width (GTK_WIDGET (labelw), MIN_LABEL_WIDTH);
921 gtk_widget_show (labelw);
922 gtk_box_pack_start (GTK_BOX (parent), labelw, FALSE, FALSE, 2);
925 /* Do this after 'labelw' so that it appears above, not to left. */
926 parent = insert_fake_hbox (parent);
930 GtkWidget *w = gtk_label_new (_((char *) p->low_label));
931 link_atk_label_to_widget (w, scale);
932 gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
933 gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
934 set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH);
936 gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
939 gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_BOTTOM);
940 gtk_scale_set_draw_value (GTK_SCALE (scale), debug_p);
941 gtk_scale_set_digits (GTK_SCALE (scale), (p->integer_p ? 0 : 2));
942 set_widget_min_width (GTK_WIDGET (scale), MIN_SLIDER_WIDTH);
944 gtk_box_pack_start (GTK_BOX (parent), scale, FALSE, FALSE, 4);
946 gtk_widget_show (scale);
950 GtkWidget *w = gtk_label_new (_((char *) p->high_label));
951 link_atk_label_to_widget (w, scale);
952 gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT);
953 gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
954 set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH);
956 gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
964 GtkAdjustment *adj = make_adjustment (filename, p);
965 GtkWidget *spin = gtk_spin_button_new (adj, 15, 0);
966 gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE);
967 gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (spin), TRUE);
968 gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), GET_ADJ_VALUE(adj));
969 set_widget_min_width (GTK_WIDGET (spin), MIN_SPINBUTTON_WIDTH);
973 GtkWidget *w = gtk_label_new (_(label));
974 link_atk_label_to_widget (w, spin);
975 gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
976 gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
977 set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH);
979 parent = insert_fake_hbox (parent);
980 gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
983 gtk_widget_show (spin);
984 gtk_box_pack_start (GTK_BOX (parent), spin, FALSE, FALSE, 4);
991 p->widget = gtk_check_button_new_with_label (_(label));
992 /* Let these stretch -- doesn't hurt.
993 parent = insert_fake_hbox (parent);
995 gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
1000 GtkWidget *opt = gtk_option_menu_new ();
1001 GtkWidget *menu = gtk_menu_new ();
1004 for (opts = p->options; opts; opts = opts->next)
1006 parameter *s = (parameter *) opts->data;
1007 GtkWidget *i = gtk_menu_item_new_with_label (_((char *) s->label));
1008 gtk_widget_show (i);
1009 gtk_menu_append (GTK_MENU (menu), i);
1012 gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu);
1014 parent = insert_fake_hbox (parent);
1015 gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
1030 gtk_widget_set_name (p->widget, (char *) p->id);
1031 gtk_widget_show (p->widget);
1037 Absurdly, there is no GTK file entry widget, only a GNOME one,
1038 so in order to avoid depending on GNOME in this code, we have
1042 /* cancel button on GtkFileSelection: user_data unused */
1044 file_sel_cancel (GtkWidget *button, gpointer user_data)
1046 GtkWidget *dialog = button;
1047 while (GET_PARENT (dialog))
1048 dialog = GET_PARENT (dialog);
1049 gtk_widget_destroy (dialog);
1052 /* ok button on GtkFileSelection: user_data is the corresponding GtkEntry */
1054 file_sel_ok (GtkWidget *button, gpointer user_data)
1056 GtkWidget *entry = GTK_WIDGET (user_data);
1057 GtkWidget *dialog = button;
1060 while (GET_PARENT (dialog))
1061 dialog = GET_PARENT (dialog);
1062 gtk_widget_hide (dialog);
1064 path = gtk_file_selection_get_filename (GTK_FILE_SELECTION (dialog));
1065 /* apparently one doesn't free `path' */
1067 gtk_entry_set_text (GTK_ENTRY (entry), path);
1068 gtk_entry_set_position (GTK_ENTRY (entry), strlen (path));
1070 gtk_widget_destroy (dialog);
1073 /* WM close on GtkFileSelection: user_data unused */
1075 file_sel_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
1077 file_sel_cancel (widget, user_data);
1080 /* "Browse" button: user_data is the corresponding GtkEntry */
1082 browse_button_cb (GtkButton *button, gpointer user_data)
1084 GtkWidget *entry = GTK_WIDGET (user_data);
1085 const char *text = gtk_entry_get_text (GTK_ENTRY (entry));
1086 GtkFileSelection *selector =
1087 GTK_FILE_SELECTION (gtk_file_selection_new (_("Select file.")));
1089 gtk_file_selection_set_filename (selector, text);
1090 gtk_signal_connect (GTK_OBJECT (selector->ok_button),
1091 "clicked", GTK_SIGNAL_FUNC (file_sel_ok),
1093 gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
1094 "clicked", GTK_SIGNAL_FUNC (file_sel_cancel),
1096 gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
1097 GTK_SIGNAL_FUNC (file_sel_close),
1100 gtk_window_set_modal (GTK_WINDOW (selector), TRUE);
1101 gtk_widget_show (GTK_WIDGET (selector));
1105 /* Converting to and from command-lines
1109 /* Returns a copy of string that has been quoted according to shell rules:
1110 it may have been wrapped in "" and had some characters backslashed; or
1111 it may be unchanged.
1114 shell_quotify (const char *string)
1116 char *string2 = (char *) malloc ((strlen (string) * 2) + 10);
1119 int need_quotes = 0;
1124 for (in = string; *in; in++)
1135 else if (*in <= ' ' ||
1161 return strdup (string);
1164 /* Modify the string in place to remove wrapping double-quotes
1165 and interior backslashes.
1168 de_stringify (char *s)
1171 if (q != '\'' && q != '\"' && q != '`')
1173 memmove (s, s+1, strlen (s));
1174 while (*s && *s != q)
1177 memmove (s, s+1, strlen (s)+1);
1180 if (*s != q) abort();
1185 /* Substitutes a shell-quotified version of `value' into `p->arg' at
1186 the place where the `%' character appeared.
1189 format_switch (parameter *p, const char *value)
1191 char *fmt = (char *) p->arg;
1194 if (!fmt || !value) return 0;
1195 v2 = shell_quotify (value);
1196 result = (char *) malloc (strlen (fmt) + strlen (v2) + 10);
1213 /* Maps a `parameter' to a command-line switch.
1214 Returns 0 if it can't, or if the parameter has the default value.
1217 parameter_to_switch (parameter *p)
1223 return strdup ((char *) p->arg);
1229 if (!p->widget) return 0;
1231 const char *s = gtk_entry_get_text (GTK_ENTRY (p->widget));
1233 if (!strcmp ((s ? s : ""),
1234 (p->string ? (char *) p->string : "")))
1235 v = 0; /* same as default */
1237 v = format_switch (p, s);
1239 /* don't free `s' */
1244 if (!p->widget) return 0;
1246 GtkAdjustment *adj =
1248 ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
1249 : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
1252 float value = (p->invert_p
1253 ? invert_range (GET_ADJ_LOWER(adj), GET_ADJ_UPPER(adj),
1254 GET_ADJ_VALUE(adj)) - 1
1255 : GET_ADJ_VALUE(adj));
1257 if (value == p->value) /* same as default */
1261 sprintf (buf, "%d", (int) (value + (value > 0 ? 0.5 : -0.5)));
1263 sprintf (buf, "%.4f", value);
1265 s1 = strchr (buf, '.');
1268 char *s2 = s1 + strlen(s1) - 1;
1269 while (s2 > s1 && *s2 == '0') /* lose trailing zeroes */
1271 if (s2 >= s1 && *s2 == '.') /* lose trailing decimal */
1274 return format_switch (p, buf);
1277 if (!p->widget) return 0;
1279 GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget);
1280 const char *s = (gtk_toggle_button_get_active (b)
1281 ? (char *) p->arg_set
1282 : (char *) p->arg_unset);
1289 if (!p->widget) return 0;
1291 GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget);
1292 GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
1293 GtkWidget *selected = gtk_menu_get_active (menu);
1294 GList *kids = gtk_container_children (GTK_CONTAINER (menu));
1295 int menu_elt = g_list_index (kids, (gpointer) selected);
1296 GList *ol = g_list_nth (p->options, menu_elt);
1297 parameter *o = (ol ? (parameter *) ol->data : 0);
1300 if (o->type != SELECT_OPTION) abort();
1301 s = (char *) o->arg_set;
1315 /* Maps a GList of `parameter' objects to a complete command-line string.
1316 All arguments will be properly quoted.
1319 parameters_to_cmd_line (GList *parms, gboolean default_p)
1321 int L = g_list_length (parms);
1323 char **strs = (char **) calloc (sizeof (*parms), L);
1328 for (i = 0, j = 0; parms; parms = parms->next, i++)
1330 parameter *p = (parameter *) parms->data;
1331 if (!default_p || p->type == COMMAND)
1333 char *s = parameter_to_switch (p);
1335 LL += (s ? strlen(s) : 0) + 1;
1339 result = (char *) malloc (LL + 10);
1341 for (i = 0; i < j; i++)
1344 strcpy (out, strs[i]);
1345 out += strlen (out);
1350 while (out > result && out[-1] == ' ') /* strip trailing spaces */
1358 /* Returns a GList of the tokens the string, using shell syntax;
1359 Quoted strings are handled as a single token.
1362 tokenize_command_line (const char *cmd)
1365 const char *s = cmd;
1370 for (; isspace(*s); s++); /* skip whitespace */
1373 if (*s == '\'' || *s == '\"' || *s == '`')
1377 while (*s && *s != q) /* skip to matching quote */
1379 if (*s == '\\' && s[1]) /* allowing backslash quoting */
1397 ss = (char *) malloc ((s - start) + 1);
1398 strncpy (ss, start, s-start);
1400 if (*ss == '\'' || *ss == '\"' || *ss == '`')
1402 result = g_list_append (result, ss);
1409 static void parameter_set_switch (parameter *, gpointer value);
1410 static gboolean parse_command_line_into_parameters_1 (const char *filename,
1417 /* Parses the command line, and flushes those options down into
1418 the `parameter' structs in the list.
1421 parse_command_line_into_parameters (const char *filename,
1422 const char *cmd, GList *parms)
1424 GList *tokens = tokenize_command_line (cmd);
1426 for (rest = tokens; rest; rest = rest->next)
1428 char *option = rest->data;
1431 if (option[0] != '-' && option[0] != '+')
1434 fprintf (stderr, "%s: WARNING: %s: not a switch: \"%s\"\n",
1435 blurb(), filename, option);
1441 if (rest->next) /* pop off the arg to this option */
1443 char *s = (char *) rest->next->data;
1444 /* the next token is the next switch iff it matches "-[a-z]".
1445 (To avoid losing on "-x -3.1".)
1447 if (s && (s[0] != '-' || !isalpha(s[1])))
1450 rest->next->data = 0;
1455 parse_command_line_into_parameters_1 (filename, parms,
1457 if (value) free (value);
1461 g_list_free (tokens);
1466 compare_opts (const char *option, const char *value,
1467 const char *template)
1469 int ol = strlen (option);
1472 if (strncmp (option, template, ol))
1475 if (template[ol] != (value ? ' ' : 0))
1478 /* At this point, we have a match against "option".
1479 If template contains a %, we're done.
1480 Else, compare against "value" too.
1482 c = strchr (template, '%');
1487 return (template[ol] == 0);
1488 if (strcmp (template + ol + 1, value))
1496 parse_command_line_into_parameters_1 (const char *filename,
1503 parameter *match = 0;
1507 for (p = parms; p; p = p->next)
1509 parameter *pp = (parameter *) p->data;
1512 if (pp->type == SELECT)
1514 if (parse_command_line_into_parameters_1 (filename,
1525 if (compare_opts (option, value, (char *) pp->arg))
1531 else if (pp->arg_set)
1533 if (compare_opts (option, value, (char *) pp->arg_set))
1539 else if (pp->arg_unset)
1541 if (compare_opts (option, value, (char *) pp->arg_unset))
1556 if (debug_p && !parent)
1557 fprintf (stderr, "%s: WARNING: %s: no match for %s %s\n",
1558 blurb(), filename, option, (value ? value : ""));
1562 switch (match->type)
1568 if (which != -1) abort();
1569 parameter_set_switch (match, (gpointer) value);
1572 if (which != 0 && which != 1) abort();
1573 parameter_set_switch (match, GINT_TO_POINTER(which));
1576 if (which != 1) abort();
1577 parameter_set_switch (parent, GINT_TO_POINTER(index));
1586 /* Set the parameter's value.
1587 For STRING, FILENAME, SLIDER, and SPINBUTTON, `value' is a char*.
1588 For BOOLEAN and SELECT, `value' is an int.
1591 parameter_set_switch (parameter *p, gpointer value)
1593 if (p->type == SELECT_OPTION) abort();
1594 if (!p->widget) return;
1600 gtk_entry_set_text (GTK_ENTRY (p->widget), (char *) value);
1606 GtkAdjustment *adj =
1608 ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
1609 : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
1613 if (1 == sscanf ((char *) value, "%f %c", &f, &c))
1616 f = invert_range (GET_ADJ_LOWER(adj), GET_ADJ_UPPER(adj), f) - 1;
1617 gtk_adjustment_set_value (adj, f);
1623 GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget);
1624 gtk_toggle_button_set_active (b, GPOINTER_TO_INT(value));
1629 gtk_option_menu_set_history (GTK_OPTION_MENU (p->widget),
1630 GPOINTER_TO_INT(value));
1640 restore_defaults (const char *progname, GList *parms)
1642 for (; parms; parms = parms->next)
1644 parameter *p = (parameter *) parms->data;
1645 if (!p->widget) continue;
1651 gtk_entry_set_text (GTK_ENTRY (p->widget),
1652 (p->string ? (char *) p->string : ""));
1658 GtkAdjustment *adj =
1660 ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
1661 : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
1662 float value = (p->invert_p
1663 ? invert_range (p->low, p->high, p->value)
1665 gtk_adjustment_set_value (adj, value);
1670 /* A toggle button should be on by default if it inserts
1671 nothing into the command line when on. E.g., it should
1672 be on if `arg_set' is null.
1674 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (p->widget),
1675 (!p->arg_set || !*p->arg_set));
1680 GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget);
1685 for (opts = p->options, index = 0; opts;
1686 opts = opts->next, index++)
1688 parameter *s = (parameter *) opts->data;
1689 /* The default menu item is the first one with
1690 no `arg_set' field. */
1698 gtk_option_menu_set_history (GTK_OPTION_MENU (opt), selected);
1709 /* Documentation strings
1713 get_description (GList *parms, gboolean verbose_p)
1716 for (; parms; parms = parms->next)
1718 parameter *p = (parameter *) parms->data;
1719 if (p->type == DESCRIPTION)
1726 if (!doc || !doc->string)
1730 char *d = strdup ((char *) doc->string);
1733 for (s = d; *s; s++)
1736 if (s[1] == '\n') /* blank line: leave it */
1738 else if (s[1] == ' ' || s[1] == '\t')
1739 s++; /* next line is indented: leave newline */
1740 else if (!strncmp(s+1, "http:", 5))
1741 s++; /* next line begins a URL: leave newline */
1743 s[0] = ' '; /* delete newline to un-fold this line */
1746 /* strip off leading whitespace on first line only */
1747 for (s = d; *s && (*s == ' ' || *s == '\t'); s++)
1749 while (*s == '\n') /* strip leading newlines */
1752 memmove (d, s, strlen(s)+1);
1754 /* strip off trailing whitespace and newlines */
1757 while (L && isspace(d[L-1]))
1761 /* strip off duplicated whitespaces */
1762 for (s = d; *s; s++)
1769 memmove (p, s, strlen(s)+1);
1775 fprintf (stderr, "%s: text read is \"%s\"\n", blurb(),doc->string);
1776 fprintf (stderr, "%s: description is \"%s\"\n", blurb(), d);
1777 fprintf (stderr, "%s: translation is \"%s\"\n", blurb(), _(d));
1786 /* External interface.
1790 load_configurator_1 (const char *program, const char *arguments,
1793 const char *dir = hack_configuration_path;
1795 int L = strlen (dir);
1801 if (L == 0) return 0;
1803 base_program = strrchr(program, '/');
1804 if (base_program) base_program++;
1805 if (!base_program) base_program = (char *) program;
1807 file = (char *) malloc (L + strlen (base_program) + 10);
1808 data = (conf_data *) calloc (1, sizeof(*data));
1811 if (file[L-1] != '/')
1813 strcpy (file+L, base_program);
1815 for (s = file+L; *s; s++)
1816 if (*s == '/' || *s == ' ')
1818 else if (isupper (*s))
1821 strcat (file+L, ".xml");
1823 f = fopen (file, "r");
1826 int res, size = 1024;
1828 xmlParserCtxtPtr ctxt;
1834 fprintf (stderr, "%s: reading %s...\n", blurb(), file);
1836 res = fread (chars, 1, 4, f);
1844 ctxt = xmlCreatePushParserCtxt(NULL, NULL, chars, res, file);
1845 while ((res = fread(chars, 1, size, f)) > 0)
1846 xmlParseChunk (ctxt, chars, res, 0);
1847 xmlParseChunk (ctxt, chars, 0, 1);
1849 xmlFreeParserCtxt (ctxt);
1852 /* Parsed the XML file. Now make some widgets. */
1854 vbox0 = gtk_vbox_new (FALSE, 0);
1855 gtk_widget_show (vbox0);
1857 parms = make_parameters (file, doc->xmlRootNode, vbox0);
1858 sanity_check_parameters (file, parms);
1862 restore_defaults (program, parms);
1863 if (arguments && *arguments)
1864 parse_command_line_into_parameters (program, arguments, parms);
1866 data->widget = vbox0;
1867 data->parameters = parms;
1868 data->description = get_description (parms, verbose_p);
1875 fprintf (stderr, "%s: %s does not exist.\n", blurb(), file);
1877 p = calloc (1, sizeof(*p));
1879 p->arg = (xmlChar *) strdup (arguments);
1881 data->parameters = g_list_append (0, (gpointer) p);
1884 data->progname = strdup (program);
1892 split_command_line (const char *full_command_line,
1893 char **prog_ret, char **args_ret)
1895 char *line = strdup (full_command_line);
1908 while (isspace (*s)) s++;
1911 else if (*s == '=') /* if the leading word contains an "=", skip it. */
1913 while (*s && !isspace (*s)) s++;
1914 while (isspace (*s)) s++;
1921 *prog_ret = strdup (prog);
1922 *args_ret = strdup (args);
1928 load_configurator (const char *full_command_line, gboolean verbose_p)
1933 debug_p = verbose_p;
1934 split_command_line (full_command_line, &prog, &args);
1935 cd = load_configurator_1 (prog, args, verbose_p);
1944 get_configurator_command_line (conf_data *data, gboolean default_p)
1946 char *args = parameters_to_cmd_line (data->parameters, default_p);
1947 char *result = (char *) malloc (strlen (data->progname) +
1949 strcpy (result, data->progname);
1950 strcat (result, " ");
1951 strcat (result, args);
1958 set_configurator_command_line (conf_data *data, const char *full_command_line)
1962 split_command_line (full_command_line, &prog, &args);
1963 if (data->progname) free (data->progname);
1964 data->progname = prog;
1965 restore_defaults (prog, data->parameters);
1966 parse_command_line_into_parameters (prog, args, data->parameters);
1971 free_conf_data (conf_data *data)
1973 if (data->parameters)
1976 for (rest = data->parameters; rest; rest = rest->next)
1978 free_parameter ((parameter *) rest->data);
1981 g_list_free (data->parameters);
1982 data->parameters = 0;
1986 gtk_widget_destroy (data->widget);
1989 free (data->progname);
1990 if (data->description)
1991 free (data->description);
1993 memset (data, ~0, sizeof(*data));
1998 #endif /* HAVE_GTK && HAVE_XML -- whole file */