/* demo-Gtk-conf.c --- implements the dynamic configuration dialogs.
* xscreensaver, Copyright (c) 2001-2014 Jamie Zawinski <jwz@jwz.org>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#if defined(HAVE_GTK) && defined(HAVE_XML) /* whole file */
#include <xscreensaver-intl.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <ctype.h>
/*
* Both of these workarounds can be removed when support for ancient
* libxml versions is dropped. versions 1.8.11 and 2.3.4 provide the
* correct fixes.
*/
/*
* Older libxml polluted the global headerspace, while libxml2 fixed
* this. To support both old and recent libxmls, we have this
* workaround.
*/
#ifdef HAVE_OLD_XML_HEADERS
# include <parser.h>
#else /* ! HAVE_OLD_XML_HEADERS */
# include <libxml/parser.h>
#endif /* HAVE_OLD_XML_HEADERS */
/*
* handle non-native spelling mistakes in earlier versions and provide
* the source-compat fix for this that may not be in older versions.
*/
#ifndef xmlChildrenNode
# if LIBXML_VERSION >= 20000
# define xmlChildrenNode children
# define xmlRootNode children
# else
# define xmlChildrenNode childs
# define xmlRootNode root
# endif /* LIBXML_VERSION */
#endif /* xmlChildrenNode */
#include <gtk/gtk.h>
#include "demo-Gtk-conf.h"
/* Deal with deprecation of direct access to struct fields on the way to GTK3
See http://live.gnome.org/GnomeGoals/UseGseal
*/
#if GTK_CHECK_VERSION(2,14,0)
# define GET_PARENT(w) gtk_widget_get_parent (w)
# define GET_ADJ_VALUE(a) gtk_adjustment_get_value (a)
# define GET_ADJ_UPPER(a) gtk_adjustment_get_upper (a)
# define GET_ADJ_LOWER(a) gtk_adjustment_get_lower (a)
#else
# define GET_PARENT(w) ((w)->parent)
# define GET_ADJ_VALUE(a) ((a)->value)
# define GET_ADJ_UPPER(a) ((a)->upper)
# define GET_ADJ_LOWER(a) ((a)->lower)
#endif
extern const char *blurb (void);
const char *hack_configuration_path = HACK_CONFIGURATION_PATH;
static gboolean debug_p = FALSE;
#define MIN_SLIDER_WIDTH 150
#define MIN_SPINBUTTON_WIDTH 48
#define MIN_LABEL_WIDTH 70
typedef enum {
COMMAND,
FAKE,
DESCRIPTION,
FAKEPREVIEW,
STRING,
FILENAME,
SLIDER,
SPINBUTTON,
BOOLEAN,
SELECT,
SELECT_OPTION
} parameter_type;
typedef struct {
parameter_type type;
xmlChar *id; /* widget name */
xmlChar *label; /* heading label, or null */
/* command, fake, description, fakepreview, string, file
*/
xmlChar *string; /* file name, description, whatever. */
/* slider, spinbutton
*/
xmlChar *low_label; /* label for the left side */
xmlChar *high_label; /* label for the right side */
float low; /* minimum value */
float high; /* maximum value */
float value; /* default value */
gboolean integer_p; /* whether the range is integral, or real */
xmlChar *arg; /* command-line option to set (substitute "%") */
gboolean invert_p; /* whether to flip the value and pretend the
range goes from hi-low instead of low-hi. */
/* boolean, select-option
*/
xmlChar *arg_set; /* command-line option to set for "yes", or null */
xmlChar *arg_unset; /* command-line option to set for "no", or null */
/* select
*/
GList *options;
/* select_option
*/
GtkWidget *widget;
} parameter;
static parameter *make_select_option (const char *file, xmlNodePtr);
static void make_parameter_widget (const char *filename,
parameter *, GtkWidget *);
static void browse_button_cb (GtkButton *button, gpointer user_data);
/* Frees the parameter object and all strings and sub-parameters.
Does not destroy the widget, if any.
*/
static void
free_parameter (parameter *p)
{
GList *rest;
if (p->id) free (p->id);
if (p->label) free (p->label);
if (p->string) free (p->string);
if (p->low_label) free (p->low_label);
if (p->high_label) free (p->high_label);
if (p->arg) free (p->arg);
if (p->arg_set) free (p->arg_set);
if (p->arg_unset) free (p->arg_unset);
for (rest = p->options; rest; rest = rest->next)
if (rest->data)
free_parameter ((parameter *) rest->data);
memset (p, ~0, sizeof(*p));
free (p);
}
/* Debugging: dumps out a `parameter' structure.
*/
#if 0
void
describe_parameter (FILE *out, parameter *p)
{
fprintf (out, "<");
switch (p->type)
{
case COMMAND: fprintf (out, "command"); break;
case FAKE: fprintf (out, "fake"); break;
case DESCRIPTION: fprintf (out, "_description"); break;
case FAKEPREVIEW: fprintf (out, "fakepreview"); break;
case STRING: fprintf (out, "string"); break;
case FILENAME: fprintf (out, "filename"); break;
case SLIDER: fprintf (out, "number type=\"slider\""); break;
case SPINBUTTON: fprintf (out, "number type=\"spinbutton\""); break;
case BOOLEAN: fprintf (out, "boolean"); break;
case SELECT: fprintf (out, "select"); break;
default: abort(); break;
}
if (p->id) fprintf (out, " id=\"%s\"", p->id);
if (p->label) fprintf (out, " _label=\"%s\"", p->label);
if (p->string && p->type != DESCRIPTION)
fprintf (out, " string=\"%s\"", p->string);
if (p->low_label) fprintf (out, " _low-label=\"%s\"", p->low_label);
if (p->high_label) fprintf (out, " _high-label=\"%s\"", p->high_label);
if (p->low) fprintf (out, " low=\"%.2f\"", p->low);
if (p->high) fprintf (out, " high=\"%.2f\"", p->high);
if (p->value) fprintf (out, " default=\"%.2f\"", p->value);
if (p->arg) fprintf (out, " arg=\"%s\"", p->arg);
if (p->invert_p) fprintf (out, " convert=\"invert\"");
if (p->arg_set) fprintf (out, " arg-set=\"%s\"", p->arg_set);
if (p->arg_unset) fprintf (out, " arg-unset=\"%s\"", p->arg_unset);
fprintf (out, ">\n");
if (p->type == SELECT)
{
GList *opt;
for (opt = p->options; opt; opt = opt->next)
{
parameter *o = (parameter *) opt->data;
if (o->type != SELECT_OPTION) abort();
fprintf (out, " <option");
if (o->id) fprintf (out, " id=\"%s\"", o->id);
if (o->label) fprintf (out, " _label=\"%s\"", o->label);
if (o->arg_set) fprintf (out, " arg-set=\"%s\"", o->arg_set);
if (o->arg_unset) fprintf (out, " arg-unset=\"%s\"", o->arg_unset);
fprintf (out, ">\n");
}
fprintf (out, "</select>\n");
}
else if (p->type == DESCRIPTION)
{
if (p->string)
fprintf (out, " %s\n", p->string);
fprintf (out, "</_description>\n");
}
}
#endif /* 0 */
/* Like xmlGetProp() but parses a float out of the string.
If the number was expressed as a float and not an integer
(that is, the string contained a decimal point) then
`floatp' is set to TRUE. Otherwise, it is unchanged.
*/
static float
xml_get_float (xmlNodePtr node, const xmlChar *name, gboolean *floatpP)
{
const char *s = (char *) xmlGetProp (node, name);
float f;
char c;
if (!s || 1 != sscanf (s, "%f %c", &f, &c))
return 0;
else
{
if (strchr (s, '.')) *floatpP = TRUE;
return f;
}
}
static void sanity_check_parameter (const char *filename,
const xmlChar *node_name,
parameter *p);
static void sanity_check_text_node (const char *filename,
const xmlNodePtr node);
static void sanity_check_menu_options (const char *filename,
const xmlChar *node_name,
parameter *p);
/* Allocates and returns a new `parameter' object based on the
properties in the given XML node. Returns 0 if there's nothing
to create (comment, or unknown tag.)
*/
static parameter *
make_parameter (const char *filename, xmlNodePtr node)
{
parameter *p;
const char *name = (char *) node->name;
const char *convert;
gboolean floatp = FALSE;
if (node->type == XML_COMMENT_NODE)
return 0;
p = calloc (1, sizeof(*p));
if (!name) abort();
else if (!strcmp (name, "command")) p->type = COMMAND;
else if (!strcmp (name, "fullcommand")) p->type = COMMAND;
else if (!strcmp (name, "_description")) p->type = DESCRIPTION;
else if (!strcmp (name, "fakepreview")) p->type = FAKEPREVIEW;
else if (!strcmp (name, "fake")) p->type = FAKE;
else if (!strcmp (name, "boolean")) p->type = BOOLEAN;
else if (!strcmp (name, "string")) p->type = STRING;
else if (!strcmp (name, "file")) p->type = FILENAME;
else if (!strcmp (name, "number")) p->type = SPINBUTTON;
else if (!strcmp (name, "select")) p->type = SELECT;
else if (!strcmp (name, "xscreensaver-text") || /* ignored in X11; */
!strcmp (name, "xscreensaver-image") || /* used in Cocoa. */
!strcmp (name, "xscreensaver-updater") ||
!strcmp (name, "video"))
{
free (p);
return 0;
}
else if (node->type == XML_TEXT_NODE)
{
sanity_check_text_node (filename, node);
free (p);
return 0;
}
else
{
if (debug_p)
fprintf (stderr, "%s: WARNING: %s: unknown tag: \"%s\"\n",
blurb(), filename, name);
free (p);
return 0;
}
if (p->type == SPINBUTTON)
{
const char *type = (char *) xmlGetProp (node, (xmlChar *) "type");
if (!type || !strcmp (type, "spinbutton")) p->type = SPINBUTTON;
else if (!strcmp (type, "slider")) p->type = SLIDER;
else
{
if (debug_p)
fprintf (stderr, "%s: WARNING: %s: unknown %s type: \"%s\"\n",
blurb(), filename, name, type);
free (p);
return 0;
}
}
else if (p->type == DESCRIPTION)
{
if (node->xmlChildrenNode &&
node->xmlChildrenNode->type == XML_TEXT_NODE &&
!node->xmlChildrenNode->next)
p->string = (xmlChar *)
strdup ((char *) node->xmlChildrenNode->content);
}
p->id = xmlGetProp (node, (xmlChar *) "id");
p->label = xmlGetProp (node, (xmlChar *) "_label");
p->low_label = xmlGetProp (node, (xmlChar *) "_low-label");
p->high_label = xmlGetProp (node, (xmlChar *) "_high-label");
p->low = xml_get_float (node, (xmlChar *) "low", &floatp);
p->high = xml_get_float (node, (xmlChar *) "high", &floatp);
p->value = xml_get_float (node, (xmlChar *) "default", &floatp);
p->integer_p = !floatp;
convert = (char *) xmlGetProp (node, (xmlChar *) "convert");
p->invert_p = (convert && !strcmp (convert, "invert"));
p->arg = xmlGetProp (node, (xmlChar *) "arg");
p->arg_set = xmlGetProp (node, (xmlChar *) "arg-set");
p->arg_unset = xmlGetProp (node, (xmlChar *) "arg-unset");
/* Check for missing decimal point */
if (debug_p &&
p->integer_p &&
(p->high != p->low) &&
(p->high - p->low) <= 1)
fprintf (stderr,
"%s: WARNING: %s: %s: range [%.1f, %.1f] shouldn't be integral!\n",
blurb(), filename, p->id,
p->low, p->high);
if (p->type == SELECT)
{
xmlNodePtr kids;
for (kids = node->xmlChildrenNode; kids; kids = kids->next)
{
parameter *s = make_select_option (filename, kids);
if (s)
p->options = g_list_append (p->options, s);
}
}
sanity_check_parameter (filename, (const xmlChar *) name, p);
return p;
}
/* Allocates and returns a new SELECT_OPTION `parameter' object based
on the properties in the given XML node. Returns 0 if there's nothing
to create (comment, or unknown tag.)
*/
static parameter *
make_select_option (const char *filename, xmlNodePtr node)
{
if (node->type == XML_COMMENT_NODE)
return 0;
else if (node->type == XML_TEXT_NODE)
{
sanity_check_text_node (filename, node);
return 0;
}
else if (node->type != XML_ELEMENT_NODE)
{
if (debug_p)
fprintf (stderr,
"%s: WARNING: %s: %s: unexpected child tag type %d\n",
blurb(), filename, node->name, (int)node->type);
return 0;
}
else if (strcmp ((char *) node->name, "option"))
{
if (debug_p)
fprintf (stderr,
"%s: WARNING: %s: %s: child not an option tag: \"%s\"\n",
blurb(), filename, node->name, node->name);
return 0;
}
else
{
parameter *s = calloc (1, sizeof(*s));
s->type = SELECT_OPTION;
s->id = xmlGetProp (node, (xmlChar *) "id");
s->label = xmlGetProp (node, (xmlChar *) "_label");
s->arg_set = xmlGetProp (node, (xmlChar *) "arg-set");
s->arg_unset = xmlGetProp (node, (xmlChar *) "arg-unset");
sanity_check_parameter (filename, node->name, s);
return s;
}
}
/* Rudimentary check to make sure someone hasn't typed "arg-set="
when they should have typed "arg=", etc.
*/
static void
sanity_check_parameter (const char *filename, const xmlChar *node_name,
parameter *p)
{
struct {
gboolean id;
gboolean label;
gboolean string;
gboolean low_label;
gboolean high_label;
gboolean low;
gboolean high;
gboolean value;
gboolean arg;
gboolean invert_p;
gboolean arg_set;
gboolean arg_unset;
} allowed, require;
memset (&allowed, 0, sizeof (allowed));
memset (&require, 0, sizeof (require));
switch (p->type)
{
case COMMAND:
allowed.arg = TRUE;
require.arg = TRUE;
break;
case FAKE:
break;
case DESCRIPTION:
allowed.string = TRUE;
break;
case FAKEPREVIEW:
break;
case STRING:
allowed.id = TRUE;
require.id = TRUE;
allowed.label = TRUE;
require.label = TRUE;
allowed.arg = TRUE;
require.arg = TRUE;
break;
case FILENAME:
allowed.id = TRUE;
require.id = TRUE;
allowed.label = TRUE;
allowed.arg = TRUE;
require.arg = TRUE;
break;
case SLIDER:
allowed.id = TRUE;
require.id = TRUE;
allowed.label = TRUE;
allowed.low_label = TRUE;
allowed.high_label = TRUE;
allowed.arg = TRUE;
require.arg = TRUE;
allowed.low = TRUE;
/* require.low = TRUE; -- may be 0 */
allowed.high = TRUE;
/* require.high = TRUE; -- may be 0 */
allowed.value = TRUE;
/* require.value = TRUE; -- may be 0 */
allowed.invert_p = TRUE;
break;
case SPINBUTTON:
allowed.id = TRUE;
require.id = TRUE;
allowed.label = TRUE;
allowed.arg = TRUE;
require.arg = TRUE;
allowed.low = TRUE;
/* require.low = TRUE; -- may be 0 */
allowed.high = TRUE;
/* require.high = TRUE; -- may be 0 */
allowed.value = TRUE;
/* require.value = TRUE; -- may be 0 */
allowed.invert_p = TRUE;
break;
case BOOLEAN:
allowed.id = TRUE;
require.id = TRUE;
allowed.label = TRUE;
allowed.arg_set = TRUE;
allowed.arg_unset = TRUE;
break;
case SELECT:
allowed.id = TRUE;
require.id = TRUE;
break;
case SELECT_OPTION:
allowed.id = TRUE;
allowed.label = TRUE;
require.label = TRUE;
allowed.arg_set = TRUE;
break;
default:
abort();
break;
}
# define WARN(STR) \
fprintf (stderr, "%s: %s: " STR " in <%s%s id=\"%s\">\n", \
blurb(), filename, node_name, \
(!strcmp((char *) node_name, "number") \
? (p->type == SPINBUTTON ? " type=spinbutton" : " type=slider")\
: ""), \
(p->id ? (char *) p->id : ""))
# define CHECK(SLOT,NAME) \
if (p->SLOT && !allowed.SLOT) \
WARN ("\"" NAME "\" is not a valid option"); \
if (!p->SLOT && require.SLOT) \
WARN ("\"" NAME "\" is required")
CHECK (id, "id");
CHECK (label, "_label");
CHECK (string, "(body text)");
CHECK (low_label, "_low-label");
CHECK (high_label, "_high-label");
CHECK (low, "low");
CHECK (high, "high");
CHECK (value, "default");
CHECK (arg, "arg");
CHECK (invert_p, "convert");
CHECK (arg_set, "arg-set");
CHECK (arg_unset, "arg-unset");
# undef CHECK
# undef WARN
if (p->type == SELECT)
sanity_check_menu_options (filename, node_name, p);
}
static void
sanity_check_menu_options (const char *filename, const xmlChar *node_name,
parameter *p)
{
GList *opts;
int noptions = 0;
int nulls = 0;
char *prefix = 0;
/* fprintf (stderr, "\n## %s\n", p->id);*/
for (opts = p->options; opts; opts = opts->next)
{
parameter *s = (parameter *) opts->data;
if (!s->arg_set) nulls++;
noptions++;
if (s->arg_set)
{
char *a = strdup ((char *) s->arg_set);
char *spc = strchr (a, ' ');
if (spc) *spc = 0;
if (prefix)
{
if (strcmp (a, prefix))
fprintf (stderr,
"%s: %s: both \"%s\" and \"%s\" used in <select id=\"%s\">\n",
blurb(), filename, prefix, a, p->id);
free (prefix);
}
prefix = a;
}
/* fprintf (stderr, "\n %s\n", s->arg_set);*/
}
if (prefix) free (prefix);
prefix = 0;
if (nulls > 1)
fprintf (stderr,
"%s: %s: more than one menu with no arg-set in <select id=\"%s\">\n",
blurb(), filename, p->id);
}
/* "text" nodes show up for all the non-tag text in the file, including
all the newlines between tags. Warn if there is text there that
is not whitespace.
*/
static void
sanity_check_text_node (const char *filename, const xmlNodePtr node)
{
const char *body = (const char *) node->content;
if (node->type != XML_TEXT_NODE) abort();
while (isspace (*body)) body++;
if (*body)
fprintf (stderr, "%s: WARNING: %s: random text present: \"%s\"\n",
blurb(), filename, body);
}
/* Returns a list of strings, every switch mentioned in the parameters.
The strings must be freed.
*/
static GList *
get_all_switches (const char *filename, GList *parms)
{
GList *switches = 0;
GList *p;
for (p = parms; p; p = p->next)
{
parameter *pp = (parameter *) p->data;
if (pp->type == SELECT)
{
GList *list2 = get_all_switches (filename, pp->options);
switches = g_list_concat (switches, list2);
}
if (pp->arg && *pp->arg)
switches = g_list_append (switches, strdup ((char *) pp->arg));
if (pp->arg_set && *pp->arg_set)
switches = g_list_append (switches, strdup ((char *) pp->arg_set));
if (pp->arg_unset && *pp->arg_unset)
switches = g_list_append (switches, strdup ((char *) pp->arg_unset));
}
return switches;
}
/* Ensures that no switch is mentioned more than once.
*/
static void
sanity_check_parameters (const char *filename, GList *parms)
{
GList *list = get_all_switches (filename, parms);
GList *p;
for (p = list; p; p = p->next)
{
char *sw = (char *) p->data;
GList *p2;
if (*sw != '-' && *sw != '+')
fprintf (stderr, "%s: %s: switch does not begin with hyphen \"%s\"\n",
blurb(), filename, sw);
for (p2 = p->next; p2; p2 = p2->next)
{
const char *sw2 = (const char *) p2->data;
if (!strcmp (sw, sw2))
fprintf (stderr, "%s: %s: duplicate switch \"%s\"\n",
blurb(), filename, sw);
}
free (sw);
}
g_list_free (list);
}
/* Helper for make_parameters()
*/
static GList *
make_parameters_1 (const char *filename, xmlNodePtr node, GtkWidget *parent)
{
GList *list = 0;
for (; node; node = node->next)
{
const char *name = (char *) node->name;
if (!strcmp (name, "hgroup") ||
!strcmp (name, "vgroup"))
{
GtkWidget *box = (*name == 'h'
? gtk_hbox_new (FALSE, 0)
: gtk_vbox_new (FALSE, 0));
GList *list2;
gtk_widget_show (box);
gtk_box_pack_start (GTK_BOX (parent), box, FALSE, FALSE, 0);
list2 = make_parameters_1 (filename, node->xmlChildrenNode, box);
if (list2)
list = g_list_concat (list, list2);
}
else
{
parameter *p = make_parameter (filename, node);
if (p)
{
list = g_list_append (list, p);
make_parameter_widget (filename, p, parent);
}
}
}
return list;
}
/* Calls make_parameter() and make_parameter_widget() on each relevant
tag in the XML tree. Also handles the "hgroup" and "vgroup" flags.
Returns a GList of `parameter' objects.
*/
static GList *
make_parameters (const char *filename, xmlNodePtr node, GtkWidget *parent)
{
for (; node; node = node->next)
{
if (node->type == XML_ELEMENT_NODE &&
!strcmp ((char *) node->name, "screensaver"))
return make_parameters_1 (filename, node->xmlChildrenNode, parent);
}
return 0;
}
static gfloat
invert_range (gfloat low, gfloat high, gfloat value)
{
gfloat range = high-low;
gfloat off = value-low;
return (low + (range - off));
}
static GtkAdjustment *
make_adjustment (const char *filename, parameter *p)
{
float range = (p->high - p->low);
float value = (p->invert_p
? invert_range (p->low, p->high, p->value)
: p->value);
gfloat si = (p->high - p->low) / 100;
gfloat pi = (p->high - p->low) / 10;
gfloat page_size = ((p->type == SLIDER) ? 1 : 0);
if (p->value < p->low || p->value > p->high)
{
if (debug_p && p->integer_p)
fprintf (stderr, "%s: WARNING: %s: %d is not in range [%d, %d]\n",
blurb(), filename,
(int) p->value, (int) p->low, (int) p->high);
else if (debug_p)
fprintf (stderr,
"%s: WARNING: %s: %.2f is not in range [%.2f, %.2f]\n",
blurb(), filename, p->value, p->low, p->high);
value = (value < p->low ? p->low : p->high);
}
#if 0
else if (debug_p && p->value < 1000 && p->high >= 10000)
{
if (p->integer_p)
fprintf (stderr,
"%s: WARNING: %s: %d is suspicious for range [%d, %d]\n",
blurb(), filename,
(int) p->value, (int) p->low, (int) p->high);
else
fprintf (stderr,
"%s: WARNING: %s: %.2f is suspicious for range [%.2f, %.2f]\n",
blurb(), filename, p->value, p->low, p->high);
}
#endif /* 0 */
si = (int) (si + 0.5);
pi = (int) (pi + 0.5);
if (si < 1) si = 1;
if (pi < 1) pi = 1;
if (range <= 500) si = 1;
return GTK_ADJUSTMENT (gtk_adjustment_new (value,
p->low,
p->high + page_size,
si, pi, page_size));
}
static void
set_widget_min_width (GtkWidget *w, int width)
{
GtkRequisition req;
gtk_widget_size_request (GTK_WIDGET (w), &req);
if (req.width < width)
gtk_widget_set_size_request (GTK_WIDGET (w), width, -1);
}
/* If we're inside a vbox, we need to put an hbox in it, or labels appear
on top instead of to the left, and things stretch to the full width of
the window.
*/
static GtkWidget *
insert_fake_hbox (GtkWidget *parent)
{
if (GTK_IS_VBOX (parent))
{
GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (parent), hbox, FALSE, FALSE, 4);
gtk_widget_show (hbox);
return hbox;
}
return parent;
}
static void
link_atk_label_to_widget(GtkWidget *label, GtkWidget *widget)
{
AtkObject *atk_label = gtk_widget_get_accessible (label);
AtkObject *atk_widget = gtk_widget_get_accessible (widget);
atk_object_add_relationship (atk_label, ATK_RELATION_LABEL_FOR,
atk_widget);
atk_object_add_relationship (atk_widget, ATK_RELATION_LABELLED_BY,
atk_label);
}
/* Given a `parameter' struct, allocates an appropriate GtkWidget for it,
and stores it in `p->widget'.
`parent' must be a GtkBox.
*/
static void
make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent)
{
const char *label = (char *) p->label;
if (p->widget) return;
switch (p->type)
{
case STRING:
{
GtkWidget *entry = gtk_entry_new ();
parent = insert_fake_hbox (parent);
if (label)
{
GtkWidget *w = gtk_label_new (_(label));
link_atk_label_to_widget (w, entry);
gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH);
gtk_widget_show (w);
gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
}
p->widget = entry;
if (p->string)
gtk_entry_set_text (GTK_ENTRY (p->widget), (char *) p->string);
gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
break;
}
case FILENAME:
{
GtkWidget *L = gtk_label_new (label ? _(label) : "");
GtkWidget *entry = gtk_entry_new ();
GtkWidget *button = gtk_button_new_with_label (_("Browse..."));
link_atk_label_to_widget (L, entry);
gtk_widget_show (entry);
gtk_widget_show (button);
p->widget = entry;
gtk_signal_connect (GTK_OBJECT (button),
"clicked", GTK_SIGNAL_FUNC (browse_button_cb),
(gpointer) entry);
gtk_label_set_justify (GTK_LABEL (L), GTK_JUSTIFY_RIGHT);
gtk_misc_set_alignment (GTK_MISC (L), 1.0, 0.5);
set_widget_min_width (GTK_WIDGET (L), MIN_LABEL_WIDTH);
gtk_widget_show (L);
if (p->string)
gtk_entry_set_text (GTK_ENTRY (entry), (char *) p->string);
parent = insert_fake_hbox (parent);
gtk_box_pack_start (GTK_BOX (parent), L, FALSE, FALSE, 4);
gtk_box_pack_start (GTK_BOX (parent), entry, TRUE, TRUE, 4);
gtk_box_pack_start (GTK_BOX (parent), button, FALSE, FALSE, 4);
break;
}
case SLIDER:
{
GtkAdjustment *adj = make_adjustment (filename, p);
GtkWidget *scale = gtk_hscale_new (adj);
GtkWidget *labelw = 0;
if (label)
{
labelw = gtk_label_new (_(label));
link_atk_label_to_widget (labelw, scale);
gtk_label_set_justify (GTK_LABEL (labelw), GTK_JUSTIFY_LEFT);
gtk_misc_set_alignment (GTK_MISC (labelw), 0.0, 0.5);
set_widget_min_width (GTK_WIDGET (labelw), MIN_LABEL_WIDTH);
gtk_widget_show (labelw);
gtk_box_pack_start (GTK_BOX (parent), labelw, FALSE, FALSE, 2);
}
/* Do this after 'labelw' so that it appears above, not to left. */
parent = insert_fake_hbox (parent);
if (p->low_label)
{
GtkWidget *w = gtk_label_new (_((char *) p->low_label));
link_atk_label_to_widget (w, scale);
gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH);
gtk_widget_show (w);
gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
}
gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_BOTTOM);
gtk_scale_set_draw_value (GTK_SCALE (scale), debug_p);
gtk_scale_set_digits (GTK_SCALE (scale), (p->integer_p ? 0 : 2));
set_widget_min_width (GTK_WIDGET (scale), MIN_SLIDER_WIDTH);
gtk_box_pack_start (GTK_BOX (parent), scale, FALSE, FALSE, 4);
gtk_widget_show (scale);
if (p->high_label)
{
GtkWidget *w = gtk_label_new (_((char *) p->high_label));
link_atk_label_to_widget (w, scale);
gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT);
gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH);
gtk_widget_show (w);
gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
}
p->widget = scale;
break;
}
case SPINBUTTON:
{
GtkAdjustment *adj = make_adjustment (filename, p);
GtkWidget *spin = gtk_spin_button_new (adj, 15, 0);
gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE);
gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (spin), TRUE);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), GET_ADJ_VALUE(adj));
set_widget_min_width (GTK_WIDGET (spin), MIN_SPINBUTTON_WIDTH);
if (label)
{
GtkWidget *w = gtk_label_new (_(label));
link_atk_label_to_widget (w, spin);
gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH);
gtk_widget_show (w);
parent = insert_fake_hbox (parent);
gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
}
gtk_widget_show (spin);
gtk_box_pack_start (GTK_BOX (parent), spin, FALSE, FALSE, 4);
p->widget = spin;
break;
}
case BOOLEAN:
{
p->widget = gtk_check_button_new_with_label (_(label));
/* Let these stretch -- doesn't hurt.
parent = insert_fake_hbox (parent);
*/
gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
break;
}
case SELECT:
{
GtkWidget *opt = gtk_option_menu_new ();
GtkWidget *menu = gtk_menu_new ();
GList *opts;
for (opts = p->options; opts; opts = opts->next)
{
parameter *s = (parameter *) opts->data;
GtkWidget *i = gtk_menu_item_new_with_label (_((char *) s->label));
gtk_widget_show (i);
gtk_menu_append (GTK_MENU (menu), i);
}
gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu);
p->widget = opt;
parent = insert_fake_hbox (parent);
gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
break;
}
case COMMAND:
case FAKE:
case DESCRIPTION:
case FAKEPREVIEW:
break;
default:
abort();
}
if (p->widget)
{
gtk_widget_set_name (p->widget, (char *) p->id);
gtk_widget_show (p->widget);
}
}
/* File selection.
Absurdly, there is no GTK file entry widget, only a GNOME one,
so in order to avoid depending on GNOME in this code, we have
to do it ourselves.
*/
/* cancel button on GtkFileSelection: user_data unused */
static void
file_sel_cancel (GtkWidget *button, gpointer user_data)
{
GtkWidget *dialog = button;
while (GET_PARENT (dialog))
dialog = GET_PARENT (dialog);
gtk_widget_destroy (dialog);
}
/* ok button on GtkFileSelection: user_data is the corresponding GtkEntry */
static void
file_sel_ok (GtkWidget *button, gpointer user_data)
{
GtkWidget *entry = GTK_WIDGET (user_data);
GtkWidget *dialog = button;
const char *path;
while (GET_PARENT (dialog))
dialog = GET_PARENT (dialog);
gtk_widget_hide (dialog);
path = gtk_file_selection_get_filename (GTK_FILE_SELECTION (dialog));
/* apparently one doesn't free `path' */
gtk_entry_set_text (GTK_ENTRY (entry), path);
gtk_entry_set_position (GTK_ENTRY (entry), strlen (path));
gtk_widget_destroy (dialog);
}
/* WM close on GtkFileSelection: user_data unused */
static void
file_sel_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
file_sel_cancel (widget, user_data);
}
/* "Browse" button: user_data is the corresponding GtkEntry */
static void
browse_button_cb (GtkButton *button, gpointer user_data)
{
GtkWidget *entry = GTK_WIDGET (user_data);
const char *text = gtk_entry_get_text (GTK_ENTRY (entry));
GtkFileSelection *selector =
GTK_FILE_SELECTION (gtk_file_selection_new (_("Select file.")));
gtk_file_selection_set_filename (selector, text);
gtk_signal_connect (GTK_OBJECT (selector->ok_button),
"clicked", GTK_SIGNAL_FUNC (file_sel_ok),
(gpointer) entry);
gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
"clicked", GTK_SIGNAL_FUNC (file_sel_cancel),
(gpointer) entry);
gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
GTK_SIGNAL_FUNC (file_sel_close),
(gpointer) entry);
gtk_window_set_modal (GTK_WINDOW (selector), TRUE);
gtk_widget_show (GTK_WIDGET (selector));
}
/* Converting to and from command-lines
*/
/* Returns a copy of string that has been quoted according to shell rules:
it may have been wrapped in "" and had some characters backslashed; or
it may be unchanged.
*/
static char *
shell_quotify (const char *string)
{
char *string2 = (char *) malloc ((strlen (string) * 2) + 10);
const char *in;
char *out;
int need_quotes = 0;
int in_length = 0;
out = string2;
*out++ = '"';
for (in = string; *in; in++)
{
in_length++;
if (*in == '!' ||
*in == '"' ||
*in == '$')
{
need_quotes = 1;
*out++ = '\\';
*out++ = *in;
}
else if (*in <= ' ' ||
*in >= 127 ||
*in == '\'' ||
*in == '#' ||
*in == '%' ||
*in == '&' ||
*in == '(' ||
*in == ')' ||
*in == '*')
{
need_quotes = 1;
*out++ = *in;
}
else
*out++ = *in;
}
*out++ = '"';
*out = 0;
if (in_length == 0)
need_quotes = 1;
if (need_quotes)
return (string2);
free (string2);
return strdup (string);
}
/* Modify the string in place to remove wrapping double-quotes
and interior backslashes.
*/
static void
de_stringify (char *s)
{
char q = s[0];
if (q != '\'' && q != '\"' && q != '`')
abort();
memmove (s, s+1, strlen (s));
while (*s && *s != q)
{
if (*s == '\\')
memmove (s, s+1, strlen (s)+1);
s++;
}
if (*s != q) abort();
*s = 0;
}
/* Substitutes a shell-quotified version of `value' into `p->arg' at
the place where the `%' character appeared.
*/
static char *
format_switch (parameter *p, const char *value)
{
char *fmt = (char *) p->arg;
char *v2;
char *result, *s;
if (!fmt || !value) return 0;
v2 = shell_quotify (value);
result = (char *) malloc (strlen (fmt) + strlen (v2) + 10);
s = result;
for (; *fmt; fmt++)
if (*fmt != '%')
*s++ = *fmt;
else
{
strcpy (s, v2);
s += strlen (s);
}
*s = 0;
free (v2);
return result;
}
/* Maps a `parameter' to a command-line switch.
Returns 0 if it can't, or if the parameter has the default value.
*/
static char *
parameter_to_switch (parameter *p)
{
switch (p->type)
{
case COMMAND:
if (p->arg)
return strdup ((char *) p->arg);
else
return 0;
break;
case STRING:
case FILENAME:
if (!p->widget) return 0;
{
const char *s = gtk_entry_get_text (GTK_ENTRY (p->widget));
char *v;
if (!strcmp ((s ? s : ""),
(p->string ? (char *) p->string : "")))
v = 0; /* same as default */
else
v = format_switch (p, s);
/* don't free `s' */
return v;
}
case SLIDER:
case SPINBUTTON:
if (!p->widget) return 0;
{
GtkAdjustment *adj =
(p->type == SLIDER
? gtk_range_get_adjustment (GTK_RANGE (p->widget))
: gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
char buf[255];
char *s1;
float value = (p->invert_p
? invert_range (GET_ADJ_LOWER(adj), GET_ADJ_UPPER(adj),
GET_ADJ_VALUE(adj)) - 1
: GET_ADJ_VALUE(adj));
if (value == p->value) /* same as default */
return 0;
if (p->integer_p)
sprintf (buf, "%d", (int) (value + (value > 0 ? 0.5 : -0.5)));
else
sprintf (buf, "%.4f", value);
s1 = strchr (buf, '.');
if (s1)
{
char *s2 = s1 + strlen(s1) - 1;
while (s2 > s1 && *s2 == '0') /* lose trailing zeroes */
*s2-- = 0;
if (s2 >= s1 && *s2 == '.') /* lose trailing decimal */
*s2-- = 0;
}
return format_switch (p, buf);
}
case BOOLEAN:
if (!p->widget) return 0;
{
GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget);
const char *s = (gtk_toggle_button_get_active (b)
? (char *) p->arg_set
: (char *) p->arg_unset);
if (s)
return strdup (s);
else
return 0;
}
case SELECT:
if (!p->widget) return 0;
{
GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget);
GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
GtkWidget *selected = gtk_menu_get_active (menu);
GList *kids = gtk_container_children (GTK_CONTAINER (menu));
int menu_elt = g_list_index (kids, (gpointer) selected);
GList *ol = g_list_nth (p->options, menu_elt);
parameter *o = (ol ? (parameter *) ol->data : 0);
const char *s;
if (!o) abort();
if (o->type != SELECT_OPTION) abort();
s = (char *) o->arg_set;
if (s)
return strdup (s);
else
return 0;
}
default:
if (p->widget)
abort();
else
return 0;
}
}
/* Maps a GList of `parameter' objects to a complete command-line string.
All arguments will be properly quoted.
*/
static char *
parameters_to_cmd_line (GList *parms, gboolean default_p)
{
int L = g_list_length (parms);
int LL = 0;
char **strs = (char **) calloc (sizeof (*parms), L);
char *result;
char *out;
int i, j;
for (i = 0, j = 0; parms; parms = parms->next, i++)
{
parameter *p = (parameter *) parms->data;
if (!default_p || p->type == COMMAND)
{
char *s = parameter_to_switch (p);
strs[j++] = s;
LL += (s ? strlen(s) : 0) + 1;
}
}
result = (char *) malloc (LL + 10);
out = result;
for (i = 0; i < j; i++)
if (strs[i])
{
strcpy (out, strs[i]);
out += strlen (out);
*out++ = ' ';
free (strs[i]);
}
*out = 0;
while (out > result && out[-1] == ' ') /* strip trailing spaces */
*(--out) = 0;
free (strs);
return result;
}
/* Returns a GList of the tokens the string, using shell syntax;
Quoted strings are handled as a single token.
*/
static GList *
tokenize_command_line (const char *cmd)
{
GList *result = 0;
const char *s = cmd;
while (*s)
{
const char *start;
char *ss;
for (; isspace(*s); s++); /* skip whitespace */
start = s;
if (*s == '\'' || *s == '\"' || *s == '`')
{
char q = *s;
s++;
while (*s && *s != q) /* skip to matching quote */
{
if (*s == '\\' && s[1]) /* allowing backslash quoting */
s++;
s++;
}
s++;
}
else
{
while (*s &&
(! (isspace(*s) ||
*s == '\'' ||
*s == '\"' ||
*s == '`')))
s++;
}
if (s > start)
{
ss = (char *) malloc ((s - start) + 1);
strncpy (ss, start, s-start);
ss[s-start] = 0;
if (*ss == '\'' || *ss == '\"' || *ss == '`')
de_stringify (ss);
result = g_list_append (result, ss);
}
}
return result;
}
static void parameter_set_switch (parameter *, gpointer value);
static gboolean parse_command_line_into_parameters_1 (const char *filename,
GList *parms,
const char *option,
const char *value,
parameter *parent);
/* Parses the command line, and flushes those options down into
the `parameter' structs in the list.
*/
static void
parse_command_line_into_parameters (const char *filename,
const char *cmd, GList *parms)
{
GList *tokens = tokenize_command_line (cmd);
GList *rest;
for (rest = tokens; rest; rest = rest->next)
{
char *option = rest->data;
rest->data = 0;
if (option[0] != '-' && option[0] != '+')
{
if (debug_p)
fprintf (stderr, "%s: WARNING: %s: not a switch: \"%s\"\n",
blurb(), filename, option);
}
else
{
char *value = 0;
if (rest->next) /* pop off the arg to this option */
{
char *s = (char *) rest->next->data;
/* the next token is the next switch iff it matches "-[a-z]".
(To avoid losing on "-x -3.1".)
*/
if (s && (s[0] != '-' || !isalpha(s[1])))
{
value = s;
rest->next->data = 0;
rest = rest->next;
}
}
parse_command_line_into_parameters_1 (filename, parms,
option, value, 0);
if (value) free (value);
free (option);
}
}
g_list_free (tokens);
}
static gboolean
compare_opts (const char *option, const char *value,
const char *template)
{
int ol = strlen (option);
char *c;
if (strncmp (option, template, ol))
return FALSE;
if (template[ol] != (value ? ' ' : 0))
return FALSE;
/* At this point, we have a match against "option".
If template contains a %, we're done.
Else, compare against "value" too.
*/
c = strchr (template, '%');
if (c)
return TRUE;
if (!value)
return (template[ol] == 0);
if (strcmp (template + ol + 1, value))
return FALSE;
return TRUE;
}
static gboolean
parse_command_line_into_parameters_1 (const char *filename,
GList *parms,
const char *option,
const char *value,
parameter *parent)
{
GList *p;
parameter *match = 0;
gint which = -1;
gint index = 0;
for (p = parms; p; p = p->next)
{
parameter *pp = (parameter *) p->data;
which = -99;
if (pp->type == SELECT)
{
if (parse_command_line_into_parameters_1 (filename,
pp->options,
option, value,
pp))
{
which = -2;
match = pp;
}
}
else if (pp->arg)
{
if (compare_opts (option, value, (char *) pp->arg))
{
which = -1;
match = pp;
}
}
else if (pp->arg_set)
{
if (compare_opts (option, value, (char *) pp->arg_set))
{
which = 1;
match = pp;
}
}
else if (pp->arg_unset)
{
if (compare_opts (option, value, (char *) pp->arg_unset))
{
which = 0;
match = pp;
}
}
if (match)
break;
index++;
}
if (!match)
{
if (debug_p && !parent)
fprintf (stderr, "%s: WARNING: %s: no match for %s %s\n",
blurb(), filename, option, (value ? value : ""));
return FALSE;
}
switch (match->type)
{
case STRING:
case FILENAME:
case SLIDER:
case SPINBUTTON:
if (which != -1) abort();
parameter_set_switch (match, (gpointer) value);
break;
case BOOLEAN:
if (which != 0 && which != 1) abort();
parameter_set_switch (match, GINT_TO_POINTER(which));
break;
case SELECT_OPTION:
if (which != 1) abort();
parameter_set_switch (parent, GINT_TO_POINTER(index));
break;
default:
break;
}
return TRUE;
}
/* Set the parameter's value.
For STRING, FILENAME, SLIDER, and SPINBUTTON, `value' is a char*.
For BOOLEAN and SELECT, `value' is an int.
*/
static void
parameter_set_switch (parameter *p, gpointer value)
{
if (p->type == SELECT_OPTION) abort();
if (!p->widget) return;
switch (p->type)
{
case STRING:
case FILENAME:
{
gtk_entry_set_text (GTK_ENTRY (p->widget), (char *) value);
break;
}
case SLIDER:
case SPINBUTTON:
{
GtkAdjustment *adj =
(p->type == SLIDER
? gtk_range_get_adjustment (GTK_RANGE (p->widget))
: gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
float f;
char c;
if (1 == sscanf ((char *) value, "%f %c", &f, &c))
{
if (p->invert_p)
f = invert_range (GET_ADJ_LOWER(adj), GET_ADJ_UPPER(adj), f) - 1;
gtk_adjustment_set_value (adj, f);
}
break;
}
case BOOLEAN:
{
GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget);
gtk_toggle_button_set_active (b, GPOINTER_TO_INT(value));
break;
}
case SELECT:
{
gtk_option_menu_set_history (GTK_OPTION_MENU (p->widget),
GPOINTER_TO_INT(value));
break;
}
default:
abort();
}
}
static void
restore_defaults (const char *progname, GList *parms)
{
for (; parms; parms = parms->next)
{
parameter *p = (parameter *) parms->data;
if (!p->widget) continue;
switch (p->type)
{
case STRING:
case FILENAME:
{
gtk_entry_set_text (GTK_ENTRY (p->widget),
(p->string ? (char *) p->string : ""));
break;
}
case SLIDER:
case SPINBUTTON:
{
GtkAdjustment *adj =
(p->type == SLIDER
? gtk_range_get_adjustment (GTK_RANGE (p->widget))
: gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
float value = (p->invert_p
? invert_range (p->low, p->high, p->value)
: p->value);
gtk_adjustment_set_value (adj, value);
break;
}
case BOOLEAN:
{
/* A toggle button should be on by default if it inserts
nothing into the command line when on. E.g., it should
be on if `arg_set' is null.
*/
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (p->widget),
(!p->arg_set || !*p->arg_set));
break;
}
case SELECT:
{
GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget);
GList *opts;
int selected = 0;
int index;
for (opts = p->options, index = 0; opts;
opts = opts->next, index++)
{
parameter *s = (parameter *) opts->data;
/* The default menu item is the first one with
no `arg_set' field. */
if (!s->arg_set)
{
selected = index;
break;
}
}
gtk_option_menu_set_history (GTK_OPTION_MENU (opt), selected);
break;
}
default:
abort();
}
}
}
/* Documentation strings
*/
static char *
get_description (GList *parms, gboolean verbose_p)
{
parameter *doc = 0;
for (; parms; parms = parms->next)
{
parameter *p = (parameter *) parms->data;
if (p->type == DESCRIPTION)
{
doc = p;
break;
}
}
if (!doc || !doc->string)
return 0;
else
{
char *d = strdup ((char *) doc->string);
char *s;
char *p;
for (s = d; *s; s++)
if (s[0] == '\n')
{
if (s[1] == '\n') /* blank line: leave it */
s++;
else if (s[1] == ' ' || s[1] == '\t')
s++; /* next line is indented: leave newline */
else if (!strncmp(s+1, "http:", 5))
s++; /* next line begins a URL: leave newline */
else
s[0] = ' '; /* delete newline to un-fold this line */
}
/* strip off leading whitespace on first line only */
for (s = d; *s && (*s == ' ' || *s == '\t'); s++)
;
while (*s == '\n') /* strip leading newlines */
s++;
if (s != d)
memmove (d, s, strlen(s)+1);
/* strip off trailing whitespace and newlines */
{
int L = strlen(d);
while (L && isspace(d[L-1]))
d[--L] = 0;
}
/* strip off duplicated whitespaces */
for (s = d; *s; s++)
if (s[0] == ' ')
{
p = s+1;
while (*s == ' ')
s++;
if (*p && (s != p))
memmove (p, s, strlen(s)+1);
}
#if 0
if (verbose_p)
{
fprintf (stderr, "%s: text read is \"%s\"\n", blurb(),doc->string);
fprintf (stderr, "%s: description is \"%s\"\n", blurb(), d);
fprintf (stderr, "%s: translation is \"%s\"\n", blurb(), _(d));
}
#endif /* 0 */
return (d);
}
}
/* External interface.
*/
static conf_data *
load_configurator_1 (const char *program, const char *arguments,
gboolean verbose_p)
{
const char *dir = hack_configuration_path;
char *base_program;
int L = strlen (dir);
char *file;
char *s;
FILE *f;
conf_data *data;
if (L == 0) return 0;
base_program = strrchr(program, '/');
if (base_program) base_program++;
if (!base_program) base_program = (char *) program;
file = (char *) malloc (L + strlen (base_program) + 10);
data = (conf_data *) calloc (1, sizeof(*data));
strcpy (file, dir);
if (file[L-1] != '/')
file[L++] = '/';
strcpy (file+L, base_program);
for (s = file+L; *s; s++)
if (*s == '/' || *s == ' ')
*s = '_';
else if (isupper (*s))
*s = tolower (*s);
strcat (file+L, ".xml");
f = fopen (file, "r");
if (f)
{
int res, size = 1024;
char chars[1024];
xmlParserCtxtPtr ctxt;
xmlDocPtr doc = 0;
GtkWidget *vbox0;
GList *parms;
if (verbose_p)
fprintf (stderr, "%s: reading %s...\n", blurb(), file);
res = fread (chars, 1, 4, f);
if (res <= 0)
{
free (data);
data = 0;
goto DONE;
}
ctxt = xmlCreatePushParserCtxt(NULL, NULL, chars, res, file);
while ((res = fread(chars, 1, size, f)) > 0)
xmlParseChunk (ctxt, chars, res, 0);
xmlParseChunk (ctxt, chars, 0, 1);
doc = ctxt->myDoc;
xmlFreeParserCtxt (ctxt);
fclose (f);
/* Parsed the XML file. Now make some widgets. */
vbox0 = gtk_vbox_new (FALSE, 0);
gtk_widget_show (vbox0);
parms = make_parameters (file, doc->xmlRootNode, vbox0);
sanity_check_parameters (file, parms);
xmlFreeDoc (doc);
restore_defaults (program, parms);
if (arguments && *arguments)
parse_command_line_into_parameters (program, arguments, parms);
data->widget = vbox0;
data->parameters = parms;
data->description = get_description (parms, verbose_p);
}
else
{
parameter *p;
if (verbose_p)
fprintf (stderr, "%s: %s does not exist.\n", blurb(), file);
p = calloc (1, sizeof(*p));
p->type = COMMAND;
p->arg = (xmlChar *) strdup (arguments);
data->parameters = g_list_append (0, (gpointer) p);
}
data->progname = strdup (program);
DONE:
free (file);
return data;
}
static void
split_command_line (const char *full_command_line,
char **prog_ret, char **args_ret)
{
char *line = strdup (full_command_line);
char *prog;
char *args;
char *s;
prog = line;
s = line;
while (*s)
{
if (isspace (*s))
{
*s = 0;
s++;
while (isspace (*s)) s++;
break;
}
else if (*s == '=') /* if the leading word contains an "=", skip it. */
{
while (*s && !isspace (*s)) s++;
while (isspace (*s)) s++;
prog = s;
}
s++;
}
args = s;
*prog_ret = strdup (prog);
*args_ret = strdup (args);
free (line);
}
conf_data *
load_configurator (const char *full_command_line, gboolean verbose_p)
{
char *prog;
char *args;
conf_data *cd;
debug_p = verbose_p;
split_command_line (full_command_line, &prog, &args);
cd = load_configurator_1 (prog, args, verbose_p);
free (prog);
free (args);
return cd;
}
char *
get_configurator_command_line (conf_data *data, gboolean default_p)
{
char *args = parameters_to_cmd_line (data->parameters, default_p);
char *result = (char *) malloc (strlen (data->progname) +
strlen (args) + 2);
strcpy (result, data->progname);
strcat (result, " ");
strcat (result, args);
free (args);
return result;
}
void
set_configurator_command_line (conf_data *data, const char *full_command_line)
{
char *prog;
char *args;
split_command_line (full_command_line, &prog, &args);
if (data->progname) free (data->progname);
data->progname = prog;
restore_defaults (prog, data->parameters);
parse_command_line_into_parameters (prog, args, data->parameters);
free (args);
}
void
free_conf_data (conf_data *data)
{
if (data->parameters)
{
GList *rest;
for (rest = data->parameters; rest; rest = rest->next)
{
free_parameter ((parameter *) rest->data);
rest->data = 0;
}
g_list_free (data->parameters);
data->parameters = 0;
}
if (data->widget)
gtk_widget_destroy (data->widget);
if (data->progname)
free (data->progname);
if (data->description)
free (data->description);
memset (data, ~0, sizeof(*data));
free (data);
}
#endif /* HAVE_GTK && HAVE_XML -- whole file */