/*
 * Maketool - GTK-based front end for gmake
 * Copyright (c) 1999-2003 Greg Banks
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "maketool.h"
#include "ui.h"
#include "util.h"
#include "maketool_task.h"
#include "log.h"

CVSID("$Id: autoconf.c,v 1.16 2003/09/29 01:07:20 gnb Exp $");

#define strassign(x, s) \
    do { \
    	if ((x) != 0) \
	    g_free(x); \
	(x) = g_strdup(s); \
    } while(0)

#define strfree(x) \
    do { \
    	if ((x) != 0) \
	{ \
	    g_free(x); \
	    (x) = 0; \
	} \
    } while(0)

typedef enum
{
    CAT_NONE,
    CAT_CONFIG,
    CAT_DIRECTORY,
    CAT_HOST,
    CAT_FEATURE,
    
    NUM_CATEGORIES
}
category_t;

#define W_CHECK 0
#define W_TEXT 1

typedef enum
{
    ARG_NONE,
    ARG_FIXED,
    ARG_OPTIONAL
}
argtype_t;

#define OPT_ADVANCED	(1<<0)	    /* appears only in Advanced mode */
#define OPT_PRESENT	(1<<1)	    /* option has been set by user or config.status */
#define OPT_NOMETA	(1<<2)	    /* no metacharacters allowed in value */
#define OPT_NOSPACE	(1<<3)	    /* no whitespace allowed in value */
#define OPT_SILENT	(1<<4)	    /* no option_t created */

typedef struct
{
    char *name;
    category_t category;
    argtype_t argtype;
    char *metavar;
    char *help;
    char *defvalue;
    char *value;
    unsigned int flags;
    GtkWidget *hbox;
    GtkWidget *widgets[3];
}
option_t;

typedef struct
{
    const char *name;
    int value;
} benum_t;

static category_t category = CAT_NONE;
static estring optline = ESTRING_STATIC_INIT;
static const char *category_names[NUM_CATEGORIES] =
{
    "-none-",
    N_("Configuration"),
    N_("Directory and file names"),
    N_("Host type"),
    N_("Features and packages")
};
static GList *all_options;
static const benum_t initial_opt_flags[] = 
{
{"cache-file",	    	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"no-create",	    	    OPT_ADVANCED},
{"quiet,",  	    	    OPT_SILENT},
{"quiet",   	    	    OPT_ADVANCED},
{"prefix",     	    	    OPT_NOSPACE|OPT_NOMETA},
{"exec-prefix",     	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"bindir",  	    	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"sbindir", 	    	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"libexecdir",	    	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"datadir", 	    	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"sysconfdir",	    	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"sharedstatedir",  	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"localstatedir",   	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"libdir",  	    	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"includedir",	    	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"oldincludedir",   	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"infodir", 	    	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"mandir",  	    	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"srcdir",  	    	    OPT_ADVANCED|OPT_NOSPACE|OPT_NOMETA},
{"program-prefix",  	    OPT_ADVANCED|OPT_NOSPACE},
{"program-suffix",  	    OPT_ADVANCED|OPT_NOSPACE},
{"program-transform-name",  OPT_ADVANCED},
{"host",    	    	    OPT_ADVANCED|OPT_NOSPACE},
{"build",   	    	    OPT_ADVANCED|OPT_NOSPACE},
{"target",  	    	    OPT_ADVANCED|OPT_NOSPACE},
{"x-includes",	    	    OPT_ADVANCED},
{"x-libraries",     	    OPT_ADVANCED},
{"enable-maintainer-mode",  OPT_ADVANCED},
{"help",    	    	    OPT_SILENT},
{"version", 	    	    OPT_SILENT},
{"disable-FEATURE", 	    OPT_SILENT},
{"enable-FEATURE",  	    OPT_SILENT},
{"with-PACKAGE",    	    OPT_SILENT},
{"without-PACKAGE", 	    OPT_SILENT},
{0,0}
};

static const char *advanced_btn_names[] =
{
    N_("Advanced..."),
    N_("Basic..."),
};


#if DEBUG
static const benum_t opt_flag_desc[] = 
{
{"advanced",	OPT_ADVANCED},
{"present", 	OPT_PRESENT},
{"nometa",  	OPT_NOMETA},
{"nospace", 	OPT_NOSPACE},
{"silent",  	OPT_SILENT},
{0, 0}
};
#endif


static GtkWidget *autoconf_shell;
static GtkWidget *advanced_btn;
static GtkWidget *notebook;
static GtkWidget *category_pages[NUM_CATEGORIES];
static GtkWidget *preview_page;
static GtkWidget *preview_text;
static GtkTooltips *tooltips;
static gboolean advanced = FALSE;
static gboolean from_client;
static gboolean finished;
static long result;



/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

static int
benum_lookup_str(const benum_t *be, const char *str)
{
    for ( ; be->name != 0 ; be++)
    {
    	if (!strcmp(be->name, str))
	    return be->value;
    }
    return -1;
}

#if DEBUG
static char *
benum_describe_bits(const benum_t *be, int val)
{
    estring e;
    
    if (val == 0)
    	return g_strdup("0");

    estring_init(&e);
    
    /* find any matching benum entries and use them as description */
    for ( ; be->name != 0 ; be++)
    {
    	if ((val & be->value) == be->value)
	{
	    if (e.length > 0)
		estring_append_char(&e, ',');
	    estring_append_string(&e, be->name);
	    val &= ~be->value;
	}
    }

    /* describe any unnamed bits as numeric literals */
    if (val != 0)
    {
    	int i;
	
    	for (i=0 ; i<32 ; i++)
	{
	    if ((unsigned)val & (1<<i))
	    {
		if (e.length > 0)
		    estring_append_char(&e, ',');
		estring_append_printf(&e, "1<<%d", i);
	    }
	}
    }
    
    return e.data;
}
#endif

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

/*
 * Returns a new string containing the fully shell-escaped
 * configure command with all selected options.
 */
static char *
build_configure_command(void)
{
    GList *list;
    estring cmd;
        

    estring_init(&cmd);
    
    estring_append_string(&cmd, "./configure");
    
    for (list = all_options ; list != 0 ; list = list->next)
    {
    	option_t *opt = (option_t *)list->data;
	char *val = 0;
	
	if (!(opt->flags & OPT_PRESENT))
	    continue;

	estring_append_printf(&cmd, " --%s", opt->name);

	switch (opt->argtype)
	{
	case ARG_NONE:
	    val = 0;
	    break;
	case ARG_FIXED:
	    val = opt->value;
	    if (val == 0)
	    	val = "";
	    break;
	case ARG_OPTIONAL:
	    val = opt->value;
	    if (val != 0 && *val == '\0')
	    	val = 0;
	    break;
	}
	
	if (val != 0)
	{
	    val = strescape(val);
	    estring_append_printf(&cmd, "=%s", val);
	    g_free(val);
	}
    }
    
    return cmd.data;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/


static void
add_option(
    category_t cat,
    const char *name,
    argtype_t argtype,
    const char *metavar,
    const char *help,
    const char *def)
{
    option_t *opt;
    char *defval;
    int flags;
    
    flags = benum_lookup_str(initial_opt_flags, name);
    if (flags == -1)
    	flags = 0;  /* don't know about this one, should be OK */
    
    /* Handle some well-known meta-options and real options which are
     * not useful in the GUI, by pretending they don't exist.
     */
    if (flags & OPT_SILENT)
    	return;
	

    /* hack to make PREFIX and EPREFIX metavars in autoconf defaults work */
    if (def == 0)
    	defval = 0;
    else if (!strprefix(def, "PREFIX/"))
    	defval = g_strconcat("${prefix}", def+6, 0);
    else if (!strprefix(def, "EPREFIX/"))
    	defval = g_strconcat("${exec_prefix}", def+7, 0);
    else if (!strcmp(name, "exec-prefix"))
    	defval = g_strdup("${prefix}");
    else
    	defval = g_strdup(def);

    opt = g_new(option_t, 1);
    memset(opt, 0, sizeof(*opt));
    
    opt->name = g_strdup(name);
    opt->category = cat;
    opt->argtype = argtype;
    opt->metavar = g_strdup(metavar);
    opt->help = g_strdup(help);
    opt->defvalue = defval;
    opt->value = g_strdup(defval);
    opt->flags = flags;
    
    all_options = g_list_append(all_options, opt);
    
    /* Hack to present help better.  Works for LANG=en. */
    if (islower(opt->help[0]))
    	opt->help[0] = toupper(opt->help[0]);

#if DEBUG
    {
    	char *s;
	
	s = benum_describe_bits(opt_flag_desc, opt->flags);
	fprintf(stderr,
    	    "OPTION{\n  category=\"%s\"\n  name=\"%s\"\n  argtype=%d\n  flags=%s\n  metavar=\"%s\"\n  help=\"%s\"\n  default=\"%s\"\n}\n\n",
    	    category_names[opt->category],
	    opt->name,
	    opt->argtype,
    	    s,
	    opt->metavar,
	    opt->help,
	    opt->defvalue);
	g_free(s);
    }
#endif
}

    
static void
delete_option(option_t *opt)
{
    if (opt->hbox != 0)
    {
    	gtk_widget_hide(opt->hbox);
	gtk_widget_destroy(opt->hbox);
    }

    strfree(opt->name);
    strfree(opt->metavar);
    strfree(opt->help);
    strfree(opt->defvalue);
    strfree(opt->value);
    g_free(opt);
}


static option_t *
find_option(const char *name)
{
    GList *list;
    
    for (list = all_options ; list != 0 ; list = list->next)
    {
    	option_t *opt = (option_t *)list->data;
	
	if (!strcmp(opt->name, name))
	    return opt;
    }
    
    return 0;
}

static GList *
select_options(category_t cat)
{
    GList *res = 0;
    GList *list;
    
    for (list = all_options ; list != 0 ; list = list->next)
    {
    	option_t *opt = (option_t *)list->data;
	
	if (opt->category == cat)
	    res = g_list_append(res, opt);
    }
    
    return res;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

static void
parse_option_line(void)
{
    const char *x;
    estring opt, metavar, help, def;
    argtype_t argtype = ARG_NONE;

    estring_init(&opt);
    estring_init(&metavar);
    estring_init(&help);
    estring_init(&def);

    /* parse option name */
    for (x = optline.data ; *x && !isspace(*x) && *x != '=' && *x != '[' ; x++)
	estring_append_char(&opt, *x);
	
    if (*x == '=')
    {
    	/* parse meta argument */
	argtype = ARG_FIXED;
	
	for (x++ ; *x && !isspace(*x) ; x++)
	    estring_append_char(&metavar, *x);
    }
    else if (x[0] == '[' && x[1] == '=')
    {
    	/* parse meta argument */
	argtype = ARG_OPTIONAL;
	
	for (x+=2 ; *x && *x != ']' ; x++)
	    estring_append_char(&metavar, *x);
    }
    
    /* skip some spaces */
    for (x++ ; *x && isspace(*x) ; x++)
	;
	
    /* parse help */
    for ( ; *x && *x != '[' ; x++)
	estring_append_char(&help, *x);
    estring_chomp(&help);
        
    /* parse default */
    if (*x == '[')
    {
	for (x++ ; *x && *x != ']' ; x++)
	    estring_append_char(&def, *x);
    }
    

    add_option(category, opt.data, argtype, metavar.data, help.data, def.data);

    estring_free(&opt);
    estring_free(&metavar);
    estring_free(&help);
    estring_free(&def);
}

static void
configure_help_input(Task *task, int len, const char *line)
{
#if DEBUG
    fprintf(stderr, "==> \"%s\"\n", line);
#endif
    
    /* Skip known fluff */
    if (!strprefix(line, "Usage:") ||
        !strprefix(line, "Options:"))
    	return;
	
    
    /* handle option lines and their continuations */
    if (!strprefix(line, "  --"))
    {
    	/* parse previous line and its continuations */
    	if (optline.length > 0)
    	    parse_option_line();
	
	/* remember the new line in case there are continuations */
	estring_truncate(&optline);
	estring_append_string(&optline, line+4);

	/* trim trailing whitespace off new line */
	estring_chomp(&optline);
    }
    else if (isspace(line[0]) &&
    	     isspace(line[1]) &&
	     isspace(line[2]))
    {
    	/* continuation line */
	
	/* first skip leading whitespace -- crunch to single space */
	while (*line && isspace(*line))
	    line++;
	estring_append_char(&optline, ' ');
	
	/* add the rest of the list */
	estring_append_string(&optline, line);
    }
    else
    {
    	/* Categories etc */
    	if (optline.length > 0)
    	    parse_option_line();
	estring_truncate(&optline);

	/* Parse known categories */
	if (!strprefix(line, "Configuration:"))
	{
    	    category = CAT_CONFIG;
	}
	else if (!strprefix(line, "Directory and file names:"))
	{
    	    category = CAT_DIRECTORY;
	}
	else if (!strprefix(line, "Host type:"))
	{
    	    category = CAT_HOST;
	}
	else if (!strprefix(line, "Features and packages:"))
	{
    	    category = CAT_FEATURE;
	}
	else if (!strprefix(line, "--enable and --with options recognized:"))
	{
    	    category = CAT_FEATURE;
	}
    }
}

static void
configure_help_reap(Task *task)
{
    /* parse last option */
    if (optline.length > 0)
    	parse_option_line();
    estring_free(&optline);


#if DEBUG
    fprintf(stderr, "DONE\n");
#endif
}


static TaskOps configure_help_ops =
{
0,  	    	    	    	/* start */
configure_help_input,	    	/* input */
configure_help_reap, 	    	/* reap */
0   	    		    	/* destroy */
};



static void
read_configure_options(void)
{
    Task *task;

    /* Delete all previously read options */
    while (all_options != 0)
    {
    	delete_option((option_t *)all_options->data);
	all_options = g_list_remove_link(all_options, all_options);
    }
    
    task = task_create(
    	(Task *)g_new(Task, 1),
	g_strdup("./configure --help"),
	/*env*/0,
	&configure_help_ops,
	TASK_LINEMODE);
    if (!task_run(task))
    	task_unref(task);   /* drops last reference & deletes */
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/


static void
parse_config_status_line(const char *buf)
{
    estring name, value;
    int quote;
    option_t *opt;
    
    estring_init(&name);
    estring_init(&value);
    
    /* Skip leading characters */
    buf += 2;
    
    /* Skip configure script filename */
    for ( ; buf && !isspace(*buf) ; buf++)
    	;
	
    /* 
     * Parse arguments.
     */
    for (;;)
    {
	/* skip whitespace */
	for ( ; buf && isspace(*buf) ; buf++)
    	    ;
	if (*buf == '\0')
	    break;
	    
	/* handle quoted arguments */
	quote = (*buf == '\'' ? '\'' : 0);
	if (quote)
	    buf++;
	
	estring_truncate(&name);
	estring_truncate(&value);
	
	if (buf[0] != '-' || buf[1] != '-')
	{
	    fprintf(stderr, "ERROR: option \"%s\" does not begin with --\n", buf);
	    break;
	}
	buf += 2;
	
	/* parse option name */
	for ( ; *buf && (quote ? *buf != quote : !isspace(*buf)) && *buf != '=' ; buf++)
	    estring_append_char(&name, *buf);
	    
	if (*buf == '=')
	{
	    buf++;
	    /* parse option value */
	    for ( ; *buf && (quote ? *buf != quote : !isspace(*buf)) ; buf++)
		estring_append_char(&value, *buf);
	}
	
	if (quote && *buf == quote)
	    buf++;
	    
#if DEBUG
    	fprintf(stderr, "NAME=\"%s\" VALUE=\"%s\"\n", name.data, value.data);
#endif
	
	if ((opt = find_option(name.data)) == 0)
	{
	    fprintf(stderr, "ERROR: unknown option \"%s\"\n", name.data);
	    continue;
	}
	
	if (opt->value != 0)
	    g_free(opt->value);
	opt->value = g_strdup(value.data);
	opt->flags |= OPT_PRESENT;
    }
    
    estring_free(&name);
    estring_free(&value);
}


static void
parse_config_status(void)
{
    FILE *fp;
    char buf[1024];
    
    if ((fp = fopen("config.status", "r")) == 0)
    {
    	if (errno != ENOENT)
	    perror("config.status");
	return;
    }

    while (fgets(buf, sizeof(buf), fp) != 0)
    {
    	if (!strprefix(buf, "# ./configure") ||
	    !strprefix(buf, "# configure"))
	{
	    parse_config_status_line(buf);
	    break;
	}
    }    
    
    fclose(fp);
}


/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

static void
apply_advanced(void)
{
    GList *list;
    int cat;
    
    /* set visibility of each option */
    for (list = all_options ; list != 0 ; list = list->next)
    {
    	option_t *opt = (option_t *)list->data;
	
	ui_widget_set_visible(opt->hbox, (!(opt->flags & OPT_ADVANCED) || advanced));
	if (opt->widgets[W_TEXT] != 0 && GTK_WIDGET_VISIBLE(opt->widgets[W_TEXT]))
	    gtk_widget_set_sensitive(opt->widgets[W_TEXT], (opt->flags & OPT_PRESENT));
    }
    
    /* set visiblity of containing notebook pages */
    for (cat=1 ; cat<NUM_CATEGORIES ; cat++)
    {
	GtkWidget *sw = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook), cat-1);
	GtkWidget *page = category_pages[cat];

    	ui_widget_set_visible(sw,
	    (ui_container_num_visible_children(GTK_CONTAINER(page)) > 0));
    }
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

static void
update_preview(void)
{
    char *cmd = build_configure_command();

    gtk_text_freeze(GTK_TEXT(preview_text));
    gtk_text_set_point(GTK_TEXT(preview_text), 0);
    gtk_text_forward_delete(GTK_TEXT(preview_text),
    	    	    gtk_text_get_length(GTK_TEXT(preview_text)));
    gtk_text_insert(GTK_TEXT(preview_text), /*font*/0, /*fore*/0,
    	    	    	/*back*/0, cmd, strlen(cmd));
    gtk_text_thaw(GTK_TEXT(preview_text));
            
    g_free(cmd);
}


/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

static void
configure_start(Task *task)
{
    if (!from_client)
	log_start_build(task->command);
    else
    	log_add_line(task->command);
}

static void
configure_reap(Task *task)
{
    if (!from_client)
	log_end_build(task->command);
    finished = TRUE;
    result = task_is_successful(task) ? 0 : 1;
}

static TaskOps configure_ops =
{
configure_start,  	   	/* start */
handle_line,	    	    	/* input */
configure_reap, 	    	/* reap */
0   	    	    	    	/* destroy */
};

static Task *
configure_task(char *command)
{
    return task_create(
    	(Task *)g_new(Task, 1),
	command,
	prefs.var_environment,
	&configure_ops,
	TASK_LINEMODE);
}


/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

/* Hide the dialog window, and wait until it goes away */    

static void
hide_configure_window(void)
{
    gtk_widget_hide(autoconf_shell);
    while (g_main_pending())
    	g_main_iteration(/*may_block*/FALSE);
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

static void
autoconf_cancel_cb(GtkWidget *w, gpointer data)
{
/*    DPRINTF("autoconf_cancel_cb\n"); */
    
    hide_configure_window();
    finished = TRUE;
    result = 1;
}

static void
autoconf_advanced_cb(GtkWidget *w, gpointer data)
{
    
/*    DPRINTF("autoconf_advanced_cb\n"); */
    
    advanced = !advanced;
    gtk_label_set_text(GTK_LABEL(GTK_BIN(advanced_btn)->child),
    	    	    	  _(advanced_btn_names[advanced]));
    apply_advanced();
}


static void
autoconf_ok_cb(GtkWidget *w, gpointer data)
{
    
/*    DPRINTF("autoconf_ok_cb\n"); */

    hide_configure_window();

    task_spawn(configure_task(build_configure_command()));
}


/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/


static void
on_check_toggled(GtkWidget *w, gpointer data)
{
    option_t *opt = (option_t *)data;
        
/*    DPRINTF("on_check_toggled\n"); */

    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)))
	opt->flags |= OPT_PRESENT;
    else
	opt->flags &= ~OPT_PRESENT;

    if (opt->widgets[W_TEXT] != 0)
    {
    	gtk_widget_set_sensitive(opt->widgets[W_TEXT], opt->flags & OPT_PRESENT);
	if (opt->flags & OPT_PRESENT)
	{
	    gtk_widget_grab_focus(opt->widgets[W_TEXT]);
	    gtk_editable_select_region(GTK_EDITABLE(opt->widgets[W_TEXT]), 0, -1);
	}
    }
    
    update_preview();
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/


static void
on_text_changed(GtkWidget *w, gpointer data)
{
    option_t *opt = (option_t *)data;
    gboolean valid = TRUE;
    const char *newval = gtk_entry_get_text(GTK_ENTRY(w));
    const char *p;
        
/*    DPRINTF("on_text_changed\n"); */

    /* perform basic field validation */
    if (opt->flags & OPT_NOMETA)
    {
    	for (p = newval ; *p ; p++)
	{
	    if (ismetachar(*p))
	    {
		valid = FALSE;
		break;
	    }
	}
    }
    
    if (valid && (opt->flags & OPT_NOSPACE))
    {
    	for (p = newval ; *p ; p++)
	{
	    if (isspace(*p))
	    {
		valid = FALSE;
		break;
	    }
	}
    }
    
    if (!valid)
    {
	gtk_entry_set_text(GTK_ENTRY(w), opt->value);
	gdk_beep();
    	return;
    }
    
    /* valid change, so save the new value */
    strassign(opt->value, newval);
    
    update_preview();
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/


static void
create_option_widgets(void)
{
    int cat;
    GList *options;
    int row;
    
    for (cat = 1 ; cat <NUM_CATEGORIES ; cat++)
    {
	options = select_options(cat);
	if (options == 0)
    	    continue;



	row = 0;
	while (options != 0)
	{
	    GtkWidget *check;
	    GtkWidget *text;
    	    option_t *opt = (option_t *)options->data;

	    opt->hbox = gtk_hbox_new(/*homogenous*/FALSE, /*spacing*/SPACING);
	    gtk_box_pack_start(GTK_BOX(category_pages[cat]), opt->hbox,
    	    	    		/*expand*/FALSE, /*fill*/TRUE, /*padding*/0);
#if DEBUG
    	    gtk_widget_set_name(opt->hbox, opt->name);
#endif
	    gtk_widget_show(opt->hbox);

	    check = gtk_check_button_new_with_label(_(opt->help));
	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), opt->flags & OPT_PRESENT);
	    gtk_signal_connect(GTK_OBJECT(check), "toggled", 
    		GTK_SIGNAL_FUNC(on_check_toggled), opt);
	    gtk_tooltips_set_tip(tooltips, check, opt->name, 0);
	    gtk_label_set_line_wrap(GTK_LABEL(GTK_BIN(check)->child), TRUE);
	    gtk_box_pack_start(GTK_BOX(opt->hbox), check,
    	    	    		/*expand*/FALSE, /*fill*/TRUE, /*padding*/0);
	    gtk_widget_show(check);
	    opt->widgets[W_CHECK] = check;

    	    if (opt->argtype != ARG_NONE)
	    {
		text = gtk_entry_new();
		if (opt->value != 0)
		    gtk_entry_set_text(GTK_ENTRY(text), opt->value);
		gtk_signal_connect(GTK_OBJECT(text), "changed", 
    		    GTK_SIGNAL_FUNC(on_text_changed), opt);
		gtk_widget_set_sensitive(text, opt->flags & OPT_PRESENT);
		gtk_tooltips_set_tip(tooltips, text, opt->name, 0);
		gtk_box_pack_start(GTK_BOX(opt->hbox), text,
    	    	    		    /*expand*/TRUE, /*fill*/TRUE, /*padding*/0);
		gtk_widget_show(text);
		opt->widgets[W_TEXT] = text;
    	    }

	    options = g_list_remove_link(options, options);
	    row++;
	}

    }
    apply_advanced();
}

static const char preview_str[] = N_("\
The command line of the configure script will appear \
like this, according to the options you have chosen.\
");

static void
create_autoconf_shell(void)
{
    GtkWidget *button;
    GtkWidget *sw;
    GtkWidget *page;
    GtkWidget *label;
    int cat;
    
    autoconf_shell = ui_create_dialog(toplevel,  _("Maketool: Configure Options"));
    gtk_window_set_modal(GTK_WINDOW(autoconf_shell), TRUE);
    ui_set_help_tag(autoconf_shell, "configure-window");
    
/*    gtk_container_border_width(GTK_CONTAINER(autoconf_shell), SPACING); */
    
    /* TODO: centralize with ui.c */ 
    tooltips = gtk_tooltips_new();
 
    notebook = gtk_notebook_new();
    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(autoconf_shell)->vbox), notebook,
    	    	    	/*expand*/TRUE, /*fill*/TRUE, /*padding*/0);
			
    /* create a page for each category */
    for (cat = 1 ; cat <NUM_CATEGORIES ; cat++)
    {
	sw = gtk_scrolled_window_new(0, 0);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
    	    	    	    	    /*hscrollbar_policy*/GTK_POLICY_NEVER,
				    /*vscrollbar_policy*/GTK_POLICY_AUTOMATIC);
	gtk_container_border_width(GTK_CONTAINER(sw), SPACING);

	page = gtk_vbox_new(/*homogenous*/FALSE, /*spacing*/SPACING);
	gtk_container_border_width(GTK_CONTAINER(page), SPACING);
	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), page);

	gtk_widget_show(page);
	gtk_widget_show(sw);
	category_pages[cat] = page;

	label = gtk_label_new(_(category_names[cat]));
	gtk_widget_show(label);

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), sw, label);
    }

    
    /*
     * Create the preview page.
     */
    preview_page = gtk_vbox_new(/*homogenous*/FALSE, /*spacing*/SPACING);
    gtk_container_border_width(GTK_CONTAINER(preview_page), SPACING);
    
    label = gtk_label_new(_(preview_str));
    gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
    gtk_box_pack_start(GTK_BOX(preview_page), label,
    	    	    	/*expand*/FALSE, /*fill*/TRUE, /*padding*/0);
    gtk_widget_show(label);
    
    preview_text = gtk_text_new(0, 0);
    gtk_text_set_editable(GTK_TEXT(preview_text), FALSE);
    gtk_box_pack_start(GTK_BOX(preview_page), preview_text,
    	    	    	/*expand*/TRUE, /*fill*/TRUE, /*padding*/0);
    gtk_widget_show(preview_text);

    gtk_widget_show(preview_page);

    label = gtk_label_new(_("Preview"));
    gtk_widget_show(label);

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), preview_page, label);
    
    
    gtk_widget_show(notebook);
    

    /* 
     * Create the action area.
     */
    button = ui_dialog_create_button(autoconf_shell,
    	    	    	    _("OK"),
    	    	    	    autoconf_ok_cb, 0);
    gtk_window_set_default(GTK_WINDOW(autoconf_shell), button);
    advanced_btn = ui_dialog_create_button(autoconf_shell,
    	    	    	    _(advanced_btn_names[advanced]),
			    autoconf_advanced_cb, 0);
    ui_dialog_create_button(autoconf_shell,
    	    	    	    _("Cancel"),
			    autoconf_cancel_cb, 0);
}



/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

long
show_configure_window(gboolean fc)
{
    from_client = fc;
    
    read_configure_options();
    parse_config_status();
    if (autoconf_shell == 0)
    	create_autoconf_shell();
    create_option_widgets();
    update_preview();
    gtk_widget_show(autoconf_shell);
    
    if (from_client)
    {
    	/* block waiting for user to cancel or configure task to finish */
    	finished = FALSE;
    	do
	    gtk_main_iteration();
	while (!finished);
    }
    
    return result;
}

static void
build_configure_cb(GtkWidget *w, gpointer data)
{
    show_configure_window(/*from_client*/FALSE);
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

static gboolean
check_for_configure_in(void)
{
    return file_exists("configure.in");
}

static gboolean
check_for_configure(void)
{
    FILE *fp;
    gboolean gotmagic;
    int lines;
    char buf[1024];
    static const char magic[] = "# Generated automatically using autoconf ";
#define MAXLINES 20
    
    fp = fopen("configure", "r");
    if (fp == 0)
    	return FALSE;
	
    /*
     * Some bizarre packages include a "configure" script which
     * is *not* autoconf-generated, so for our purposes (e.g.
     * parsing the output of "configure --help") it doesn't exist.
     */
    /* First, check its some kind of script */
    if (fgetc(fp) != '#' || fgetc(fp) != '!')
    {
    	fclose(fp);
    	return FALSE;
    }
    fgets(buf, sizeof(buf), fp);    /* skip to end of line */
    
    /* Second, check for the magic string */
    gotmagic = FALSE;
    for (lines = 0 ;
    	 lines < MAXLINES && fgets(buf, sizeof(buf), fp) != 0 ;
	 lines++)
    {
    	if (!strprefix(buf, magic))
	{
	    gotmagic = TRUE;
	    break;
	}
    }

    fclose(fp);
    return gotmagic;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
/*
 * MakeSystem for autoconf projects in distribution mode,
 * i.e. the author has not distributed enough of the autoconf
 * machinery in the source tarball to enable users to make
 * changes to the autoconf machinery, in the expectation
 * that the user will simply run the existing configure script.
 * It's not supposed to be done that way, but it happens.
 */

static gboolean
ac_dist_probe(void)
{
    return (check_for_configure());
}

static const char * const ac_dist_deps[] = 
{
    "Makefile.in",
    "config.status",
    "configure",
    0
};

static const MakeCommand ac_dist_commands[] = 
{
    {N_("_Remove config.cache"), "/bin/rm -f config.cache"},
    {N_("Run _configure..."), 0, build_configure_cb},
    {N_("Run config._status"), "./config.status"},
    {0}
};

const MakeSystem makesys_ac_dist = 
{
    "autoconf-dist",	    	    	    /* name */
    N_("GNU autoconf (distribution only)"), /* label */
    ac_dist_probe,  	    	    	    /* probe */
    0,	    	    	    	    	    /* parent */
    TRUE,  	    	    	    	    /* automatic */
    "Makefile",     	    	    	    /* makefile */
    ac_dist_deps,   	    	    	    /* makefile_deps */
    0,	    	    	    	    	    /* standard_targets */
    ac_dist_commands	    	    	    /* commands */
};

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
/*
 * MakeSystem for autoconf projects in maintainer mode,
 * i.e. the full ability to futz with autoconf stuff is
 * expected.
 */
 
static gboolean
ac_maint_probe(void)
{
    return (check_for_configure_in());
}

static const char * const ac_maint_deps[] = 
{
    "configure.in",
    0
};

static const MakeCommand ac_maint_commands[] = 
{
    {N_("Run a_utoconf"), "autoconf"},
    {0}
};

const MakeSystem makesys_ac_maint = 
{
    "autoconf-maint",	    	    	    /* name */
    N_("GNU autoconf"),     	    	    /* label */
    ac_maint_probe, 	    	    	    /* probe */
    &makesys_ac_dist,	    	    	    /* parent */
    TRUE,  	    	    	    	    /* automatic */
    "Makefile",     	    	    	    /* makefile */
    ac_maint_deps,  	    	    	    /* makefile_deps */
    0,	    	    	    	    	    /* standard_targets */
    ac_maint_commands	    	    	    /* commands */
};

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
/*
 * MakeSystem for automake projects.  Note that we don't
 * distinguish between distribution and maintainer modes
 * and assume that if the Makefile.am file is distributed
 * then all the rest of the autoconf machinery is available.
 */

static gboolean
am_probe(void)
{
    return (file_exists("Makefile.am"));
}

static const char * const am_deps[] = 
{
    "Makefile.am",
    0
};

static const MakeCommand am_commands[] = 
{
    {N_("Run auto_make"), "automake -a"},
    {0}
};

const MakeSystem makesys_am = 
{
    "automake",	    	/* name */
    N_("GNU automake"),	/* label */
    am_probe, 	    	/* probe */
    &makesys_ac_maint,	/* parent */
    TRUE,  	    	/* automatic */
    "Makefile",     	/* makefile */
    am_deps,  	    	/* makefile_deps */
    0,	    	    	/* standard_targets */
    am_commands	    	/* commands */
};

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
/*END*/


syntax highlighted by Code2HTML, v. 0.9.1