/* 
 *  gretl -- Gnu Regression, Econometrics and Time-series Library
 *  Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
 * 
 *  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 3 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, see <http://www.gnu.org/licenses/>.
 * 
 */

/* selector.c for gretl */

#include "gretl.h"
#include "selector.h"
#include "dlgutils.h"
#include "menustate.h"
#include "fileselect.h"
#include "fnsave.h"
#include "treeutils.h"
#include "toolbar.h"
#include "winstack.h"
#include "tabwin.h"
#include "lagpref.h"

#include "var.h"
#include "gretl_func.h"
#include "libset.h"
#include "uservar.h"
#include "johansen.h"
#include "gretl_bfgs.h"

/* for graphical selector buttons */
#include "arrows.h"

#if 0
# include "bootstrap.h"
#endif

#define LDEBUG 0
#define VLDEBUG 0

enum {
    SR_LVARS  = 1,
    SR_RVARS1,
    SR_RVARS2
};

#define N_EXTRA  8

struct _selector {
    GtkWidget *dlg;
    GtkWidget *vbox;
    GtkWidget *action_area;
    GtkWidget *lvars;
    GtkWidget *table;
    GtkWidget *depvar;
    GtkWidget *rvars1;
    GtkWidget *rvars2;
    GtkWidget *default_check;
    GtkWidget *add_button;
    GtkWidget *remove_button;
    GtkWidget *lags_button;
    GtkWidget *hess_button;
    GtkWidget *x12a_button;
    GtkWidget *xdiff_button;
    GtkWidget *hccme_button;
    GtkWidget *extra[N_EXTRA];
    int ci;
    int blocking;
    int active_var;
    int error;
    int n_left;
    int state_pushed;
    int row;
    int n_rows;
    gretlopt opts;
    char *cmdlist;
    gpointer data;
    int (*callback)();
};

enum {
    COL_ID = 0,
    COL_LAG,
    COL_NAME,
    COL_FLAG
};

#define EXTRA_LAGS (N_EXTRA - 1)

#define VNAME_WIDTH 18
#define BUTTON_WIDTH 64

/* single-equation estimation commands plus some GUI extensions */
#define MODEL_CODE(c) (MODEL_COMMAND(c) || c == PANEL_WLS || c == PANEL_B || \
                       c == OLOGIT || c == OPROBIT || c == REPROBIT || \
		       c == MLOGIT || c == IV_LIML || c == IV_GMM || \
		       c == COUNTMOD)

#define IV_MODEL(c) (c == IVREG || c == IV_LIML || c == IV_GMM)

#define NONPARAM_CODE(c) (c == LOESS || c == NADARWAT)

#define COINT_CODE(c) (c == COINT || c == COINT2)

#define VEC_CODE(c) (c == COINT || c == COINT2 || c == VAR || \
                     c == VECM || c == VLAGSEL)

#define VEC_MODEL_CODE(c) (c == VAR || c == VECM || c == VLAGSEL)

#define VECLAGS_CODE(c) (c == VAR || c == VECM)

#define ADDVAR_CODE(c) (c == LOGS || c == LAGS || c == SQUARE || \
                        c == DIFF || c == LDIFF)

#define TWO_VARS_CODE(c) (c == SPEARMAN || c == ELLIPSE || c == XCORRGM || \
	                  c == QQPLOT)

#define THREE_VARS_CODE(c) (c == GR_DUMMY || c == GR_XYZ || \
			    c == GR_3D || c == ANOVA)

#define FNPKG_CODE(c) (c == SAVE_FUNCTIONS || c == EDIT_FUNCTIONS)

#define SHOW_LISTS_CODE(c) (c == SUMMARY || c == CORR || c == MAHAL || c == PCA)

#define WANT_TOGGLES(c) (c == DPANEL || \
                         c == ARMA || \
                         c == COINT || \
                         c == COINT2 || \
			 c == CORR || \
                         c == GARCH || \
                         c == HECKIT || \
                         c == BIPROBIT || \
                         c == INTREG || \
                         c == IVREG || \
                         c == LOGIT || \
                         c == OLOGIT || \
                         c == MLOGIT || \
                         c == MPOLS || \
                         c == OLS || \
                         c == PANEL || \
                         c == PANEL_WLS || \
                         c == PANEL_B || \
			 c == COUNTMOD || \
			 c == DURATION || \
                         c == PROBIT || \
                         c == OPROBIT || \
			 c == REPROBIT || \
	                 c == QUANTREG || \
			 c == SPEARMAN || \
                         c == TOBIT || \
                         c == VAR || \
                         c == VECM || \
                         c == VLAGSEL || \
                         c == WLS || \
                         c == XTAB)

#define USE_VECXLIST(c) (c == VAR || c == VLAGSEL || c == VECM || \
                         c == COINT2)

#define USE_RXLIST(c) (c == VECM || c == COINT2)

#define AUX_LAST(c) (c == IVREG || \
                     c == IV_LIML || \
                     c == IV_GMM || \
                     c == HECKIT || \
                     c == BIPROBIT || \
                     c == VAR || \
                     c == VLAGSEL || \
                     c == VECM || \
                     c == COINT2 || \
                     c == SAVE_FUNCTIONS || \
		     c == EDIT_FUNCTIONS)

#define USE_ZLIST(c) (c == IVREG || c == IV_LIML || c == IV_GMM || \
                      c == HECKIT || c == BIPROBIT)

#define RHS_PREFILL(c) (c == CORR || \
	                c == MAHAL || \
			c == PCA || \
                	c == SUMMARY || \
			c == XTAB)

#define dataset_lags_ok(d) ((d)->structure == TIME_SERIES || \
			    (d)->structure == SPECIAL_TIME_SERIES || \
                            (d)->structure == STACKED_TIME_SERIES)

#define select_lags_primary(c) (MODEL_CODE(c))

#define select_lags_depvar(c) (MODEL_CODE(c) && c != ARMA && \
			       c != DPANEL)

/* Should we have a lags button associated with auxiliary
   variable selector? */

#define select_lags_aux(c) (c == VAR || c == VLAGSEL || c == VECM || \
                            c == IVREG || c == IV_LIML || c == IV_GMM || \
                            c == HECKIT || c == BIPROBIT)

#define list_lag_special(i) (i < -1)

/* static state variables */

static int default_y = -1;
static int want_seasonals = 0;
static int default_order;
static int vartrend = 0;
static int varconst = 1;
static int lags_hidden;
static int arma_p = 1;
static int arma_P = 0;
static int arima_d = 0;
static int arima_D = 0;
static int arma_q = 1;
static int arma_Q = 0;
static int garch_p = 1;
static int garch_q = 1;
static int arma_const = 1;
static int garch_const = 1;
static int arma_x12 = 0;
static int arma_hessian = 1;
static int arima_xdiff = 1;
static int selvar;
static int y2var;
static int offvar;
static int censvar;
static int jrank = 1;
static int jcase = J_UNREST_CONST;
static int verbose;
static int lovar;
static int hivar;
static int wtvar;
static int np_xvar;
static char lp_pvals;

static char dpd_2step;
static char dpd_asy;
static char dpd_p;

static int y_x_lags_enabled;
static int y_w_lags_enabled;

static int *xlist;
static int *instlist;
static int *veclist;
static int *vecxlist;

static char *arlags;
static char *malags;
static char cluster_var[VNAMELEN];

static double tobit_lo = 0;
static double tobit_hi = NADBL;

static gretlopt model_opt;

static GtkWidget *multiplot_label;
static GtkWidget *multiplot_menu;

static selector *open_selector;

static gint listvar_special_click (GtkWidget *widget, GdkEventButton *event, 
				   gpointer data);
static gint lvars_right_click (GtkWidget *widget, GdkEventButton *event, 
			       selector *sr);
static gint listvar_flagcol_click (GtkWidget *widget, GdkEventButton *event, 
				   gpointer data);
static int list_show_var (int v, int ci, int show_lags);
static void functions_list (selector *sr);
static void primary_rhs_varlist (selector *sr);
static gboolean lags_dialog_driver (GtkWidget *w, selector *sr);
static void call_iters_dialog (GtkWidget *w, GtkWidget *combo);
static void reset_arma_spinners (selector *sr);

static int want_combo (selector *sr)
{
    return (sr->ci == ARMA ||
	    sr->ci == VECM || 
	    sr->ci == COINT ||
	    sr->ci == COINT2 ||
	    sr->ci == COUNTMOD ||
	    sr->ci == DURATION || 
	    sr->ci == IV_GMM ||
	    sr->ci == EXPORT);
}

static int want_radios (selector *sr)
{
    int c = sr->ci;
    int ret = 0;

    if (c == PANEL || c == SCATTERS || c == AR1 ||
	c == LOGIT || c == PROBIT || c == HECKIT ||
	c == XTAB || c == SPEARMAN || c == PCA ||
	c == QUANTREG || c == DPANEL || 
	c == LOGISTIC || c == VAROMIT) {
	ret = 1;
    } else if (c == ADD || c == OMIT) {
	windata_t *vwin = (windata_t *) sr->data;
	MODEL *pmod = (MODEL *) vwin->data;

	if (c == OMIT && pmod->ci == HECKIT) {
	    /* omit: Wald test only */
	    sr->opts |= OPT_W;
	} else if (c == ADD && pmod->ci != OLS) {
	    /* add: LM option is OLS-only */
	    ret = 0;
	} else {
	    ret = 1;
	}
    } 

    return ret;
}

static void selector_set_blocking (selector *sr, int modal)
{
    if (modal) {
	gretl_set_window_modal(sr->dlg);
    }
    sr->blocking = 1;
    gtk_main();
}

static int selection_at_max (selector *sr, int nsel)
{
    return (TWO_VARS_CODE(sr->ci) && nsel == 2);
}

static int sr_get_lag_context (selector *sr, int locus)
{
    int c = 0;

    if (sr == NULL || !dataset_lags_ok(dataset)) {
	return 0;
    }

    if (locus == SR_RVARS1 && select_lags_primary(sr->ci)) {
	c = LAG_X;
    } else if (locus == SR_RVARS2 && select_lags_aux(sr->ci)) {
	c = (USE_ZLIST(sr->ci))? LAG_W : LAG_X;
    }

    return c;
}

static int lag_context_from_widget (GtkWidget *w)
{
    gint locus = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "locus"));
    selector *sr = g_object_get_data(G_OBJECT(w), "sr");
    int context = 0;

    if (sr != NULL && locus) {
	context = sr_get_lag_context(sr, locus);
    }

    return context;
}

static int lags_button_relevant (selector *sr, int locus)
{
    if (sr->lags_button != NULL) {
	if (locus == SR_RVARS1 && select_lags_primary(sr->ci)) {
	    return 1;
	} else if (locus == SR_RVARS2 && select_lags_aux(sr->ci)) {
	    return 1;
	}
    }

    return 0;
}

void enable_lags_for_context (int context, gboolean s)
{
    if (context == LAG_Y_X) {
	y_x_lags_enabled = s;
    } else if (context == LAG_Y_W) {
	y_w_lags_enabled = s;
    }
}

gboolean lags_enabled_for_context (int context)
{
    if (context == LAG_Y_X) {
	return y_x_lags_enabled;
    } else if (context == LAG_Y_W) {
	return y_w_lags_enabled;
    } else {
	return TRUE;
    }
}

void clear_selector (void)
{
    default_y = -1;
    default_order = 0;
    selvar = 0;
    y2var = 0;
    offvar = 0;
    censvar = 0;
    wtvar = 0;
    np_xvar = 0;
    vartrend = 0;
    varconst = 1;
    lovar = hivar = 0;

    dpd_asy = dpd_2step = 0;
    dpd_p = 1;

    arma_p = 1;
    arima_d = 0;
    arma_q = 1;
    arma_P = 0;
    arima_D = 0;
    arma_Q = 0;
    arma_const = 1;
    arma_hessian = 1;

    garch_p = 1;
    garch_q = 1;
    garch_const = 1;

    jrank = 1;
    jcase = J_UNREST_CONST;

    free(xlist);
    xlist = NULL;
    free(instlist);
    instlist = NULL;

    free(veclist);
    veclist = NULL;
    free(vecxlist);
    vecxlist = NULL;

    free(arlags);
    arlags = NULL;
    free(malags);
    malags = NULL;

    *cluster_var = '\0';

    tobit_lo = 0;
    tobit_hi = NADBL;

    lp_pvals = 0;

    destroy_lag_preferences();
    call_iters_dialog(NULL, NULL);

    if (open_selector != NULL) {
	selector *sr = open_selector;

	if (sr->ci == ARMA) {
	    reset_arma_spinners(sr);
	}
    }
}

GtkWidget *selector_get_window (const selector *sr)
{
    return sr->dlg;
}

static int presel;

void selector_set_varnum (int v)
{
    presel = v;
}

void modelspec_dialog (int ci)
{
    selection_dialog(ci, _("gretl: specify model"), do_model);
}

static int varnum_from_keystring (MODEL *pmod, const char *key)
{
    char *s = (char *) gretl_model_get_data(pmod, key);
    int v = 0;

    if (s != NULL && *s != '\0') {
	v = series_index(dataset, s);
	if (v == dataset->v) {
	    v = 0;
	}
    }

    return v;
}

static int *lag_list_from_mask (char *mask, int k)
{
    int *list;
    int i, j;

    list = gretl_list_new(k);

    if (list != NULL) {
	j = 1;
	for (i=0; mask[i]; i++) {
	    if (mask[i] == '1') {
		list[j++] = i + 1;
	    } else {
		list[0] -= 1;
	    }
	}
    }

    return list;
}

static void retrieve_arma_info (MODEL *pmod)
{
    int acode = gretl_model_get_int(pmod, "arma_flags");
    int *laglist;
    char *mask;

    if (!(acode & ARMA_EXACT)) {
	model_opt |= OPT_C;
    } 

    if (acode & ARMA_X12A) {
	arma_x12 = 1;
    }    

    if (pmod->opt & OPT_G) {
	/* use OPG for covariance matrix */
	arma_hessian = 0;
    }

    if (pmod->opt & OPT_Y) {
	/* don't difference ARIMAX regressors */
	arima_xdiff = 0;
    }

    if (pmod->opt & OPT_L) {
	model_opt |= OPT_L;
    }

    arma_p = arma_model_nonseasonal_AR_order(pmod);
    arma_q = arma_model_nonseasonal_MA_order(pmod);
    arima_d = gretl_model_get_int(pmod, "arima_d");
    arma_P = gretl_model_get_int(pmod, "arma_P");
    arma_Q = gretl_model_get_int(pmod, "arma_Q");
    arima_D = gretl_model_get_int(pmod, "arima_D");
    arma_const = pmod->ifc;

    free(arlags);
    arlags = NULL;

    if (arma_p > 0) {
	mask = (char *) gretl_model_get_data(pmod, "pmask");
	if (mask != NULL) {
	    laglist = lag_list_from_mask(mask, arma_p);
	    if (laglist != NULL) {
		arlags = gretl_list_to_string(laglist);
		free(laglist);
	    }
	}
    }

    free(malags);
    malags = NULL;

    if (arma_q > 0) {
	mask = (char *) gretl_model_get_data(pmod, "qmask");
	if (mask != NULL) {
	    laglist = lag_list_from_mask(mask, arma_q);
	    if (laglist != NULL) {
		free(malags);
		malags = gretl_list_to_string(laglist);
		free(laglist);
	    }
	} 
    }   
}

static void retrieve_AR_lags_info (MODEL *pmod)
{
    free(arlags);
    arlags = NULL;

    if (pmod->arinfo != NULL && pmod->arinfo->arlist != NULL) {
	arlags = gretl_list_to_string(pmod->arinfo->arlist);
    }
}

static void retrieve_heckit_info (MODEL *pmod, int *gotinst)
{
    int *zlist = gretl_model_get_secondary_list(pmod);

    if (zlist != NULL) {
	selvar = zlist[1];
	free(instlist);
	instlist = gretl_list_copy_from_pos(zlist, 2);
	if (instlist != NULL) {
	    *gotinst = 1;
	}
	free(zlist);
    }

    if (pmod->opt & OPT_T) {
	/* two-step estimation */
	model_opt |= OPT_T;
    }    
}

static void retrieve_biprobit_info (MODEL *pmod, int *gotinst)
{
    /* the biprobit list takes the form:
       y1 y2 x1 ; x2
    */
    if (pmod->list != NULL && pmod->list[0] > 2) {
	y2var = pmod->list[2];
    }
    free(instlist);
    instlist = gretl_model_get_secondary_list(pmod);
    *gotinst = instlist != NULL;
}

static void retrieve_tobit_info (MODEL *pmod)
{
    double x0 = gretl_model_get_double(pmod, "llimit");
    double x1 = gretl_model_get_double(pmod, "rlimit");

    if (na(x0) && na(x1)) {
	/* the standard set-up */
	tobit_lo = 0;
	tobit_hi = NADBL;
    } else {
	/* user-specified limits */
	tobit_lo = x0;
	tobit_hi = x1;
    } 
}	

/* support for the "Modify model..." Edit menu item */

void selector_from_model (windata_t *vwin)
{
    void *ptr = vwin->data;
    int ci = vwin->role;

    model_opt = OPT_NONE;

    if (ci == ARBOND) {
	/* handle old session models */
	ci = DPANEL;
    }

    if (ci == VIEW_MODEL) {
	/* single-equation model (mostly) */
	MODEL *pmod = (MODEL *) ptr;
	int sel_ci = pmod->ci;
	int cv, dv = -1, gotinst = 0;

	if (pmod->ci == NLS || pmod->ci == MLE || pmod->ci == GMM) {
	    revise_nl_model(pmod, vwin_toplevel(vwin));
	    return;
	}

	if (pmod->ci == INTREG) {
	    lovar = varnum_from_keystring(pmod, "lovar");
	    hivar = varnum_from_keystring(pmod, "hivar");
	} else {
	    dv = gretl_model_get_depvar(pmod);
	    if (dv >= 0 && dv < dataset->v) {
		default_y = dv;
	    }
	}

	free(xlist);
	xlist = gretl_model_get_x_list(pmod);

	if (pmod->ci == WLS) {
	    wtvar = pmod->nwt;
	} else if (pmod->ci == ARMA) {
	    retrieve_arma_info(pmod);
	} else if (pmod->ci == GARCH) {
	    garch_p = pmod->list[1];
	    garch_q = pmod->list[2];
	    garch_const = pmod->ifc;
	} else if (pmod->ci == HECKIT) {
	    retrieve_heckit_info(pmod, &gotinst);
	} else if (COUNT_MODEL(pmod->ci)) {
	    if (pmod->ci == NEGBIN) {
		if (pmod->opt & OPT_M) {
		    model_opt |= OPT_M;
		} else {
		    model_opt |= OPT_N;
		}
	    }
	    sel_ci = COUNTMOD;
	    offvar = gretl_model_get_int(pmod, "offset_var");
	} else if (pmod->ci == DURATION) {
	    if (pmod->opt & OPT_E) {
		model_opt |= OPT_E;
	    } else if (pmod->opt & OPT_L) {
		model_opt |= OPT_L;
	    } else if (pmod->opt & OPT_Z) {
		model_opt |= OPT_Z;
	    } 
	    censvar = gretl_model_get_int(pmod, "cens_var");
	} else if (pmod->ci == IVREG) {
	    free(instlist);
	    instlist = gretl_model_get_secondary_list(pmod);
	    if (instlist != NULL) {
		gotinst = 1;
	    }
	    if (pmod->opt & OPT_L) {
		sel_ci = IV_LIML;
	    } else if (pmod->opt & OPT_G) {
		sel_ci = IV_GMM;
	    }
	} else if (pmod->ci == ARCH) {
	    default_order = gretl_model_get_int(pmod, "arch_order");
	} else if (pmod->ci == AR) {
	    retrieve_AR_lags_info(pmod);
	} else if (pmod->ci == AR1) {
	    if (pmod->opt & OPT_P) {
		model_opt |= OPT_P;
	    } else if (pmod->opt & OPT_H) {
		model_opt |= OPT_H;
	    }
	} else if (pmod->ci == PANEL) {
	    if (pmod->opt & OPT_F) {
		model_opt |= OPT_F;
	    } else if (pmod->opt & OPT_U) {
		model_opt |= OPT_U;
	    } else if (pmod->opt & OPT_W) {
		sel_ci = PANEL_WLS;
	    } else if (pmod->opt & OPT_B) {
		sel_ci = PANEL_B;
	    }
	    if (pmod->opt & OPT_D) {
		model_opt |= OPT_D;
	    }
	} else if (pmod->ci == DPANEL) {
	    if (pmod->opt & OPT_A) {
		model_opt |= OPT_A;
	    }
	    if (pmod->opt & OPT_D) {
		model_opt |= OPT_D;
	    }
	    if (pmod->opt & OPT_T) {
		model_opt |= OPT_T;
	    }
	    if (pmod->opt & OPT_L) {
		model_opt |= OPT_L;
	    }
	    if (pmod->opt & OPT_X) {
		model_opt |= OPT_X;
	    }
	} else if (pmod->ci == LAD) {
	    if (gretl_model_get_int(pmod, "rq")) {
		sel_ci = QUANTREG;
	    }
	    /* FIXME replicate rq_tauvec? */
	} else if (pmod->ci == LOGIT) {
	    if (gretl_model_get_int(pmod, "ordered")) {
		sel_ci = OLOGIT;
	    } else if (gretl_model_get_int(pmod, "multinom")) {
		sel_ci = MLOGIT;
	    }
	} else if (pmod->ci == PROBIT) {
	    if (gretl_model_get_int(pmod, "ordered")) {
		sel_ci = OPROBIT;
	    } else if (pmod->opt & OPT_E) {
		sel_ci = REPROBIT;
	    }
	} else if (pmod->ci == TOBIT) {
	    retrieve_tobit_info(pmod);
	} else if (pmod->ci == BIPROBIT) {
	    retrieve_biprobit_info(pmod, &gotinst);
	}

	if (pmod->opt & OPT_R) {
	    model_opt |= OPT_R;
	}

	*cluster_var = '\0';
	cv = gretl_model_get_cluster_var(pmod);
	if (cv > 0 && cv < dataset->v) {
	    model_opt |= OPT_C;
	    strcpy(cluster_var, dataset->varname[cv]);
	}  	    

	y_x_lags_enabled = y_w_lags_enabled = 0;

	if ((dataset_is_time_series(dataset) || 
	     dataset_is_panel(dataset)) && 
	    (xlist != NULL || gotinst)) {
	    set_lag_prefs_from_model(dv, xlist, instlist);
	}

	modelspec_dialog(sel_ci);
    } else if (ci == VAR || ci == VECM) {
	GRETL_VAR *var = (GRETL_VAR *) ptr;

	varconst = (var->detflags & DET_CONST);
	vartrend = (var->detflags & DET_TREND);
	want_seasonals = (var->detflags & DET_SEAS);

	if (var->robust) {
	    model_opt |= OPT_R;
	}

	free(veclist);
	veclist = gretl_list_copy(var->ylist);

	free(vecxlist);
	if (var->rlist != NULL) {
	    vecxlist = gretl_lists_join_with_separator(var->xlist,
						       var->rlist);
	} else {
	    vecxlist = gretl_list_copy(var->xlist);
	}

	set_lag_prefs_from_VAR(var->lags, vecxlist);

	default_order = var->order;

	if (ci == VECM) {
	    jrank = gretl_VECM_rank(var);
	    jcase = jcode(var);
	    default_order += 1;
	}

	selection_dialog(ci, (ci == VAR)? _("gretl: VAR") : _("gretl: VECM"),
			 do_vector_model);
    } else if (ci == SYSTEM) {
	revise_system_model(ptr, vwin_toplevel(vwin));
    }

    model_opt = OPT_NONE;
}

#define UNRESTRICTED N_("U")
#define RESTRICTED   N_("R")

static char varflag[8];

static void set_varflag (const char *s)
{
    *varflag = '\0';
    strncat(varflag, s, 7);
}

int selector_get_depvar_number (const selector *sr)
{
    int ynum = -1;

    if (sr == NULL) {
	sr = open_selector;
    }

    if (sr != NULL && sr->depvar != NULL) {
	const char *s = gtk_entry_get_text(GTK_ENTRY(sr->depvar));

	if (s != NULL && *s != '\0') {
	    ynum = series_index(dataset, s);
	    if (ynum == dataset->v) {
		ynum = -1;
	    }
	}
    }

    return ynum;
}

static gint dblclick_lvars_row (GtkWidget *w, GdkEventButton *event, 
				selector *sr); 

/* when adding a lag to the exogenous vars box for a VECM,
   see if we can find a previously added lag (or non-lag) of
   this variable, and if so use that to set the Restricted/
   Unrestricted flag on the newly added lag
*/

static void set_varflag_from_parent (int v, int lag, GtkTreeModel *mod, 
				     GtkTreeIter *iter)
{
    GtkTreeIter piter;
    gchar *flag;
    gint pv, plag;
    int done = 0;

    if (gtk_tree_model_get_iter_first(mod, &piter)) {
	while (1) {
	    gtk_tree_model_get(mod, &piter, COL_ID, &pv, COL_LAG, &plag, -1);
	    if (pv == v && plag != lag) {
		gtk_tree_model_get(mod, &piter, COL_FLAG, &flag, -1);
		gtk_list_store_set(GTK_LIST_STORE(mod), iter, COL_FLAG, flag, -1);
		done = 1;
		g_free(flag);
		break;
		
	    }
	    if (!gtk_tree_model_iter_next(mod, &piter)) {  
		break;
	    }
	}
    }

    if (!done) {
	gtk_list_store_set(GTK_LIST_STORE(mod), iter, COL_FLAG, varflag, -1);
    }
}

static void 
real_varlist_set_var (int v, int lag, GtkTreeModel *mod, GtkTreeIter *iter)
{
    int ncols = gtk_tree_model_get_n_columns(mod);
    GtkListStore *store = GTK_LIST_STORE(mod);

    if (lag == 0) {
	gtk_list_store_set(store, iter, COL_ID, v, COL_LAG, 0, 
			   COL_NAME, dataset->varname[v], 
			   -1);
    } else {
	char vstr[VNAMELEN + 8];

	sprintf(vstr, "%s(-%d)", dataset->varname[v], lag);
	gtk_list_store_set(store, iter, COL_ID, v, COL_LAG, lag, 
			   COL_NAME, vstr, -1);
    }

    if (ncols == 4) {
	if (lag == 0) {
	    gtk_list_store_set(store, iter, COL_FLAG, varflag, -1);
	} else {
	    set_varflag_from_parent(v, lag, mod, iter);
	}
    }
} 

static int 
varlist_remove_var_full (int v, GtkTreeModel *mod, GtkTreeIter *iter)
{
    GtkTreeIter *last = iter;
    int row = -1, ok = 1;
    int tv, i = 0;

#if VLDEBUG
    fprintf(stderr, "\nvarlist_remove_var_full: looking for var %d (%s)\n", v,
	    dataset->varname[v]);
#endif

    if (gtk_tree_model_get_iter_first(mod, iter)) {
	last = iter;
	while (1) {
	    gtk_tree_model_get(mod, iter, COL_ID, &tv, -1);
#if VLDEBUG
	    fprintf(stderr, "row %d: checking against %d\n", i, tv);
#endif
	    if (tv == v) {
		ok = gtk_list_store_remove(GTK_LIST_STORE(mod), iter);
#if VLDEBUG
		fprintf(stderr, "removed at row %d, now ok = %d\n", i, ok);
#endif
		if (row < 0) {
		    row = i;
		}
		if (ok) {
		    continue;
		} else {
		    break;
		}
	    }
	    if (!gtk_tree_model_iter_next(mod, iter)) {
		/* iter is now invalid */
		iter = last;
		break;
	    }
	    last = iter;
	    i++;
	} 
    } else {
	iter = last;
    }

    return row;
}   

static void
varlist_insert_var_full (int v, GtkTreeModel *mod, GtkTreeIter *iter, 
			 selector *sr, int locus)
{
    int lcontext = 0;

#if VLDEBUG
    fprintf(stderr, "varlist_insert_var_full: starting var %d\n", v);
#endif

    if (v > 0 && dataset_lags_ok(dataset)) {
	lcontext = sr_get_lag_context(sr, locus);
    }

    if (lcontext) {
	int *laglist = get_lag_pref_as_list(v, lcontext);

	if (laglist != NULL) {
	    int i, row, append = 0;

	    row = varlist_remove_var_full(v, mod, iter);
	    if (row < 0) {
		append = 1;
	    }

#if VLDEBUG
	    fprintf(stderr, "got laglist, done prior removal, row=%d, append=%d\n",
		    row, append);
#endif
	    for (i=1; i<=laglist[0]; i++) {
		if (append) {
		    gtk_list_store_append(GTK_LIST_STORE(mod), iter);
		} else {
		    gtk_list_store_insert(GTK_LIST_STORE(mod), iter, row++);
		}
#if VLDEBUG
		fprintf(stderr, "adding var %d, lag %d\n", v, laglist[i]);
#endif
		real_varlist_set_var(v, laglist[i], mod, iter);
	    }
	    free(laglist);
	} else {
	    lcontext = 0;
	}
    }

    if (lcontext == 0) {
	gtk_list_store_append(GTK_LIST_STORE(mod), iter);
	real_varlist_set_var(v, 0, mod, iter); 
    }
}

static gboolean set_active_var (GtkWidget *widget, GdkEventButton *event,
				selector *sr)
{
    GtkTreeView *view = GTK_TREE_VIEW(widget);
    GtkTreeModel *model = gtk_tree_view_get_model(view);
    GtkTreePath *path;

    if (gtk_tree_view_get_path_at_pos(view, event->x, event->y, &path, 
				      NULL, NULL, NULL)) { 
	GtkTreeIter iter;
	gint varnum, row;

	gtk_tree_model_get_iter(model, &iter, path);
	gtk_tree_model_get(model, &iter, COL_ID, &varnum, -1);
	if (sr != NULL) {
	    sr->active_var = varnum;
	}
	row = tree_path_get_row_number(path);
	gtk_tree_path_free(path);
	/* note: the following is used in listbox_drag() */
	g_object_set_data(G_OBJECT(widget), "active_row",
			  GINT_TO_POINTER(row));
    }

    return FALSE;
}

static void list_append_var_simple (GtkListStore *store, GtkTreeIter *iterp, int v)
{
    const char *vname = dataset->varname[v];

    gtk_list_store_append(store, iterp);    
    gtk_list_store_set(store, iterp, 
		       COL_ID, v,
		       COL_LAG, 0,
		       COL_NAME, vname,
		       -1);
}

static void list_append_var (GtkTreeModel *mod, GtkTreeIter *iter,
			     int v, selector *sr, int locus)
{
    int i, lcontext = 0;

    if (v > 0 && dataset_lags_ok(dataset)) {
	lcontext = sr_get_lag_context(sr, locus);
    }

    if (lcontext) {
	int *laglist = get_lag_pref_as_list(v, lcontext);

	if (laglist != NULL) {
	    for (i=1; i<=laglist[0]; i++) {
		gtk_list_store_append(GTK_LIST_STORE(mod), iter);
		real_varlist_set_var(v, laglist[i], mod, iter);
	    }
	    free(laglist);
	} else {
	    lcontext = 0;
	}
    }

    if (lcontext == 0) {
	gtk_list_store_append(GTK_LIST_STORE(mod), iter);
	real_varlist_set_var(v, 0, mod, iter);
    }
}

static void render_varname (GtkTreeViewColumn *column,
			    GtkCellRenderer *renderer,
			    GtkTreeModel *model,
			    GtkTreeIter *iter,
			    gpointer p)
{
    gint id;
 
    gtk_tree_model_get(model, iter, COL_ID, &id, -1);
    if (id < 0) {
	g_object_set(renderer, "weight", PANGO_WEIGHT_BOLD, NULL);
    } else {
	g_object_set(renderer, "weight", PANGO_WEIGHT_NORMAL, NULL);
    }
}

/* build a new liststore and associated tree view, and pack into the
   given @box */

static GtkWidget *var_list_box_new (GtkBox *box, selector *sr, int locus) 
{
    GtkListStore *store; 
    GtkWidget *view, *scroller;
    GtkCellRenderer *renderer;
    GtkTreeViewColumn *column;
    GtkTreeSelection *select;
    gboolean flagcol = FALSE;
    int width = 140;
    int height = -1;
    
    if (USE_RXLIST(sr->ci) && locus == SR_RVARS2) {
	/* VECM special, with restricted/unrestricted flag column */
	store = gtk_list_store_new(4, G_TYPE_INT, G_TYPE_INT, 
				   G_TYPE_STRING, G_TYPE_STRING);
	flagcol = TRUE;
    } else {
	/* ID number, lag, varname */
	store = gtk_list_store_new(3, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING);
    }

    view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
    g_object_unref(G_OBJECT(store));

    g_object_set_data(G_OBJECT(view), "sr", sr);
    g_object_set_data(G_OBJECT(view), "locus", GINT_TO_POINTER(locus));

    renderer = gtk_cell_renderer_text_new();
    g_object_set(renderer, "ypad", 0, NULL);
    column = gtk_tree_view_column_new_with_attributes(NULL,
						      renderer,
						      "text", 
						      COL_NAME,
						      NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); 

    if (flagcol) {
	column = gtk_tree_view_column_new_with_attributes(NULL,
							  renderer,
							  "text", 
							  COL_FLAG,
							  NULL);
	gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
    } else {
	gtk_tree_view_column_set_cell_data_func(column, renderer, 
						render_varname, 
						NULL, NULL);
    }

    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
    gtk_tree_view_set_reorderable(GTK_TREE_VIEW(view), FALSE);

    select = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));

    gtk_tree_selection_set_mode(select, GTK_SELECTION_MULTIPLE);
    g_signal_connect(G_OBJECT(view), "motion-notify-event",
		     G_CALLBACK(listbox_drag), NULL);

    if (locus == SR_LVARS) { 
	/* left-hand box with the selectable vars */
	g_signal_connect(G_OBJECT(view), "button-press-event",
			 G_CALLBACK(lvars_right_click),
			 sr);
	g_signal_connect(G_OBJECT(view), "button-press-event",
			 G_CALLBACK(set_active_var),
			 sr);
	g_signal_connect(G_OBJECT(view), "button-press-event",
			 G_CALLBACK(dblclick_lvars_row),
			 sr);
    } else if (locus == SR_RVARS1 || locus == SR_RVARS2) { 
	/* lists of selected items */
	g_signal_connect(G_OBJECT(view), "button-press-event",
			 G_CALLBACK(set_active_var),
			 NULL);
	if (flagcol) {
	    g_signal_connect(G_OBJECT(view), "button-press-event",
			     G_CALLBACK(listvar_flagcol_click),
			     view);
	} else {
	    g_signal_connect(G_OBJECT(view), "button-press-event",
			     G_CALLBACK(listvar_special_click),
			     view);
	}
    } 

    scroller = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
				   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroller),
					GTK_SHADOW_IN);    
    gtk_container_add(GTK_CONTAINER(scroller), view);

    gtk_box_pack_start(box, scroller, TRUE, TRUE, 0);

    width *= gui_scale;
    gtk_widget_set_size_request(view, width, height);
    gtk_widget_show(view);

#if GTK_MAJOR_VERSION >= 3
    gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(scroller),
					      width);
#endif

    gtk_widget_show(scroller);

    return view;
}

static int binary_var_check (int v, const char *vname)
{
    if (!gretl_isdummy(dataset->t1, dataset->t2, dataset->Z[v])) {
	errbox(_("The variable '%s' is not a 0/1 variable."), vname);
	return 1;
    }

    return 0;
}

/* add to "extra" var slot the current selection from sr->lvars */

static void real_set_extra_var (GtkTreeModel *model, GtkTreePath *path,
				GtkTreeIter *iter, selector *sr)
{
    gchar *vname;
    gint v;

    gtk_tree_model_get(model, iter, COL_ID, &v, COL_NAME, &vname, -1);

    if (v < 0) {
	return;
    }

    if (sr->ci == HECKIT || sr->ci == BIPROBIT) {
	if (binary_var_check(v, vname)) {
	    return;
	}
    } else if (NONPARAM_CODE(sr->ci)) {
	const gchar *test = gtk_entry_get_text(GTK_ENTRY(sr->depvar));

	if (!strcmp(vname, test)) {
	    /* can't have the same var in both places */
	    gtk_entry_set_text(GTK_ENTRY(sr->depvar), "");
	}
    }
    
    gtk_entry_set_text(GTK_ENTRY(sr->extra[0]), vname);
    g_free(vname);
    g_object_set_data(G_OBJECT(sr->extra[0]), "data",
		      GINT_TO_POINTER(v));
}

static void set_extra_var_callback (GtkWidget *w, selector *sr)
{
    GtkTreeSelection *selection;

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sr->lvars));
    gtk_tree_selection_selected_foreach(selection, 
					(GtkTreeSelectionForeachFunc) 
					real_set_extra_var,
					sr);
}

static void real_set_third_var (GtkTreeModel *model, GtkTreePath *path,
				GtkTreeIter *iter, selector *sr)
{
    gchar *vname = NULL;
    gint v;

    gtk_tree_model_get(model, iter, COL_ID, &v, COL_NAME, &vname, -1);    

    if (v < 0) {
	g_free(vname);
	return;
    }

    gtk_entry_set_text(GTK_ENTRY(sr->rvars1), vname);
    g_free(vname);
    g_object_set_data(G_OBJECT(sr->rvars1), "data",
		      GINT_TO_POINTER(v));
}

static void set_third_var_callback (GtkWidget *w, selector *sr)
{
    GtkTreeSelection *selection;

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sr->lvars));
    gtk_tree_selection_selected_foreach(selection, 
					(GtkTreeSelectionForeachFunc) 
					real_set_third_var,
					sr);
}

/* When adding a variable to the Endogenous or Exogenous listing
   for VAR, VECM, etc., check that the variable in question is not
   present in the other listing: if it is, then remove it.

   And similarly, when constructing a list of function interfaces
   for a package, don't allow selection of a given function as
   both a public interface and a private ("Helper") function.
*/

static void dual_selection_fix_conflicts (selector *sr, int new_locus)
{
    GtkTreeModel *targ, *src;
    GtkTreeIter iter;
    gboolean ok;
    int *srclist = NULL;
    gint v;

    if (new_locus == SR_RVARS1) {
	src = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->rvars1));
	targ = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->rvars2));
    } else {
	src = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->rvars2));
	targ = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->rvars1));
    }

    /* construct list of vars in @src */

    if (gtk_tree_model_get_iter_first(src, &iter)) {
	do {
	    gtk_tree_model_get(src, &iter, COL_ID, &v, -1);
	    gretl_list_append_term(&srclist, v);
        } while (gtk_tree_model_iter_next(src, &iter));
    }

    /* check @srclist against @targ */

    if (srclist != NULL && gtk_tree_model_get_iter_first(targ, &iter)) {
	do {
	    ok = TRUE;
	    gtk_tree_model_get(targ, &iter, COL_ID, &v, -1);
	    if (in_gretl_list(srclist, v)) {
		ok = gtk_list_store_remove(GTK_LIST_STORE(targ), &iter);
	    }
        } while (ok && gtk_tree_model_iter_next(targ, &iter));
    }

    free(srclist);
}

static void 
maybe_insert_or_revise_depvar_lags (selector *sr, int v, int lcontext,
				    int revise)
{
    int *laglist = NULL;
    GtkWidget *w;
    GtkTreeModel *mod;
    GtkTreeIter iter;
    int append = 1;
    int modv, row = 0;
    int jmin = 0, jmax = 1;
    int i, j;

    if (lcontext == LAG_Y_X) {
	jmin = 0;
	jmax = 1;
    } else if (lcontext == LAG_Y_W) {
	/* instrument, not indep var */
	jmin = 1;
	jmax = 2;
    }

    for (j=jmin; j<jmax; j++) {

	w = (j > 0)? sr->rvars2: sr->rvars1;
	if (w == NULL) {
	    return;
	}

	mod = gtk_tree_view_get_model(GTK_TREE_VIEW(w));

	if (lcontext == 0) {
	    lcontext = (j > 0)? LAG_Y_W : LAG_Y_X;
	}

	laglist = get_lag_pref_as_list(v, lcontext);
	if (laglist == NULL) {
	    if (revise) {
		varlist_remove_var_full(v, mod, &iter);
	    }
	    return;
	}

	varlist_remove_var_full(v, mod, &iter);

	if (gtk_tree_model_get_iter_first(mod, &iter)) {
	    do {
		gtk_tree_model_get(mod, &iter, COL_ID, &modv, -1);
		if (modv > 0) {
		    append = 0;
		    break;
		}
		row++;
	    } while (gtk_tree_model_iter_next(mod, &iter));
	} 

	for (i=1; i<=laglist[0]; i++) {
	    if (append) {
		gtk_list_store_append(GTK_LIST_STORE(mod), &iter);
	    } else {
		gtk_list_store_insert(GTK_LIST_STORE(mod), &iter, row++);
	    }
#if VLDEBUG
	    fprintf(stderr, "depvar_lags: adding var %d, lag %d\n", v, laglist[i]);
#endif
	    real_varlist_set_var(v, laglist[i], mod, &iter); 
	}    

	free(laglist);
    }
}

static void maybe_insert_depvar_lags (selector *sr, int v, int lcontext)
{
    maybe_insert_or_revise_depvar_lags(sr, v, lcontext, 0);
}

static void maybe_revise_depvar_lags (selector *sr, int v, int lcontext)
{
    maybe_insert_or_revise_depvar_lags(sr, v, lcontext, 1);
}

static void remove_as_indep_var (selector *sr, gint v)
{
    GtkTreeView *view = GTK_TREE_VIEW(sr->rvars1);
    GtkTreeModel *model = gtk_tree_view_get_model(view);
    GtkTreeIter iter;
    gboolean ok;
    gint xnum;

    if (gtk_tree_model_get_iter_first(model, &iter)) {
	do {
	    ok = TRUE;
	    gtk_tree_model_get(model, &iter, COL_ID, &xnum, -1);
	    if (xnum == v) {
		ok = gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
	    }
        } while (ok && gtk_tree_model_iter_next(model, &iter));
    }
}

static void dependent_var_cleanup (selector *sr, int newy)
{
    int oldy = selector_get_depvar_number(sr);

    if (GTK_IS_TREE_VIEW(sr->rvars1) && oldy != newy) {
	if (oldy > 0) {
	    remove_as_indep_var(sr, oldy); /* lags business */
	}
	y_x_lags_enabled = 0;
	y_w_lags_enabled = 0;
	remove_as_indep_var(sr, newy);
    }
}

static void np_xvar_cleanup (selector *sr, int newy)
{
    const gchar *test = gtk_entry_get_text(GTK_ENTRY(sr->extra[0]));
    
    if (test != NULL && *test != '\0') {
	int xv = current_series_index(dataset, test);

	if (xv == newy) {
	    gtk_entry_set_text(GTK_ENTRY(sr->extra[0]), "");
	}
    }
}

static int set_dependent_var_from_active (selector *sr)
{
    gint v = sr->active_var;
    const char *vname;

    if (v < 0 || v >= dataset->v || sr->depvar == NULL) {
	return 1;
    } 

    vname = dataset->varname[v];

    if (sr->ci == PROBIT || sr->ci == BIPROBIT) {
	if (binary_var_check(v, vname)) {
	    return 1;
	}
    }

    /* models: if we select foo as regressand, remove it from the list
       of regressors if need be; also remove lags associated with the
       previous dependent var, if any.
    */
    if (MODEL_CODE(sr->ci)) {
	dependent_var_cleanup(sr, v);
    } else if (NONPARAM_CODE(sr->ci)) {
	np_xvar_cleanup(sr, v);
    }

    gtk_entry_set_text(GTK_ENTRY(sr->depvar), vname);

    if (select_lags_depvar(sr->ci)) {
	maybe_insert_depvar_lags(sr, v, 0);
    }

    return 0;
}

static void real_set_dependent_var (GtkTreeModel *model, GtkTreePath *path,
				    GtkTreeIter *iter, selector *sr)
{
    gchar *vname;
    gint v;

    gtk_tree_model_get(model, iter, COL_ID, &v, COL_NAME, &vname, -1);
    
    if (v < 0) {
	return;
    }

    if (sr->ci == PROBIT || sr->ci == BIPROBIT) {
	if (binary_var_check(v, vname)) {
	    return;
	}
    }

    if (MODEL_CODE(sr->ci)) {
	dependent_var_cleanup(sr, v);
    }

    gtk_entry_set_text(GTK_ENTRY(sr->depvar), vname);

    if (select_lags_depvar(sr->ci)) {
	maybe_insert_depvar_lags(sr, v, 0);
    }
}

static void set_dependent_var_callback (GtkWidget *w, selector *sr)
{
    GtkTreeSelection *selection;

    if (sr->depvar == NULL) return;

    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(sr->lvars));
    gtk_tree_selection_selected_foreach (selection, 
					 (GtkTreeSelectionForeachFunc) 
					 real_set_dependent_var,
					 sr);
}

static void set_right_var_from_main (GtkTreeModel *model, GtkTreePath *path,
				     GtkTreeIter *iter, selector *sr)
{
    GtkTreeModel *rmod;
    GtkTreeIter r_iter;
    gchar *idstr = NULL;
    int v;

    gtk_tree_model_get(model, iter, COL_ID, &idstr, -1);

    rmod = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->rvars1));
    if (rmod == NULL) {
	g_free(idstr);
	return;
    }

    v = atoi(idstr);

    if (gtk_tree_model_get_iter_first(rmod, &r_iter)) {
	while (gtk_tree_model_iter_next(rmod, &r_iter)) {
	    ;
	}
    }

    gtk_list_store_append(GTK_LIST_STORE(rmod), &r_iter);
    real_varlist_set_var(v, 0, rmod, &r_iter);   

    g_free(idstr);
}

static void set_vars_from_main (selector *sr)
{
    GtkTreeSelection *selection;

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(mdata->listbox));
    gtk_tree_selection_selected_foreach(selection, 
					(GtkTreeSelectionForeachFunc) 
					set_right_var_from_main,
					sr);
}

/* Append a specified variable in the SR_RVARS1 locus: used when
   saving data and there's only one variable to save.
*/

static void select_singleton (selector *sr)
{
    GtkTreeModel *lmod, *rmod;
    GtkTreeIter iter;
    int v;

    lmod = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->lvars));
    if (lmod == NULL) {
	return;
    }    

    rmod = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->rvars1));
    if (rmod == NULL) {
	return;
    }

    gtk_tree_model_get_iter_first(lmod, &iter);
    gtk_tree_model_get(lmod, &iter, COL_ID, &v, -1);

    gtk_tree_model_get_iter_first(rmod, &iter);
    gtk_list_store_append(GTK_LIST_STORE(rmod), &iter);
    gtk_list_store_set(GTK_LIST_STORE(rmod), &iter, 
		       COL_ID, v, COL_LAG, 0, 
		       COL_NAME, dataset->varname[v], -1);
}

static int varflag_dialog (int v, GtkWidget *parent)
{
    const char *opts[] = {
	N_("Unrestricted"),
	N_("Restricted")
    };
    gchar *title, *label;
    int ret;

    title = g_strdup_printf("gretl: %s", _("add exogenous variable"));
    label = g_strdup_printf(_("Status of '%s' in VECM:"), dataset->varname[v]);

    ret = radio_dialog(title, label, opts, 2, 0, 0, parent);
    if (ret == 0) {
	set_varflag(UNRESTRICTED);
    } else if (ret == 1) {
	set_varflag(RESTRICTED);
    }

    g_free(title);
    g_free(label);

    return ret;
}

static int arima_selected (selector *sr)
{
    int ret = 0;

    if (sr->extra[2] != NULL) {
	/* the arima_d spinner */
	ret = spinner_get_int(sr->extra[2]);
    }

    if (!ret && sr->extra[6] != NULL) {
	/* the seasonal arima_D spinner */
	ret = spinner_get_int(sr->extra[6]);
    } 

    return ret;
}

static void xdiff_button_set_sensitive (selector *sr, gboolean s)
{
    if (sr->xdiff_button != NULL) { 
	s = s && arima_selected(sr);
	gtk_widget_set_sensitive(sr->xdiff_button, s);
    }
}

static int rvars1_n_vars (selector *sr)
{
    GtkTreeModel *model;
    GtkTreeIter iter;
    int nv = 0;

    model = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->rvars1));
    if (model == NULL) {
	return 0;
    }

    if (gtk_tree_model_get_iter_first(model, &iter)) {
	do {
	    nv++;
	} while (gtk_tree_model_iter_next(model, &iter));
    }

    return nv;
}

/* add a variable (or possibly a list of variables) to the listbox 
   at @locus */

static void real_add_generic (GtkTreeModel *srcmodel, GtkTreeIter *srciter, 
			      selector *sr, int locus)
{
    GtkWidget *w;
    GtkTreeModel *model;
    GtkTreeIter iter;
    gchar *vname = NULL;
    const int *vlist = NULL;
    gint v, xnum;
    gint at_max = 0;
    gint keep_names = 0;
    int i, addvars = 1;
    int nvars = 0;
    int err = 0;

    w = (locus == SR_RVARS2)? sr->rvars2 : sr->rvars1;

    if (!GTK_IS_TREE_VIEW(w)) {
	return;
    }

    model = gtk_tree_view_get_model(GTK_TREE_VIEW(w));
    if (model == NULL) {
	return;
    }

    /* get the 'source' info */
    gtk_tree_model_get(srcmodel, srciter, COL_ID, &v, COL_NAME, &vname, -1);

    if (v < 0) {
	/* we should have a gretl list, not a single variable */
	vlist = get_list_by_name(vname);
	if (vlist == NULL) {
	    err = E_DATA;
	} else {
	    addvars = vlist[0];
	}
    } else {
	keep_names = 
	    GPOINTER_TO_INT(g_object_get_data(G_OBJECT(sr->lvars), 
					      "keep-names"));
    }

    if (err) {
	gui_errmsg(err);
	return;
    } 

    if (!keep_names) {
	g_free(vname);
	vname = NULL;
    }

    for (i=0; i<addvars && !at_max; i++) {
	int already_there = 0;

	if (vlist != NULL) {
	    v = vlist[i+1];
	}

	/* first check if we're maxed out, or if the variable to
	   add is already present */

	if (gtk_tree_model_get_iter_first(model, &iter)) {
	    do {
		if (i == 0 && selection_at_max(sr, ++nvars)) {
		    at_max = 1;
		}
		if (!at_max && !already_there) {
		    gtk_tree_model_get(model, &iter, COL_ID, &xnum, -1);
		    if (xnum == v) {
			already_there = 1; 
		    }
		}
	    } while (gtk_tree_model_iter_next(model, &iter));
	}

	if (!already_there && !at_max) {
	    /* OK to append */
	    if (keep_names) {
		int ncols = gtk_tree_model_get_n_columns(model);

		gtk_list_store_append(GTK_LIST_STORE(model), &iter);
		gtk_list_store_set(GTK_LIST_STORE(model), &iter, 
				   COL_ID, v, COL_LAG, 0, COL_NAME, vname, -1);
		if (ncols == 4) {
		    gtk_list_store_set(GTK_LIST_STORE(model), &iter, 
				       COL_FLAG, varflag, -1);
		} 
		g_free(vname);
	    } else {
		if (locus == SR_RVARS2 && USE_RXLIST(sr->ci)) {
		    if (varflag_dialog(v, sr->dlg) < 0) {
			return;
		    }
		}
#if VLDEBUG
		fprintf(stderr, "real_add_generic: calling varlist_insert_var_full\n");
#endif
		varlist_insert_var_full(v, model, &iter, sr, locus);
	    }

	    if (selection_at_max(sr, ++nvars)) {
		at_max = 1;
	    }
	}
    }

    if (sr->add_button != NULL && at_max) {
	gtk_widget_set_sensitive(sr->add_button, FALSE);
    }

    if (nvars > 0) {
	if (lags_button_relevant(sr, locus)) {
	    gtk_widget_set_sensitive(sr->lags_button, TRUE);
	} else if (VECLAGS_CODE(sr->ci) && locus == SR_RVARS1 &&
		   sr->extra[EXTRA_LAGS] != NULL) {
	    gtk_widget_set_sensitive(sr->extra[EXTRA_LAGS], TRUE);
	}
	if (sr->ci == ARMA) {
	    xdiff_button_set_sensitive(sr, TRUE);
	}
	if (USE_VECXLIST(sr->ci) || FNPKG_CODE(sr->ci)) {
	    dual_selection_fix_conflicts(sr, locus);
	}	
    }
}

static void add_to_rvars1 (GtkTreeModel *model, GtkTreePath *path,
			   GtkTreeIter *iter, selector *sr)
{
    if (MODEL_CODE(sr->ci)) {
	/* don't add the regressand to the list of regressors */
	gint xnum, ynum;

	gtk_tree_model_get(model, iter, COL_ID, &xnum, -1);
	ynum = selector_get_depvar_number(sr);
	if (ynum >= 0 && xnum == ynum) {
	    return;
	}
    }

    real_add_generic(model, iter, sr, SR_RVARS1);
}

static void add_to_rvars2 (GtkTreeModel *model, GtkTreePath *path,
			   GtkTreeIter *iter, selector *sr)
{
    real_add_generic(model, iter, sr, SR_RVARS2);
}

static void add_to_rvars2_callback (GtkWidget *w, selector *sr)
{
    GtkTreeSelection *selection;

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sr->lvars));
    gtk_tree_selection_selected_foreach(selection, 
					(GtkTreeSelectionForeachFunc) 
					add_to_rvars2,
					sr);
}

static void add_all_to_rvars1_callback (GtkWidget *w, selector *sr)
{
    GtkTreeSelection *selection;

    if (!GTK_IS_TREE_VIEW(sr->lvars)) {
	return;
    }

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sr->lvars));
    gtk_tree_selection_select_all(selection);
    gtk_tree_selection_selected_foreach(selection, 
					(GtkTreeSelectionForeachFunc) 
					add_to_rvars1,
					sr);
}

static void add_to_rvars1_callback (GtkWidget *w, selector *sr)
{
    GtkTreeSelection *selection;

    if (!GTK_IS_TREE_VIEW(sr->lvars)) {
	return;
    }

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sr->lvars));
    gtk_tree_selection_selected_foreach(selection, 
					(GtkTreeSelectionForeachFunc) 
					add_to_rvars1,
					sr);
}

static void remove_from_right (GtkWidget *w, selector *sr, 
			       GtkWidget *listbox)
{
    GtkTreeView *view = GTK_TREE_VIEW(listbox);
    GtkTreeModel *model = gtk_tree_view_get_model(view);
    GtkTreeSelection *selection = gtk_tree_view_get_selection(view);
    GtkTreePath *path;
    GtkTreeIter iter, last;
    int context = 0;
    int nsel = 0;

    if (model == NULL || selection == NULL) {
	return;
    }

    /* get to the last row */
    if (gtk_tree_model_get_iter_first(model, &iter)) {
	last = iter;
	nsel = 1;
	while (gtk_tree_model_iter_next(model, &iter)) {
	    last = iter;
	    nsel++;
	}
    } else {
	return;
    }

    context = lag_context_from_widget(GTK_WIDGET(view));
    
    /* work back up, deleting selected rows */
    path = gtk_tree_model_get_path(model, &last);
    while (1) {
	if (gtk_tree_model_get_iter(model, &last, path) &&
	    gtk_tree_selection_iter_is_selected(selection, &last)) {
	    if (context) {
		gint v, lag;

		gtk_tree_model_get(model, &last, COL_ID, &v, COL_LAG, &lag, -1);
		remove_specific_lag(v, lag, context);
	    }
	    gtk_list_store_remove(GTK_LIST_STORE(model), &last);
	    nsel--;
	}
	if (!gtk_tree_path_prev(path)) {
	    break;
	}
    } 

    if (sr->add_button != NULL &&
	!gtk_widget_is_sensitive(sr->add_button) &&
	!selection_at_max(sr, nsel)) {
	gtk_widget_set_sensitive(sr->add_button, TRUE);
    }

    if (nsel == 0) {
	/* the listbox is now empty */
	if (context && sr->lags_button != NULL) {
	    gtk_widget_set_sensitive(sr->lags_button, FALSE);
	}
	if (GTK_WIDGET(view) == sr->rvars1 && sr->ci == ARMA) {
	    xdiff_button_set_sensitive(sr, FALSE);
	}
    }
}

static void remove_from_rvars1_callback (GtkWidget *w, selector *sr)
{
    remove_from_right(w, sr, sr->rvars1);
}

static void remove_from_rvars2_callback (GtkWidget *w, selector *sr)
{
    remove_from_right(w, sr, sr->rvars2);
}

/* double-clicking sets the dependent variable and marks it as the
   default, if applicable */

static gint 
dblclick_lvars_row (GtkWidget *w, GdkEventButton *event, selector *sr) 
{
    if (sr->depvar != NULL && event != NULL && 
	event->type == GDK_2BUTTON_PRESS) {
	int err = set_dependent_var_from_active(sr);

	if (!err && sr->default_check != NULL) {
	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sr->default_check),
					 TRUE);
	}
    }

    return FALSE;
}

/* flip the flag that designates an exogenous veriable in a VECM
   as either Unrestricted or Restricted (to the cointegrating
   space) 
*/

static void maybe_flip_vecm_flag (GtkTreeModel *model, GtkTreePath *path,
				  GtkTreeIter *iter, GtkWidget *w)
{
    gint i = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "opt"));
    gchar *orig = NULL, *repl = NULL;

    gtk_tree_model_get(model, iter, COL_FLAG, &orig, -1);

    if (orig != NULL) {
	repl = (i == 0)? "U" : "R";
	if (strcmp(orig, repl)) {
	    gtk_list_store_set(GTK_LIST_STORE(model), iter, COL_FLAG, repl, -1);
	}
	g_free(orig);
    }
}

static void flag_popup_activated (GtkWidget *w, gpointer data)
{
    GtkTreeView *view = GTK_TREE_VIEW(data);
    GtkTreeSelection *sel;

    sel = gtk_tree_view_get_selection(view);
    gtk_tree_selection_selected_foreach(sel, 
					(GtkTreeSelectionForeachFunc) 
					maybe_flip_vecm_flag,
					w);
    gtk_widget_destroy(w);
}

static void create_flag_item (GtkWidget *popup, int i, GtkWidget *view)
{
    static char *flag_strs[] = {
	N_("Unrestricted"),
	N_("Restricted")
    };
    GtkWidget *item;

    item = gtk_menu_item_new_with_label(_(flag_strs[i]));
    g_object_set_data(G_OBJECT(item), "opt", GINT_TO_POINTER(i));
    g_signal_connect(G_OBJECT(item), "activate",
		     G_CALLBACK(flag_popup_activated),
		     view);
    g_signal_connect(G_OBJECT(item), "destroy",
		     G_CALLBACK(delete_widget),
		     popup);
    gtk_widget_show(item);
    gtk_menu_shell_append(GTK_MENU_SHELL(popup), item);
}

static gint listvar_flagcol_click (GtkWidget *widget, 
				   GdkEventButton *event, 
				   gpointer data)
{
    GtkWidget *view = GTK_WIDGET(data);
    GtkWidget *flag_popup;
    int i;

    if (right_click(event)) {
	flag_popup = gtk_menu_new();
	for (i=0; i<2; i++) {
	    create_flag_item(flag_popup, i, view);
	}
	gtk_menu_popup(GTK_MENU(flag_popup), NULL, NULL, NULL, NULL,
		       event->button, event->time);
	return TRUE;
    }

    return FALSE;
}

static int selection_get_row_info (GtkTreeModel *model,
				   GtkTreeSelection *sel,
				   int *r0, int *r1, int *rmin)
{
    GtkTreeIter iter;
    int i, rmax = 0;

    *r0 = 10000;
    *r1 = -1;
    *rmin = 0;

    if (gtk_tree_model_get_iter_first(model, &iter)) {
	gint id;

	gtk_tree_model_get(model, &iter, COL_ID, &id, -1);
	if (id == 0) {
	    /* don't shift const from position 0 */
	    *rmin = 1;
	}
	for (i=0; ; i++) {
	    if (gtk_tree_selection_iter_is_selected(sel, &iter)) {
		if (i < *r0) {
		    *r0 = i;
		}
		if (i > *r1) {
		    *r1 = i;
		}	    
	    }
	    if (!gtk_tree_model_iter_next(model, &iter)) {
		break;
	    }
	}
	rmax = i;
    }

    return rmax;
}

static void set_selection_from_list (GtkTreeView *view,
				     GtkTreeModel *model,
				     GtkTreeSelection *sel,
				     const int *list)
{
    GtkTreeIter iter;
    int id, nsel = 0;

    gtk_tree_selection_unselect_all(sel);
    gtk_tree_model_get_iter_first(model, &iter);

    while (gtk_tree_model_iter_next(model, &iter) && nsel < list[0]) {
	gtk_tree_model_get(model, &iter, COL_ID, &id, -1);
	if (in_gretl_list(list, id)) {
	    gtk_tree_selection_select_iter(sel, &iter);
	    nsel++;
	} 
    }
}

static int swap_row_content (GtkTreeModel *model, GtkTreeIter *iter0,
			     GtkTreeIter *iter1, int flags)
{
    GtkListStore *store = GTK_LIST_STORE(model);
    gint id0, id1, l0, l1;
    gchar *s0, *s1;

    gtk_tree_model_get(model, iter0, COL_ID, &id0, COL_LAG, &l0, 
		       COL_NAME, &s0, -1);
    gtk_tree_model_get(model, iter1, COL_ID, &id1, COL_LAG, &l1, 
		       COL_NAME, &s1, -1);
    gtk_list_store_set(store, iter0, COL_ID, id1, COL_LAG, l1, 
		       COL_NAME, s1, -1);
    gtk_list_store_set(store, iter1, COL_ID, id0, COL_LAG, l0, 
		       COL_NAME, s0, -1);

    g_free(s0);
    g_free(s1);

    if (flags) {
	gtk_tree_model_get(model, iter0, COL_FLAG, &s0);
	gtk_tree_model_get(model, iter1, COL_FLAG, &s1);
	gtk_list_store_set(store, iter0, COL_FLAG, s1);
	gtk_list_store_set(store, iter1, COL_FLAG, s0);

	g_free(s0);
	g_free(s1);
    }    

    return id1;
}

static void move_selected_rows (GtkMenuItem *item, GtkTreeView *view)
{
    gint down = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "down"));
    GtkTreeSelection *sel = gtk_tree_view_get_selection(view);
    GtkTreeModel *model = gtk_tree_view_get_model(view);
    GtkTreeIter iter0, iter1;
    GtkTreePath *path;
    gboolean flags = FALSE;
    int id, *sellist = NULL;

    if (gtk_tree_view_get_column(view, 3) != NULL) {
	flags = TRUE;
    }

    if (down) {
	tree_model_get_iter_last(model, &iter1);
	while (tree_model_iter_prev(model, &iter1)) {
	    if (gtk_tree_selection_iter_is_selected(sel, &iter1)) {
		path = gtk_tree_model_get_path(model, &iter1);
		gtk_tree_path_next(path);
		gtk_tree_model_get_iter(model, &iter0, path);
		id = swap_row_content(model, &iter0, &iter1, flags);
		gretl_list_append_term(&sellist, id);
		gtk_tree_path_free(path);
	    }	    
	}	
    } else {
	gtk_tree_model_get_iter_first(model, &iter1);
	while (gtk_tree_model_iter_next(model, &iter1)) {
	    if (gtk_tree_selection_iter_is_selected(sel, &iter1)) {
		path = gtk_tree_model_get_path(model, &iter1);
		gtk_tree_path_prev(path);
		gtk_tree_model_get_iter(model, &iter0, path);
		id = swap_row_content(model, &iter0, &iter1, flags);
		gretl_list_append_term(&sellist, id);
		gtk_tree_path_free(path);
	    }
	}
    }
    
    if (sellist != NULL) {
	set_selection_from_list(view, model, sel, sellist);    
	free(sellist);
    }

    gtk_widget_destroy(GTK_WIDGET(item));
}

static gint listvar_special_click (GtkWidget *widget, GdkEventButton *event, 
				   gpointer data)
{
    if (right_click(event)) {
	GtkTreeView *view = GTK_TREE_VIEW(data);
	GtkTreeModel *model = gtk_tree_view_get_model(view);
	GtkTreeSelection *sel = gtk_tree_view_get_selection(view);
	int r0, r1, rmin, rmax;

	rmax = selection_get_row_info(model, sel, &r0, &r1, &rmin);

	if (r0 >= 0 && !(rmin == 1 && r0 == 0)) {
	    const gchar *icons[] = {
		GTK_STOCK_GO_UP,
		GTK_STOCK_GO_DOWN
	    };
	    GtkWidget *popup = gtk_menu_new();
	    GtkWidget *item;
	    int i;

	    for (i=0; i<2; i++) {
		if (i == 0 && r0 <= rmin) {
		    /* can't do move up */
		    continue;
		} else if (i == 1 && r1 >= rmax) {
		    /* can't do move down */
		    continue;
		}
		item = gtk_image_menu_item_new_from_stock(icons[i], NULL);
		g_object_set_data(G_OBJECT(item), "down", 
				  GINT_TO_POINTER(i));
		g_signal_connect(G_OBJECT(item), "activate",
				 G_CALLBACK(move_selected_rows), view);
		g_signal_connect(G_OBJECT(item), "destroy",
				 G_CALLBACK(delete_widget), popup);
		gtk_widget_show_all(item);
		gtk_menu_shell_append(GTK_MENU_SHELL(popup), item);	
	    }
	
	    gtk_menu_popup(GTK_MENU(popup), NULL, NULL, NULL, NULL,
			   event->button, event->time);
	}
	return TRUE;
    } 

    return FALSE;
}

static gint lvars_right_click (GtkWidget *widget, GdkEventButton *event, 
			       selector *sr)
{
    if (right_click(event)) {
	if (NONPARAM_CODE(sr->ci)) {
	    set_extra_var_callback(NULL, sr);
	} else if (sr->ci == GR_FBOX) {
	    set_third_var_callback(NULL, sr);
	} else {
	    add_to_rvars1_callback(NULL, sr);
	}
	return TRUE;
    }

    return FALSE;
}

/* end special click callbacks */

static void varlist_insert_const (GtkWidget *w)
{
    GtkTreeModel *mod = gtk_tree_view_get_model(GTK_TREE_VIEW(w));
    GtkTreeIter iter;

    gtk_tree_model_get_iter_first(mod, &iter);
    gtk_list_store_append(GTK_LIST_STORE(mod), &iter);
    gtk_list_store_set(GTK_LIST_STORE(mod), &iter, 
		       COL_ID, 0, COL_LAG, 0, COL_NAME, "const", -1);
}

static void clear_vars (GtkWidget *w, selector *sr)
{
    GtkTreeSelection *selection;

    /* deselect all vars on left */
    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sr->lvars));
    gtk_tree_selection_unselect_all(selection);

    /* clear dependent var slot */
    if (sr->depvar != NULL) {
	gtk_entry_set_text(GTK_ENTRY(sr->depvar), "");
	if (sr->default_check != NULL) {
	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sr->default_check),
					 FALSE);
	}
	default_y = -1;
    }

    /* extra variable entry? */
    if (GTK_IS_ENTRY(sr->extra[0])) {
	gtk_entry_set_text(GTK_ENTRY(sr->extra[0]), "");
    }

    if (THREE_VARS_CODE(sr->ci)) {
	/* clear special slot */
	gtk_entry_set_text(GTK_ENTRY(sr->rvars1), "");
    } else if (sr->rvars1 != NULL) {
	/* empty upper right variable list */
	clear_varlist(sr->rvars1);
	if (sr->add_button != NULL) {
	    gtk_widget_set_sensitive(sr->add_button, TRUE);
	}
    }

    if (MODEL_CODE(sr->ci) && sr->ci != ARMA && sr->ci != GARCH) {
	/* insert default const in regressors box */
	varlist_insert_const(sr->rvars1);
    }

    if (sr->rvars2 != NULL) {
	/* empty lower right variable list */
	clear_varlist(sr->rvars2);
	if (USE_ZLIST(sr->ci)) {
	    varlist_insert_const(sr->rvars2);
	}
    }

    if (sr->lags_button != NULL) {
	gtk_widget_set_sensitive(sr->lags_button, FALSE);
    }

    if (VECLAGS_CODE(sr->ci) && sr->extra[EXTRA_LAGS] != NULL) {
	gtk_widget_set_sensitive(sr->extra[EXTRA_LAGS], FALSE);
	gtk_widget_set_sensitive(sr->extra[0], TRUE);
    }

    clear_selector();
}

static gint varlist_row_count (selector *sr, int locus, int *realrows)
{
    int lcontext = 0;
    GtkWidget *w;
    GtkTreeModel *mod;
    GtkTreeIter iter;
    gint v, lag, n = 0;

    w = (locus == SR_RVARS1)? sr->rvars1 : sr->rvars2;

    if (w == NULL || !gtk_widget_is_sensitive(w)) {
	return 0;
    }

    if (realrows != NULL) {
	lcontext = sr_get_lag_context(sr, locus);
	*realrows = 0;
    }

    if (w != NULL) {
	mod = gtk_tree_view_get_model(GTK_TREE_VIEW(w));
	if (GTK_IS_TREE_MODEL(mod) && 
	    gtk_tree_model_get_iter_first(mod, &iter)) {
	    do {
		n++;
		if (lcontext) {
		    gtk_tree_model_get(mod, &iter, COL_ID, &v, COL_LAG, &lag, -1);
		    if (!is_lag_dummy(v, lag, lcontext)) {
			*realrows += 1;
		    }
		}
	    } while (gtk_tree_model_iter_next(mod, &iter));
	}	    
    }

    if (realrows != NULL && lcontext == 0) {
	*realrows = n;
    }

    return n;
}

static void topslot_empty (int ci)
{
    switch (ci) {
    case GR_XY:
    case GR_3D:
    case GR_IMP:
	warnbox(_("You must select an X-axis variable"));
	break;
    case SCATTERS:
	warnbox(_("You must select a Y-axis variable"));
	break;
    case INTREG:
	warnbox(_("You must select a lower bound variable"));
	break;
    default:
	warnbox(_("You must select a dependent variable"));
    }
}

static void reverse_list (char *list)
{
    char *tmp, *p;
    char istr[8];

    p = strchr(list, ';');
    if (p == NULL) return;

    tmp = malloc(strlen(list) + 4);
    if (tmp == NULL) return;

    sscanf(list, "%7s", istr);

    strcpy(tmp, p + 2);
    strcat(tmp, " ; ");
    strcat(tmp, istr);

    strcpy(list, tmp);

    free(tmp);
}

enum cmdlist_codes {
    ADD_NOW,
    ADD_AT_END
};

static int add_to_cmdlist (selector *sr, const char *add)
{
    int n = strlen(sr->cmdlist);
    char *cmdlist = NULL;
    int err = 0;

    if (n % MAXLEN > MAXLEN - 32) {
	int blocks = 2 + n / MAXLEN;

	cmdlist = realloc(sr->cmdlist, blocks * MAXLEN);
	if (cmdlist == NULL) {
	    err = 1;
	} else {
	    sr->cmdlist = cmdlist;
	}
    }

    if (!err) {
	strcat(sr->cmdlist, add);
    }

    return err;
}

static char *arma_lag_string (char *targ, const char *s)
{
    while (*s == ' ') s++;

    if (*s == '\0') {
	strcpy(targ, "0 ");
    } else if (isalpha(*s) || *s == '{') {
	sprintf(targ, "%s ", s);
    } else {
	sprintf(targ, "{%s} ", s);
    } 

    gretl_charsub(targ, ',', ' ');

    return targ;
}

static void arma_spec_to_cmdlist (selector *sr)
{
    const char *txt;
    char s[32];

    free(arlags);
    arlags = NULL;

    free(malags);
    malags = NULL;

    if (gtk_widget_is_sensitive(sr->extra[1])) {
	/* "gappy" AR lags activated */
	txt = gtk_entry_get_text(GTK_ENTRY(sr->extra[1]));
	add_to_cmdlist(sr, arma_lag_string(s, txt)); 
	arlags = gretl_strdup(txt);
    } else {
	/* regular max AR lag */
	arma_p = spinner_get_int(sr->extra[0]);
	sprintf(s, "%d ", arma_p);
	add_to_cmdlist(sr, s);
    } 

    arima_d = spinner_get_int(sr->extra[2]);
    sprintf(s, "%d ", arima_d);
    add_to_cmdlist(sr, s);

    if (gtk_widget_is_sensitive(sr->extra[4])) {
	/* "gappy" MA lags activated */
	txt = gtk_entry_get_text(GTK_ENTRY(sr->extra[4]));
	add_to_cmdlist(sr, arma_lag_string(s, txt));
	add_to_cmdlist(sr, "; ");
	malags = gretl_strdup(txt);
    } else {
	/* regular max MA lag */
	arma_q = spinner_get_int(sr->extra[3]);
	sprintf(s, "%d ; ", arma_q);
	add_to_cmdlist(sr, s);
    } 

    if (sr->extra[5] != NULL) {
	arma_P = spinner_get_int(sr->extra[5]);
	arima_D = spinner_get_int(sr->extra[6]);
	arma_Q = spinner_get_int(sr->extra[7]);

	if (arma_P > 0 || arima_D > 0 || arma_Q > 0) {
	    sprintf(s, "%d %d %d ; ", arma_P, arima_D, arma_Q);
	    add_to_cmdlist(sr, s);
	}
    }
}

static void add_pdq_vals_to_cmdlist (selector *sr)
{
    char s[32] = {0};

    if (sr->ci == GARCH) {
	garch_p = spinner_get_int(sr->extra[0]);
	garch_q = spinner_get_int(sr->extra[1]);
	sprintf(s, "%d %d ; ", garch_p, garch_q);
    } else if (sr->ci == DPANEL) {
	dpd_p = spinner_get_int(sr->extra[0]);
	sprintf(s, "%d ; ", dpd_p);
    } else if (sr->ci == ARCH) {
	int p = spinner_get_int(sr->extra[0]);

	sprintf(s, "%d ", p);
    } 

    add_to_cmdlist(sr, s);
}

static void read_ellipse_alpha (selector *sr)
{
    if (sr->extra[0] != NULL) {
	char s[8];
	double cval;

	cval = gtk_spin_button_get_value(GTK_SPIN_BUTTON(sr->extra[0]));
	sprintf(s, "%g ", 1 - cval);
	add_to_cmdlist(sr, s);
    }
}

/* Take the stored preferred laglist for a variable (if any) and
   convert to a string specification for adding to the regression
   command line.
*/

static char *
discrete_lags_string (const char *vname, const int *laglist,
		      char context)
{
    int len = strlen(vname) + 4;
    gchar *tmp;
    char *s;
    int i, li;
    int err = 0;

    s = malloc(len + laglist[0] * 6);

    if (s != NULL) {
	sprintf(s, " %s(", vname);
	for (i=1; i<=laglist[0]; i++) {
	    li = laglist[i];
	    if (li > 999) {
		err = 1;
		break;
	    }
	    tmp = g_strdup_printf("%s%d", (li > 0)? "-" : "", li);
	    strcat(s, tmp);
	    if (i < laglist[0]) {
		strcat(s, ", ");
	    } else {
		strcat(s, ")");
	    }
	    g_free(tmp);
	}

	if (err) {
	    free(s);
	    s = NULL;
	}
    }

    return s;
}  

int selector_get_VAR_order (const selector *sr)
{
    return spinner_get_int(sr->extra[0]);
}

/* for use in constructing command list, possibly with
   embedded lags */

static char *get_lagpref_string (int v, char context,
				 selector *sr)
{
    const char *vname = dataset->varname[v];
    const int *laglist;
    int lmin, lmax;
    char *s = NULL;

    if (v == 0) {
	if (context == LAG_Y_X || context == LAG_Y_W) {
	    /* const as dependent: empty lags string */
	    return g_strdup("");
	} else {
	    /* const as indep var: just return itself */
	    return g_strdup(" 0");
	}
    }

    get_lag_preference(v, &lmin, &lmax, &laglist, context, sr);

    if (laglist != NULL) {
	s = discrete_lags_string(vname, laglist, context);
    } else if (lmin != lmax) {
	s = g_strdup_printf(" %s(%s%d to -%d)", vname, (lmin > 0)? "-" : "",
			    lmin, lmax);
    } else if (lmin != 0) {
	s = g_strdup_printf(" %s(%s%d)", vname, (lmin > 0)? "-" : "",
			    lmin);
    } else if (context != LAG_Y_X && context != LAG_Y_W) {
	s = g_strdup_printf(" %d", v);
    }

#if LDEBUG
    if (s != NULL) {
	fprintf(stderr, "get_lagpref_string (v=%d, context=%d):\n"
		" constructed s = '%s'\n", v, (int) context, s);
    }
#endif

    return s;
} 

static int maybe_resize_recorder_lists (selector *sr, int n)
{
    int err = 0;

    if (MODEL_CODE(sr->ci) || VEC_CODE(sr->ci)) {
	int *newlist;

	if (MODEL_CODE(sr->ci)) {
	    newlist = gretl_list_resize(&xlist, n);
	} else {
	    newlist = gretl_list_resize(&veclist, n);
	}
	if (newlist == NULL) {
	    err = E_ALLOC;
	}
    }

    return err;
}

static int maybe_resize_exog_recorder_lists (selector *sr, int n)
{
    int err = 0;

    if (USE_ZLIST(sr->ci) || USE_VECXLIST(sr->ci)) {
	int *newlist;

	if (USE_ZLIST(sr->ci)) {
	    newlist = gretl_list_resize(&instlist, n);
	} else {
	    newlist = gretl_list_resize(&vecxlist, n);
	}
	if (newlist == NULL) {
	    err = E_ALLOC;
	}
    } 

    return err;
}

static void nonparam_record_xvar (const char *s)
{
    int k;

    if (sscanf(s, "%d", &k) == 1 && k > 0) {
	np_xvar = k;
    }
}

static void get_rvars1_data (selector *sr, int rows, int context)
{
    GtkTreeModel *model;
    GtkTreeIter iter;
    gint rvar, lag;
    gchar *rvstr;
    int added = 0;
    int i, j = 1;

    if ((SAVE_DATA_ACTION(sr->ci) || sr->ci == EXPORT) && 
	sr->ci != COPY_CSV && rows == sr->n_left) {
	/* saving/exporting all available series: leave the 
	   list blank in case it overflows 
	*/
	return;
    }   

    sr->n_left = 0;

    model = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->rvars1));
    gtk_tree_model_get_iter_first(model, &iter);

    for (i=0; i<rows; i++) {
	gtk_tree_model_get(model, &iter, COL_ID, &rvar, COL_LAG, &lag, -1);

	if (is_lag_dummy(rvar, lag, context)) {
	    gtk_tree_model_iter_next(model, &iter);
	    continue;
	}

	if (context) {
	    rvstr = get_lagpref_string(rvar, context, sr);
	} else {
	    rvstr = g_strdup_printf(" %d", rvar);
	}

	if (rvstr == NULL) {
	    sr->error = E_ALLOC;
	    break;
	} else {
	    add_to_cmdlist(sr, rvstr);
	    g_free(rvstr);
	    added++;
	}

	/* save for future reference */
	if (MODEL_CODE(sr->ci) && xlist != NULL) {
	    xlist[j++] = rvar;
	} else if (VEC_CODE(sr->ci) && veclist != NULL) {
	    veclist[j++] = rvar;
	}

	gtk_tree_model_iter_next(model, &iter);
    }

#if 0
    if (MODEL_CODE(sr->ci)) {
	printlist(xlist, "xlist");
    }
#endif

    if (sr->ci == ARMA && added && !(sr->opts & OPT_N)) {
	/* add const explicitly unless forbidden */
	add_to_cmdlist(sr, " 0");
    }
}

static int veclist_add_term (int **listp, int *jp, int v)
{
    int n = (*listp)[0];

    if (*jp > n) {
	gretl_list_append_term(listp, v);
	if (*listp == NULL) {
	    return E_ALLOC;
	} else {
	    *jp += 1;
	}
    } else {
	(*listp)[*jp] = v;
	*jp += 1;
    }

    return 0;
}

/* VECM: parse out the exogenous vars as either restricted
   or unrestricted.  Right now we don't attempt to combine
   this with auto lag selection ("Lags" button), but maybe
   we should */

static int get_vecm_exog_list (selector *sr, int rows, 
			       GtkTreeModel *mod)
{
    GtkTreeIter iter;
    int *xlist = NULL, *zlist = NULL;
    gchar *flag = NULL;
    gchar *tmp = NULL;
    int lag, i, j = 1;
    int v, err = 0;

    gtk_tree_model_get_iter_first(mod, &iter);

    for (i=0; i<rows && !err; i++) {
	flag = NULL;
	lag = 0;

	gtk_tree_model_get(mod, &iter, COL_ID, &v, COL_LAG, &lag, 
			   COL_FLAG, &flag, -1);

	if (flag == NULL) {
	    err = E_DATA;
	} else if (lag != 0) {
	    v = laggenr(v, lag, dataset);
	    if (v < 0) {
		err = E_DATA;
	    }
	}

	if (!err) {
	    if (*flag == 'U') {
		/* unrestricted terms */
		gretl_list_append_term(&xlist, v);
		if (xlist == NULL) {
		    err = E_ALLOC;
		}
	    } else if (*flag == 'R') {
		/* restricted terms */
		gretl_list_append_term(&zlist, v);
		if (zlist == NULL) {
		    err = E_ALLOC;
		}		
	    } else {
		err = E_DATA;
	    }
	    g_free(flag);
	} 

	gtk_tree_model_iter_next(mod, &iter);
    }

    if (!err) {
	if (xlist != NULL) {
	    tmp = gretl_list_to_string(xlist);
	    add_to_cmdlist(sr, tmp);
	    g_free(tmp);
	    for (i=1; i<=xlist[0] && !err; i++) {
		v = xlist[i];
		err = veclist_add_term(&vecxlist, &j, v);
	    }
	    free(xlist);
	} 

	if (zlist != NULL && !err) {
	    add_to_cmdlist(sr, " ;");
	    tmp = gretl_list_to_string(zlist);
	    add_to_cmdlist(sr, tmp);
	    g_free(tmp);
	    err = veclist_add_term(&vecxlist, &j, LISTSEP);
	    for (i=1; i<=zlist[0] && !err; i++) {
		v = zlist[i];
		err = veclist_add_term(&vecxlist, &j, v);
	    }
	    free(zlist);	    
	}
    }

    if (err) {
	gui_errmsg(err);
    }    

    return err;
}

/* get the component of the model (or whatever) specification from the
   secondary list box on the right, if applicable */

static int get_rvars2_data (selector *sr, int rows, int context)
{
    GtkTreeModel *model;
    GtkTreeIter iter;
    gint exog, lag, ynum;
    int *reclist = NULL;
    gchar *tmp;
    int ncols, i, j = 1;
    int err = 0;

    model = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->rvars2));
    ncols = gtk_tree_model_get_n_columns(model);

    if (USE_RXLIST(sr->ci) && ncols == 4) {
	return get_vecm_exog_list(sr, rows, model);
    }

    gtk_tree_model_get_iter_first(model, &iter);

    if (USE_ZLIST(sr->ci)) {
	reclist = instlist;
    } else if (USE_VECXLIST(sr->ci)) {
	reclist = vecxlist;
    }

    ynum = selector_get_depvar_number(sr);

    for (i=0; i<rows; i++) {

	gtk_tree_model_get(model, &iter, COL_ID, &exog, COL_LAG, &lag, -1);

	if (IV_MODEL(sr->ci) && exog == ynum && lag == 0) {
	    /* HECKIT? */
	    errbox(_("You can't use the dependent variable as an instrument"));
	    err = 1;
	    break;
	}
		
	if (is_lag_dummy(exog, lag, context)) {
	    gtk_tree_model_iter_next(model, &iter);
	    continue;
	}

	if (context) {
	    tmp = get_lagpref_string(exog, context, sr);
	} else {
	    tmp = g_strdup_printf(" %d", exog);
	}

	add_to_cmdlist(sr, tmp);
	g_free(tmp);

	if (reclist != NULL) {
	    reclist[j++] = exog;
	}

	gtk_tree_model_iter_next(model, &iter);
    }

    return err;
}

static void read_quantreg_extras (selector *sr)
{
    GtkWidget *w = gtk_bin_get_child(GTK_BIN(sr->extra[0]));
    const gchar *s = gtk_entry_get_text(GTK_ENTRY(w));
	
    if (s == NULL || *s == '\0') {
	warnbox(_("You must specify a quantile"));
	sr->error = 1;
    } else {
	/* convert the GUI string to what ought to be a valid
	   numerical matrix specification */
	gchar *tmp = g_strdup_printf("{%s}", s);
	gretl_matrix *m;

	comma_separate_numbers(tmp);
	m = generate_matrix(tmp, dataset, &sr->error);
	gretl_matrix_free(m);

	if (sr->error) {
	    warnbox(_("Invalid quantile specification"));
	} else {
	    add_to_cmdlist(sr, tmp);
	    add_to_cmdlist(sr, " ");
	}
	g_free(tmp);
    }

    w = sr->extra[1];

    if (!sr->error && w != NULL && gtk_widget_is_sensitive(w)) {
	GtkAdjustment *adj;

	adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(w));
	set_optval_double(QUANTREG, OPT_I, gtk_adjustment_get_value(adj));
    }
}

static void read_logistic_extras (selector *sr)
{
    GtkWidget *w = sr->extra[1];

    if (w != NULL && gtk_widget_is_sensitive(w)) {
	GtkAdjustment *adj;

	adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(w));
	set_optval_double(LOGISTIC, OPT_M, gtk_adjustment_get_value(adj));
    }
} 

static void read_omit_cutoff (selector *sr)
{
    if (sr->extra[0] != NULL && gtk_widget_is_sensitive(sr->extra[0])) {
	double val;

	val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(sr->extra[0]));
	if (val != 0.10) {
	    set_optval_double(OMIT, OPT_A, val);
	}
    }
}

static void read_reprobit_quadpoints (selector *sr)
{
    if (sr->extra[0] != NULL && GTK_IS_SPIN_BUTTON(sr->extra[0])) {
	int qp = spinner_get_int(sr->extra[0]);

	set_optval_int(PROBIT, OPT_G, qp);
    }
}

#define TOBIT_UNSET -1.0e300

static void read_tobit_limits (selector *sr)
{
    double lval = 0, rval = NADBL;
    const char *s;
    int err = 0;

    s = gtk_entry_get_text(GTK_ENTRY(sr->extra[0]));
    if (*s != '\0') {
	if (strcmp(s, "NA") == 0) {
	    lval = TOBIT_UNSET;
	} else {
	    err = check_atof(s);
	    if (err) {
		warnbox(gretl_errmsg_get());
		sr->error = 1;
		return;
	    } else {
		lval = atof(s);
	    }
	}
    }

    s = gtk_entry_get_text(GTK_ENTRY(sr->extra[1]));
    if (*s != '\0' && strcmp(s, "NA")) {
	err = check_atof(s);
	if (err) {
	    warnbox(gretl_errmsg_get());
	    sr->error = 1;
	    return;
	} else {	
	    rval = atof(s);
	}
    }

    /* record the user's choices */
    tobit_lo = (lval == TOBIT_UNSET)? NADBL : lval;
    tobit_hi = rval;

    if (lval == 0 && na(rval)) {
	; /* the default, no-op */
    } else {
	if (lval != TOBIT_UNSET) {
	    sr->opts |= OPT_L;
	    set_optval_double(TOBIT, OPT_L, lval);
	}
	if (!na(rval)) {
	    sr->opts |= OPT_M;
	    set_optval_double(TOBIT, OPT_M, rval);
	}
    }
}

static void read_np_extras (selector *sr)
{
    char s[16];

    if (sr->ci == LOESS) {
	int d = spinner_get_int(sr->extra[1]);
	double q;

	q = gtk_spin_button_get_value(GTK_SPIN_BUTTON(sr->extra[2]));
	sprintf(s, " d=%d q=%g", d, q);
	add_to_cmdlist(sr, s);
	if (button_is_active(sr->extra[3])) {
	    sr->opts |= OPT_R;
	}
    } else if (sr->ci == NADARWAT) {
	double h;

	h = gtk_spin_button_get_value(GTK_SPIN_BUTTON(sr->extra[1]));
	sprintf(s, " h=%g", h);
	add_to_cmdlist(sr, s);
	if (button_is_active(sr->extra[2])) {
	    sr->opts |= OPT_O;
	}	
    }
}

static int cluster_option_is_active (selector *sr)
{
    GtkWidget *hcb = sr->hccme_button;
 
    if (hcb != NULL && gtk_widget_is_sensitive(hcb) &&
	GTK_IS_BUTTON(hcb)) {
	const gchar *s = gtk_button_get_label(GTK_BUTTON(hcb));

	return s != NULL && strcmp(s, _("Cluster")) == 0;
    } else {
	return 0;
    }
}

static void maybe_read_cluster_var (selector *sr)
{
    if (cluster_option_is_active(sr)) {
	set_optval_string(sr->ci, OPT_C, cluster_var);
	sr->opts |= OPT_C;
    }
}

static void maybe_read_var_hac_option (selector *sr)
{
    GtkWidget *b = sr->hccme_button;

    if (b != NULL && gtk_widget_is_sensitive(b) &&
	GTK_IS_COMBO_BOX(b)) {
	gint i = gtk_combo_box_get_active(GTK_COMBO_BOX(b));

	if (i == 1) {
	    sr->opts |= OPT_H;
	}
    }
}

#define extra_widget_get_int(c) (c == HECKIT ||		\
	                         c == BIPROBIT ||       \
                                 c == INTREG ||		\
                                 c == COUNTMOD ||	\
				 c == DURATION ||	\
                                 c == WLS ||		\
                                 THREE_VARS_CODE(c) ||  \
				 NONPARAM_CODE(c))

#define offer_cluster_option(c) (dataset_is_cross_section(dataset) && \
				 cluster_option_ok(c))

static void parse_extra_widgets (selector *sr, char *endbit)
{
    const gchar *txt = NULL;
    char numstr[8];
    int k = 0;

    if (offer_cluster_option(sr->ci)) {
	maybe_read_cluster_var(sr);
    } 

    if (sr->ci == QUANTREG) {
	read_quantreg_extras(sr);
	return;
    }

    if (sr->ci == LOGISTIC) {
	read_logistic_extras(sr);
	return;
    }    

    if (sr->ci == ELLIPSE) {
	read_ellipse_alpha(sr);
	return;
    }

    if (sr->ci == OMIT) {
	read_omit_cutoff(sr);
	return;
    }

    if (sr->ci == TOBIT) {
	read_tobit_limits(sr);
	return;
    }

    if (sr->ci == REPROBIT) {
	read_reprobit_quadpoints(sr);
	return;
    }

    if (sr->ci == WLS || sr->ci == COUNTMOD || sr->ci == DURATION ||
	sr->ci == AR || sr->ci == HECKIT || sr->ci == BIPROBIT ||
	sr->ci == INTREG || THREE_VARS_CODE(sr->ci) || 
	NONPARAM_CODE(sr->ci)) {
	txt = gtk_entry_get_text(GTK_ENTRY(sr->extra[0]));
	if (txt == NULL || *txt == '\0') {
	    if (sr->ci == WLS) {
		warnbox(_("You must select a weight variable"));
		sr->error = 1;
	    } else if (sr->ci == AR) {
		warnbox(_("You must specify a list of lags"));
		sr->error = 1;
	    } else if (sr->ci == HECKIT) {
		warnbox(_("You must specify a selection variable"));
		sr->error = 1;
	    } else if (sr->ci == BIPROBIT) {
		warnbox(_("You must specify a second dependent variable"));
		sr->error = 1;
	    } else if (sr->ci == INTREG) {
		warnbox(_("You must specify an upper bound variable"));
		sr->error = 1;
	    } else if (sr->ci == ANOVA) {
		warnbox(_("You must specify a treatment variable"));
		sr->error = 1;
	    } else if (NONPARAM_CODE(sr->ci)) {
		warnbox(_("You must specify an independent variable"));
		sr->error = 1;
	    } else if (THREE_VARS_CODE(sr->ci)) { 
		warnbox(("You must select a Y-axis variable"));
		sr->error = 1;
	    } else if (sr->ci == COUNTMOD || sr->ci == DURATION) {
		/* empty 'extra' field is OK */
		return;
	    }
	}
    } 

    if (sr->error) {
	return;
    }

    if (extra_widget_get_int(sr->ci)) {
	k = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(sr->extra[0]), "data"));
    }

    if (sr->ci == ANOVA) {
	sprintf(numstr, " %d", k);
	add_to_cmdlist(sr, numstr);
    } else if (sr->ci == WLS || THREE_VARS_CODE(sr->ci)) {
	sprintf(numstr, "%d ", k);
	add_to_cmdlist(sr, numstr);
	if (sr->ci == WLS) {
	    wtvar = k;
	}
    } else if (sr->ci == INTREG) {
	sprintf(numstr, " %d ", k);
	add_to_cmdlist(sr, numstr);
	hivar = k;
    } else if (sr->ci == COUNTMOD) {
	sprintf(endbit, " ; %d", k);
	offvar = k;
    } else if (sr->ci == DURATION) {
	sprintf(endbit, " ; %d", k);
	censvar = k;
    } else if (sr->ci == HECKIT) {
	sprintf(endbit, " %d", k);
	selvar = k;
    } else if (sr->ci == BIPROBIT) {
	sprintf(endbit, " %d", k);
    } else if (NONPARAM_CODE(sr->ci)) {
	sprintf(endbit, " %d", k);
    } else if (sr->ci == AR) {
	free(arlags);
	arlags = gretl_strdup(txt);
	add_to_cmdlist(sr, txt);
	add_to_cmdlist(sr, " ; ");
    } 
}

static void vec_get_spinner_data (selector *sr, int *order)
{
    const int *llist;
    char *dvlags;
    char numstr[8];

    /* lag order from global spinner */
    *order = spinner_get_int(sr->extra[0]);

    /* possible list of specific lags */
    llist = get_VAR_lags_list();

    if (llist != NULL) {
	/* "gappy" lag specification for VAR */
	dvlags = gretl_list_to_lags_string(llist, &sr->error);
	if (dvlags != NULL) {
	    add_to_cmdlist(sr, dvlags);
	    free(dvlags);
	}
    } else {
	sprintf(numstr, "%d", *order);
	add_to_cmdlist(sr, numstr);
    }	

    if (sr->ci == VECM) {
	/* cointegration rank */
	jrank = spinner_get_int(sr->extra[1]);
	sprintf(numstr, " %d", jrank);
	add_to_cmdlist(sr, numstr);
    }
}

static void parse_depvar_widget (selector *sr, char *endbit, 
				 char **dvlags,
				 char **idvlags)
{
    int ynum = selector_get_depvar_number(sr);

    if (ynum < 0) {
	topslot_empty(sr->ci);
	sr->error = 1;
    } else {
	char numstr[8];

	if (sr->ci == GR_XY || sr->ci == GR_IMP) {
	    sprintf(endbit, " %d", ynum);
	} else if (sr->ci == BIPROBIT) {
	    sprintf(numstr, "%d", ynum);
	    add_to_cmdlist(sr, numstr);
	    add_to_cmdlist(sr, endbit);
	    *endbit = '\0';
	} else {
	    sprintf(numstr, "%d", ynum);
	    add_to_cmdlist(sr, numstr);
	}
	if (select_lags_depvar(sr->ci) && dataset_lags_ok(dataset)) {
	    *dvlags = get_lagpref_string(ynum, LAG_Y_X, NULL);
	    if (USE_ZLIST(sr->ci)) {
		*idvlags = get_lagpref_string(ynum, LAG_Y_W, NULL);
	    }
	}
	if (sr->default_check != NULL) {
	    if (button_is_active(sr->default_check)) {
		default_y = ynum;
	    } else {
		default_y = -1;
	    }
	} else if (sr->ci == INTREG) {
	    lovar = ynum;
	}
    }
}

static void parse_third_var_slot (selector *sr)
{
    const gchar *txt = gtk_entry_get_text(GTK_ENTRY(sr->rvars1));
    char numstr[8];

    if (txt == NULL || *txt == '\0') {
	if (sr->ci == ANOVA) {
	    /* third var is optional */
	    return;
	} else if (sr->ci == GR_3D) {
	    warnbox(_("You must select a Z-axis variable"));
	} else if (sr->ci == GR_DUMMY || sr->ci == GR_FBOX) {
	    warnbox(_("You must select a factor variable"));
	} else {
	    warnbox(_("You must select a control variable"));
	}
	sr->error = 1;
    } else {
	int v = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(sr->rvars1), 
						  "data"));
	sprintf(numstr, " %d", v);
	add_to_cmdlist(sr, numstr);
    }
}

static void selector_cancel_unavailable_options (selector *sr)
{
    if (sr->ci == ARMA) {
	if ((sr->opts & OPT_H) && !gtk_widget_is_sensitive(sr->hess_button)) {
	    sr->opts ^= OPT_H;
	}
    }
}

static void get_anova_list (selector *sr)
{
    /* get response var */
    parse_depvar_widget(sr, NULL, NULL, NULL);
    if (sr->error < 0) {
	return;
    } 

    /* get treatment var */
    parse_extra_widgets(sr, NULL);
    if (sr->error) {
	return;
    }

    /* get (optional) block var */
    parse_third_var_slot(sr);    
}

/* main function for building a command list from information stored
   in the various selector widgets */

static void compose_cmdlist (selector *sr)
{
    gint rows = 0, realrows = 0;
    char endbit[32] = {0};
    char *dvlags = NULL;
    char *idvlags = NULL;
    int context = 0;
    int order = 0;

    sr->error = 0;
    sr->cmdlist = mymalloc(MAXLEN); 
    if (sr->cmdlist == NULL) {
	return;
    }

    *sr->cmdlist = '\0';

    if (sr->ci == INTREG) {
	parse_depvar_widget(sr, endbit, &dvlags, &idvlags);
	if (!sr->error) {
	    parse_extra_widgets(sr, endbit);
	}
	goto int_next;
    } 

    if (sr->ci == ANOVA) {
	/* special: either 2 or 3 variables selected */
	get_anova_list(sr);
	return;
    }

    /* deal with content of "extra" widgets */
    if (sr->ci == ARMA) {
	arma_spec_to_cmdlist(sr);
    } else if (sr->ci == ARCH || 
	       sr->ci == GARCH || 
	       sr->ci == DPANEL) {
	add_pdq_vals_to_cmdlist(sr);
    } else if (VEC_CODE(sr->ci)) {
	vec_get_spinner_data(sr, &order);
	if (sr->ci == VAR && !sr->error) {
	    maybe_read_var_hac_option(sr);
	}
    } else {
	parse_extra_widgets(sr, endbit);
    }

    /* deal with the "depvar" widget */
    if (!sr->error && sr->depvar != NULL) {
	parse_depvar_widget(sr, endbit, &dvlags, &idvlags);
    }

 int_next:

    /* bail out if things have gone wrong already */
    if (sr->error) {
	return;
    }

    if (sr->ci == GR_FBOX || THREE_VARS_CODE(sr->ci)) { 
	parse_third_var_slot(sr);
	return;
    } 

    /* count the rows (variables) in the "primary" right-hand selection
       list box */
    rows = varlist_row_count(sr, SR_RVARS1, &realrows);

    if (sr->ci == SCATTERS) {
	if (rows > 0) {
	    add_to_cmdlist(sr, " ;");
	} else {
	    sr->error = E_ARGS;
	    gui_errmsg(sr->error);
	    return;
	}
    }    

    if ((sr->ci == COINT || sr->ci == COINT2 || sr->ci == VECM) && rows < 2) {
	warnbox(_("You must select two or more endogenous variables"));
	sr->error = 1;
	return;
    } else if ((sr->ci == VAR || sr->ci == VLAGSEL) && rows < 1) {
	warnbox(_("You must select a dependent variable"));
	sr->error = 1;
	return;
    } else if (FNPKG_CODE(sr->ci) && rows < 1) {
	warnbox(_("You must specify a public interface"));
	sr->error = 1;
	return;
    } 

    if (realrows > 0) {
	maybe_resize_recorder_lists(sr, realrows);
    }

    /* primary RHS varlist */
    if (sr->rvars1 != NULL) {
	context = sr_get_lag_context(sr, SR_RVARS1);
	get_rvars1_data(sr, rows, context);
    }

    /* cases with a (possibly optional) secondary RHS list */
    if (USE_ZLIST(sr->ci) || USE_VECXLIST(sr->ci) || FNPKG_CODE(sr->ci)) {
	rows = varlist_row_count(sr, SR_RVARS2, &realrows);
	if (rows > 0) {
	    if (realrows > 0) {
		maybe_resize_exog_recorder_lists(sr, realrows);
	    }
	    context = sr_get_lag_context(sr, SR_RVARS2);

	    if (USE_ZLIST(sr->ci) && dvlags != NULL) {
		add_to_cmdlist(sr, dvlags);
		free(dvlags);
		dvlags = NULL;
	    }

	    if (sr->ci == HECKIT) {
		add_to_cmdlist(sr, " ;");
		add_to_cmdlist(sr, endbit);
		*endbit = '\0';
	    } else if (*sr->cmdlist != '\0') {
		add_to_cmdlist(sr, " ;");
	    }

	    sr->error = get_rvars2_data(sr, rows, context);
	} else if (IV_MODEL(sr->ci)) {
	    warnbox(_("You must specify a set of instrumental variables"));
	    sr->error = 1;
	} else if (sr->ci == HECKIT) {
	    warnbox(_("You must specify regressors for the selection equation"));
	    sr->error = 1;
	} 
    }

    /* deal with any trailing strings */
    if (!sr->error) {
	if (endbit[0] != '\0') {
	    add_to_cmdlist(sr, endbit);
	} else if (dvlags != NULL) {
	    add_to_cmdlist(sr, dvlags);
	    free(dvlags);
	} else if (idvlags != NULL) {
	    add_to_cmdlist(sr, idvlags);
	    free(idvlags);
	}
	if (NONPARAM_CODE(sr->ci)) {
	    read_np_extras(sr);
	}
    }

    if ((sr->ci == SCATTERS) && !sr->error) {
	int xstate;

	xstate = gtk_combo_box_get_active(GTK_COMBO_BOX(multiplot_menu));
	if (xstate) {
	    reverse_list(sr->cmdlist);
	}
    }

#if 0
    fprintf(stderr, "sr->cmdlist:\n'%s'\n", sr->cmdlist);
#endif

    if (!sr->error) {
	/* record some choices as defaults */
	if (sr->ci == VECM || sr->ci == VAR || sr->ci == VLAGSEL) {
	    want_seasonals = (sr->opts & OPT_D)? 1 : 0;
	}
	if (sr->ci == VECM || sr->ci == VAR || sr->ci == ARCH) {
	    default_order = (order < 0)? -order : order;
	}
	if (sr->ci == VAR || sr->ci == VLAGSEL) {
	    vartrend = (sr->opts & OPT_T)? 1 : 0;
	} else if (sr->ci == ARMA) {
	    arma_const = (sr->opts & OPT_N)? 0 : 1;
	    arma_hessian = (sr->opts & OPT_G)? 0 : 1;
	    arima_xdiff = (sr->opts & OPT_Y)? 0 : 1;
	    arma_x12 = (sr->opts & OPT_X)? 1 : 0;
	} else 	if (sr->ci == GARCH) {
	    garch_const = (sr->opts & OPT_N)? 0 : 1;
	    if (sr->opts & OPT_F) {
		sr->opts &= ~OPT_F;
		libset_set_bool(USE_FCP, 1);
	    } else {
		libset_set_bool(USE_FCP, 0);
	    }
	} else if (sr->ci == LOGIT || sr->ci == PROBIT) {
	    lp_pvals = (sr->opts & OPT_P)? 1 : 0;
	} else if (sr->ci == DPANEL) {
	    dpd_2step = (sr->opts & OPT_T)? 1 : 0;
	    dpd_asy = (sr->opts & OPT_A)? 1 : 0;
	} else if (NONPARAM_CODE(sr->ci)) {
	    nonparam_record_xvar(endbit);
	}

	/* FIXME: for now we'll make dpanel backward compatible
	   with arbond (but this should be a settable option)
	*/
	if (sr->ci == DPANEL) {
	    sr->opts |= OPT_X;
	}

	/* panel: scrub --nerlove if not doing random effects */
	if (sr->ci == PANEL) {
	    if ((sr->opts & OPT_N) && !(sr->opts & OPT_U)) {
		sr->opts &= ~OPT_N;
	    }
	}

	verbose = (sr->opts & OPT_V)? 1 : 0;
    }

    selector_cancel_unavailable_options(sr);
}

static void cancel_selector (GtkWidget *widget, selector *sr)
{
    if (open_selector != NULL) {
	gtk_widget_destroy(sr->dlg);
    }
}

static void destroy_selector (GtkWidget *w, selector *sr) 
{
    if (sr->blocking) {
	gtk_main_quit();
    }

    if (sr->state_pushed) {
	pop_program_state();
    }

    free(sr->cmdlist);
    free(sr);

    open_selector = NULL;
}

static char *est_str (int cmdnum)
{
    switch (cmdnum) {
    case OLS:
	return N_("OLS");
    case HSK:
	return N_("Heteroskedasticity corrected");
    case AR1:
	return N_("AR(1)");
    case LOGIT:
	return N_("Logit");
    case OLOGIT:
	return N_("Ordered Logit");
    case MLOGIT:
	return N_("Multinomial Logit");
    case PROBIT:
	return N_("Probit");
    case OPROBIT:
	return N_("Ordered Probit");
    case REPROBIT:
	return N_("Random effects (binary) probit");
    case TOBIT:
	return N_("Tobit");
    case HECKIT:
	return N_("Heckit");
    case BIPROBIT:
	return N_("Bivariate probit");
    case LOGISTIC:
	return N_("Logistic");
    case COUNTMOD:
	return N_("Count data model");
    case DURATION:
	return N_("Duration model");
    case PANEL:
	return N_("Panel model");
    case PANEL_WLS:
	return N_("Groupwise WLS");
    case PANEL_B:
	return N_("Between-groups model");
    case DPANEL:
	return N_("Dynamic panel model");
    case WLS:
	return N_("Weighted least squares");
    case IVREG:
	return N_("Two-stage least squares");
    case IV_LIML:
	return N_("Limited information maximum likelihood");
    case IV_GMM:
	return N_("Generalized method of moments");
    case AR:
	return N_("Autoregressive model");
    case ARMA:
	return N_("ARIMA");
    case ARCH:
	return N_("ARCH");
    case GARCH:
	return N_("GARCH");
    case VAR:
	return N_("VAR");
    case VLAGSEL:
	return N_("VAR lag selection");
    case VECM:
	return N_("VECM");
    case LAD:
	return N_("LAD");
    case QUANTREG:
	return N_("Quantile regression");
    case INTREG:
	return N_("Interval regression");
    case COINT:
    case COINT2:
	return N_("Cointegration");
    case MPOLS:
	return N_("Multiple precision OLS");
    case LOESS:
	return N_("Loess");
    case NADARWAT:
	return N_("Nadaraya-Watson");
    default:
	return "";
    }
}

static char *extra_var_string (int ci)
{
    switch (ci) {
    case WLS:
	return N_("Weight variable");
    case COUNTMOD:
	return N_("Offset variable");
    case DURATION:
	return N_("Censoring variable");
    case HECKIT:
	return N_("Selection variable");
    case BIPROBIT:
	return N_("Dependent variable 2");
    case AR:
	return N_("List of AR lags");
    case QUANTREG:
	return N_("Desired quantile(s)");
    case INTREG:
	return N_("Upper bound variable");
    case GR_DUMMY:
    case GR_3D:
    case GR_XYZ:	
	return N_("Y-axis variable");
    case ANOVA:
	return N_("Treatment variable");
    case LOESS:
    case NADARWAT:
	return N_("Independent variable");
    default:
	return NULL;
    }
}

static gint flip_multiplot_axis (GtkComboBox *box, gpointer p)
{
    gint xstate = gtk_combo_box_get_active(box);

    if (xstate) {
	gtk_label_set_text(GTK_LABEL(multiplot_label), _("Y-axis variables"));
    } else {
	gtk_label_set_text(GTK_LABEL(multiplot_label), _("X-axis variables"));
    }

    return FALSE;
}

static GtkWidget *multiplot_popdown (int ci)
{
    GtkWidget *w = gtk_combo_box_text_new();

    combo_box_append_text(w, _("Y-axis variable"));
    combo_box_append_text(w, _("X-axis variable"));
    gtk_combo_box_set_active(GTK_COMBO_BOX(w), 0);

    g_signal_connect(G_OBJECT(GTK_COMBO_BOX(w)), "changed",
		     G_CALLBACK(flip_multiplot_axis), NULL);

    multiplot_menu = w;

    return w;
}

static gint set_count_data_option (GtkComboBox *box, selector *sr)
{
    gint i = gtk_combo_box_get_active(box);

    if (i == 0) {
	/* Poisson */
	sr->opts &= ~(OPT_N | OPT_M);
    } else if (i == 1) {
	/* NegBin 2 */
	sr->opts &= ~OPT_M;
	sr->opts |= OPT_N;
    } else {
	/* NegBin 1 */
	sr->opts |= OPT_M;
    }

    return FALSE;
}

static void build_count_data_popdown (selector *sr)
{
    GtkWidget *w = gtk_combo_box_text_new();
    GtkWidget *hbox, *label;

    combo_box_append_text(w, _("Poisson"));
    combo_box_append_text(w, _("NegBin 2"));
    combo_box_append_text(w, _("NegBin 1"));

    g_signal_connect(G_OBJECT(GTK_COMBO_BOX(w)), "changed",
		     G_CALLBACK(set_count_data_option), sr);

    hbox = gtk_hbox_new(FALSE, 5);
    label = gtk_label_new(_("Distribution:"));
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 5);

    if (model_opt & OPT_M) {
	sr->opts |= OPT_M;
	gtk_combo_box_set_active(GTK_COMBO_BOX(w), 2);
    } else if (model_opt & OPT_N) {
	sr->opts |= OPT_N;
	gtk_combo_box_set_active(GTK_COMBO_BOX(w), 1);
    } else {
	gtk_combo_box_set_active(GTK_COMBO_BOX(w), 0);
    }
}

static gint set_duration_option (GtkComboBox *box, selector *sr)
{
    gint i = gtk_combo_box_get_active(box);

    if (i == 0) {
	/* Weibull */
	sr->opts &= ~(OPT_E | OPT_L | OPT_Z);
    } else if (i == 1) {
	/* Exponential */
	sr->opts &= ~(OPT_L | OPT_Z);
	sr->opts |= OPT_E;
    } else if (i == 2) {
	/* Log-logistic */
	sr->opts &= ~(OPT_E | OPT_Z);
	sr->opts |= OPT_L;
    } else {
	/* Log-normal */
	sr->opts &= ~(OPT_E | OPT_L);
	sr->opts |= OPT_Z;
    }	

    return FALSE;
}

static void build_duration_popdown (selector *sr)
{
    GtkWidget *w = gtk_combo_box_text_new();
    GtkWidget *hbox, *label;

    combo_box_append_text(w, _("Weibull"));
    combo_box_append_text(w, _("Exponential"));
    combo_box_append_text(w, _("Log-logistic"));
    combo_box_append_text(w, _("Log-normal"));

    g_signal_connect(G_OBJECT(GTK_COMBO_BOX(w)), "changed",
		     G_CALLBACK(set_duration_option), sr);

    hbox = gtk_hbox_new(FALSE, 5);
    label = gtk_label_new(_("Distribution:"));
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 5);

    if (model_opt & OPT_E) {
	sr->opts |= OPT_E;
	gtk_combo_box_set_active(GTK_COMBO_BOX(w), 1);
    } else if (model_opt & OPT_L) {
	sr->opts |= OPT_L;
	gtk_combo_box_set_active(GTK_COMBO_BOX(w), 2);
    } else if (model_opt & OPT_Z) {
	sr->opts |= OPT_Z;
	gtk_combo_box_set_active(GTK_COMBO_BOX(w), 3);
    } else {
	gtk_combo_box_set_active(GTK_COMBO_BOX(w), 0);
    }
}

static gint set_gmm_est_option (GtkComboBox *box, selector *sr)
{
    gint i = gtk_combo_box_get_active(box);

    if (i == 0) {
	/* 1-step */
	sr->opts &= ~(OPT_I | OPT_T);
    } else if (i == 1) {
	/* 2-step */
	sr->opts |= OPT_T;
    } else {
	/* iterated */
	sr->opts |= OPT_I;
    }

    return FALSE;
}

static void build_gmm_popdown (selector *sr)
{
    GtkWidget *w = gtk_combo_box_text_new();
    GtkWidget *hbox;

    combo_box_append_text(w, _("One-step estimation"));
    combo_box_append_text(w, _("Two-step estimation"));
    combo_box_append_text(w, _("Iterated estimation"));

    g_signal_connect(G_OBJECT(GTK_COMBO_BOX(w)), "changed",
		     G_CALLBACK(set_gmm_est_option), sr);

    hbox = gtk_hbox_new(FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 5);

    if (model_opt & OPT_I) {
	sr->opts |= OPT_I;
	gtk_combo_box_set_active(GTK_COMBO_BOX(w), 2);
    } else if (model_opt & OPT_T) {
	sr->opts |= OPT_T;
	gtk_combo_box_set_active(GTK_COMBO_BOX(w), 1);
    } else {
	gtk_combo_box_set_active(GTK_COMBO_BOX(w), 0);
    }
}

static void table_add_left (selector *sr,
			    GtkWidget *child)
{
    guint xpad = 4, ypad = 2;

    gtk_table_attach(GTK_TABLE(sr->table), child,
		     0, 1, 0, sr->n_rows,
		     GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		     GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		     xpad, ypad);
}

static void maybe_add_row (selector *sr)
{
    if (sr->row + 1 > sr->n_rows) {
	sr->n_rows += 1;
	gtk_table_resize(GTK_TABLE(sr->table),
			 sr->n_rows, 3);
    }
}

static void table_add_mid (selector *sr,
			   GtkWidget *child)
{
    guint xpad = 4, ypad = 2;

    maybe_add_row(sr);
    gtk_table_attach(GTK_TABLE(sr->table), child,
		     1, 2, sr->row, sr->row+1,
		     0, 0,
		     xpad, ypad);
}

static void table_add_right (selector *sr,
			     GtkWidget *child,
			     int fixed)
{
    guint xpad = 4, ypad = 2;

    maybe_add_row(sr);
    gtk_table_attach(GTK_TABLE(sr->table), child,
		     2, 3, sr->row, sr->row+1,
		     GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		     fixed ? 0 : (GTK_EXPAND | GTK_SHRINK | GTK_FILL),
		     xpad, ypad);
    sr->row += 1;
}

static void table_add_vwedge (selector *sr)
{
    GtkWidget *h = gtk_hbox_new(FALSE, 0);

    maybe_add_row(sr);
    gtk_table_attach(GTK_TABLE(sr->table), h,
		     2, 3, sr->row, sr->row+1,
		     0, 0, 0, 2);
    sr->row += 1;
}

static void vbox_add_vwedge (GtkWidget *vbox)
{
    GtkWidget *h = gtk_hbox_new(FALSE, 0);
    
    gtk_box_pack_start(GTK_BOX(vbox), h, FALSE, FALSE, 2);
    gtk_widget_show(h);
}

static GtkWidget *pix_button (const guint8 *src, gchar *tip)
{
    GtkWidget *img, *button;
    GdkPixbuf *pbuf;

    pbuf = gdk_pixbuf_new_from_inline(-1, src, FALSE, NULL);
    img = gtk_image_new_from_pixbuf(pbuf);
    button = gtk_button_new();
    gtk_widget_set_size_request(button, BUTTON_WIDTH, -1);
    gtk_container_add(GTK_CONTAINER(button), img);
    gretl_tooltips_add(button, tip);
    g_object_unref(pbuf);

    return button;
}

static GtkWidget *name_entry_in_hbox (GtkWidget **pentry)
{
    GtkWidget *hbox, *entry;

    hbox = gtk_hbox_new(FALSE, 0);
    entry = gtk_entry_new();
    gtk_entry_set_max_length(GTK_ENTRY(entry), VNAMELEN - 1);
    gtk_entry_set_width_chars(GTK_ENTRY(entry), VNAME_WIDTH);
    gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);

    if (pentry != NULL) {
	*pentry = entry;
    }

    return hbox;
}

static GtkWidget *
entry_with_label_and_chooser (selector *sr,
			      gchar *label_string,
			      int label_active,
			      void (*clickfunc)())
{
    GtkWidget *tmp, *hbox, *entry;

    if (label_active) {
	tmp = multiplot_popdown(sr->ci);
	table_add_right(sr, tmp, 1);
    } else if (label_string != NULL) {
	tmp = gtk_label_new(label_string);
	table_add_right(sr, tmp, 1);
    }

    tmp = pix_button(choose_inline, _("Choose"));
    table_add_mid(sr, tmp);
    g_signal_connect(G_OBJECT(tmp), "clicked", 
		     G_CALLBACK(clickfunc), sr);

    hbox = name_entry_in_hbox(&entry);
    table_add_right(sr, hbox, 1);

    if (label_active || label_string != NULL) {
	if (clickfunc != set_third_var_callback) {
	    table_add_vwedge(sr);
	}
    }

    return entry;
}

static void build_x_axis_section (selector *sr, int v)
{
    if (sr->ci == SCATTERS) {
	sr->depvar = entry_with_label_and_chooser(sr, NULL, 1,
						  set_dependent_var_callback);
    } else if (sr->ci == GR_FBOX) {
	sr->depvar = entry_with_label_and_chooser(sr, _("Variable to plot"), 0,
						  set_dependent_var_callback);
    } else {
	sr->depvar = entry_with_label_and_chooser(sr, _("X-axis variable"), 0,
						  set_dependent_var_callback);
    }

    if (v > 0 && v < dataset->v) {
        gtk_entry_set_text(GTK_ENTRY(sr->depvar), dataset->varname[v]);
    }
}

static void maybe_activate_depvar_lags (GtkWidget *w, selector *sr)
{
    if (select_lags_depvar(sr->ci) && sr->lags_button != NULL) {
	const gchar *txt = gtk_entry_get_text(GTK_ENTRY(w));

	if (txt != NULL && *txt != 0) {
	    gtk_widget_set_sensitive(sr->lags_button, TRUE);
	}
    }
}

/* returns ID number of variable pre-inserted as dependent, or -1 */

static int build_depvar_section (selector *sr, int preselect)
{
    GtkWidget *tmp, *hbox;
    int defvar;

    if (sr->ci == INTREG) {
	defvar = (lovar > 0 && lovar < dataset->v)? lovar : -1;
    } else {
	if (default_y >= dataset->v) {
	    default_y = -1;
	}
	defvar = (preselect)? preselect : default_y;
    }

    if (sr->ci == INTREG) {
	tmp = gtk_label_new(_("Lower bound variable"));
    } else if (sr->ci == ANOVA) {
	tmp = gtk_label_new(_("Response variable"));
    } else if (sr->ci == BIPROBIT) {
	tmp = gtk_label_new(_("Dependent variable 1"));
    } else {
	tmp = gtk_label_new(_("Dependent variable"));
    }

    table_add_right(sr, tmp, 1);

    tmp = pix_button(choose_inline, _("Choose"));
    table_add_mid(sr, tmp);
    g_signal_connect(G_OBJECT(tmp), "clicked", 
		     G_CALLBACK(set_dependent_var_callback), sr);

    hbox = name_entry_in_hbox(&sr->depvar);
    g_signal_connect(G_OBJECT(sr->depvar), "changed",
		     G_CALLBACK(maybe_activate_depvar_lags), sr);
    if (defvar >= 0) {
        gtk_entry_set_text(GTK_ENTRY(sr->depvar), dataset->varname[defvar]);
    }
    table_add_right(sr, hbox, 1);

    if (sr->ci != INTREG && sr->ci != BIPROBIT) {
	sr->default_check = gtk_check_button_new_with_label(_("Set as default"));
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sr->default_check),
				     default_y >= 0);
	table_add_right(sr, sr->default_check, 1);
    } 

    if (sr->ci != DPANEL && sr->ci != BIPROBIT && sr->ci != TOBIT) {
	table_add_vwedge(sr);
    }

    return defvar;
}

/* In case we have a saved preference for the max lag of the
   endogenous vars in a VAR, set via the lags dialog, update this
   value from the global spinner 
*/

static void lag_order_sync (GtkSpinButton *b, selector *sr)
{
    if (gtk_widget_is_sensitive(GTK_WIDGET(b)) && 
	sr->extra[EXTRA_LAGS] != NULL) {
	int lmax = gtk_spin_button_get_value_as_int(b);

	set_VAR_max_lag(lmax);
    }
}

enum {
    LAG_ONLY,
    LAG_AND_RANK
};

static void lag_order_spin (selector *sr, int which)
{
    gdouble lag, minlag, maxlag;
    GtkWidget *hbox, *label, *spin;
    GtkAdjustment *adj;
    const char *labels[] = {
	N_("lag order:"),
	N_("rank:")
    };
    int i, nspin = (which == LAG_AND_RANK)? 2 : 1;

    maxlag = (dataset->n < 72)? (dataset->n / 2) : 36;
    minlag = (sr->ci == COINT)? 0 : 1;

    if (default_order > 0 && default_order <= maxlag) {
	lag = default_order;
    } else {
	lag = (dataset->pd > 12)? 12 : dataset->pd;
    }

    if (sr->ci == VLAGSEL) {
	minlag = 2;
	lag *= 2;
	if (lag > maxlag) {
	    lag = maxlag;
	}
    }

    for (i=0; i<nspin; i++) {
	hbox = gtk_hbox_new(FALSE, 5);
	if (sr->ci == VLAGSEL) {
	    label = gtk_label_new(_("maximum lag:"));
	} else {
	    label = gtk_label_new(_(labels[i]));
	}
	if (i == 0) {
	    /* lag order */
	    adj = (GtkAdjustment *) gtk_adjustment_new(lag, minlag, maxlag, 
						       1, 1, 0);
	} else {
	    /* rank */
	    adj = (GtkAdjustment *) gtk_adjustment_new(jrank, 1, 10, 
						       1, 1, 0);
	}
	spin = gtk_spin_button_new(adj, 1, 0);
	if (i == 1) {
	    gretl_tooltips_add(label, _("Number of cointegrating vectors"));
	}
	gtk_box_pack_end(GTK_BOX(hbox), spin, FALSE, FALSE, 5);
	gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	if (i == 0) {
	    /* cross-connect with lag preferences dialog */
	    if (get_VAR_lags_list() != NULL) {
		gtk_widget_set_sensitive(spin, FALSE);
	    }
	    g_signal_connect(G_OBJECT(spin), "value-changed",
			     G_CALLBACK(lag_order_sync), sr);
	}
	sr->extra[i] = spin;
	table_add_right(sr, hbox, 1);
    }
}

static void AR_order_spin (selector *sr)
{
    GtkWidget *tmp, *hbox;
    GtkAdjustment *adj;
    gdouble val, maxlag;

    hbox = gtk_hbox_new(FALSE, 5);

    if (sr->ci == ARCH) {
	tmp = gtk_label_new(_("ARCH order:"));
	val = dataset->pd;
	maxlag = 2 * dataset->pd;
    } else {
	/* dpanel */
	tmp = gtk_label_new(_("AR order:"));
	val = dpd_p;
	maxlag = 10;
	if (maxlag < dataset->pd - 2) {
	    maxlag = dataset->pd - 2;
	}
    }

    if (default_order > 0 && default_order <= maxlag) {
	val = default_order;
    }

    gtk_box_pack_start(GTK_BOX(hbox), tmp, TRUE, TRUE, 5);
    gtk_misc_set_alignment(GTK_MISC(tmp), 0.0, 0.5);
    adj = (GtkAdjustment *) gtk_adjustment_new(val, 1, maxlag, 1, 1, 0);
    sr->extra[0] = gtk_spin_button_new(adj, 1, 0);
    gtk_box_pack_start(GTK_BOX(hbox), sr->extra[0], FALSE, FALSE, 5);

    table_add_right(sr, hbox, 1);
}

static void extra_plotvar_box (selector *sr)
{
    const gchar *label;

    if (sr->ci == GR_3D) {
	label = N_("Z-axis variable");
    } else if (sr->ci == GR_DUMMY || sr->ci == GR_FBOX) {
	label = _("Factor (discrete)");
    } else if (sr->ci == GR_XYZ) {
	label = N_("Control variable");
    } else if (sr->ci == ANOVA) {
	label = N_("Block variable (optional)");
    } else {
	return;
    }

    sr->rvars1 = entry_with_label_and_chooser(sr, _(label), 0,
					      set_third_var_callback);
}

static int get_nonparam_xvar (void)
{
    if (np_xvar > 0) {
	return np_xvar;
    } else if (xlist != NULL) {
	int i;

	for (i=1; i<=xlist[0]; i++) {
	    if (xlist[i] != 0) {
		return xlist[i];
	    }
	}
    }

    return 0;
}

static int get_setvar_value (int v)
{
    if (v > 0 && v < dataset->v) {
	return v;
    } else {
	return 0;
    }
}

/* selector for auxiliary series of some kind */

static void extra_var_box (selector *sr)
{
    int setvar = 0;

    sr->extra[0] = entry_with_label_and_chooser(sr, NULL, 0,
						set_extra_var_callback);

    if (sr->ci == WLS) {
	setvar = get_setvar_value(wtvar);
    } else if (sr->ci == HECKIT) {
	setvar = get_setvar_value(selvar);
    } else if (sr->ci == INTREG) {
	setvar = get_setvar_value(hivar);
    } else if (sr->ci == COUNTMOD) {
	setvar = get_setvar_value(offvar);
    } else if (sr->ci == DURATION) {
	setvar = get_setvar_value(censvar);
    } else if (sr->ci == BIPROBIT) {
	setvar = get_setvar_value(y2var);
    } else if (NONPARAM_CODE(sr->ci)) {
	setvar = get_nonparam_xvar();
    }	

    if (setvar > 0) {
	gtk_entry_set_text(GTK_ENTRY(sr->extra[0]), dataset->varname[setvar]);
	g_object_set_data(G_OBJECT(sr->extra[0]), "data",
			  GINT_TO_POINTER(setvar));
    }	
}

static gboolean accept_right_arrow (GtkWidget *w, GdkEventKey *key,
				    gpointer p)
{
    if (key->keyval == GDK_Right) { 
        gtk_button_clicked(GTK_BUTTON(p));
	return TRUE;
    } else {
	return FALSE;
    }
}

static gboolean accept_left_arrow (GtkWidget *w, GdkEventKey *key,
				   gpointer p)
{
    if (key->keyval == GDK_Left) { 
        gtk_button_clicked(GTK_BUTTON(p));
	return TRUE;
    } else {
	return FALSE;
    }
}

static void push_pull_buttons (selector *sr, 
			       void (*addfunc)(),
			       void (*remfunc)(),
			       GtkWidget **lags_button,
			       gretlopt opt)
{
    GtkWidget *align, *vbox;
    GtkWidget *button;
    int spacing = 5;

    if ((opt & OPT_A) || lags_button == NULL) {
	spacing = 15;
    }

    align = gtk_alignment_new(0.5, 0.5, 0, 0);
    gtk_alignment_set_padding(GTK_ALIGNMENT(align), 
			      0, 0, 0, 0);
    vbox = gtk_vbox_new(TRUE, 5);    

    /* "Add" button */
    button = pix_button(add_inline, _("Add"));
    g_signal_connect(G_OBJECT(button), "clicked", 
		     G_CALLBACK(addfunc), sr);
    g_signal_connect(G_OBJECT(sr->dlg), "key-press-event", 
		     G_CALLBACK(accept_right_arrow), button);
    gtk_box_pack_start(GTK_BOX(vbox), button, 0, 0, spacing);
    if (opt & OPT_A) {
	sr->add_button = button;
    }

    /* "All" button? */
    if (SAVE_DATA_ACTION(sr->ci) || sr->ci == EXPORT) {
	button = gtk_button_new_with_label(_("All ->"));
	g_signal_connect(G_OBJECT(button), "clicked", 
			 G_CALLBACK(add_all_to_rvars1_callback), sr);
	gtk_box_pack_start(GTK_BOX(vbox), button, 0, 0, spacing);
    }    

    /* "Remove" button */
    button = pix_button(remove_inline, _("Remove"));
    g_signal_connect(G_OBJECT(button), "clicked", 
		     G_CALLBACK(remfunc), sr);
    g_signal_connect(G_OBJECT(sr->dlg), "key-press-event", 
		     G_CALLBACK(accept_left_arrow), button);
    gtk_box_pack_start(GTK_BOX(vbox), button, 0, 0, spacing);
    if (opt & OPT_R) {
	sr->remove_button = button;
    }

    /* "Lags" button? */
    if (lags_button != NULL) {
	button = gtk_button_new_with_label(_("lags..."));
	g_signal_connect(G_OBJECT(button), "clicked", 
			 G_CALLBACK(lags_dialog_driver), sr);
	gtk_widget_set_sensitive(button, FALSE);
	*lags_button = button;
	gtk_box_pack_start(GTK_BOX(vbox), button, 0, 0, spacing);
    }

    gtk_container_add(GTK_CONTAINER(align), vbox);
    table_add_mid(sr, align);
}

static void secondary_rhs_varlist (selector *sr)
{
    GtkTreeModel *mod;
    GtkListStore *store;
    GtkTreeIter iter;
    GtkWidget *hbox;
    GtkWidget *tmp = NULL;
    GtkWidget **lptr = NULL;
    int i;

    if (USE_VECXLIST(sr->ci)) {
	tmp = gtk_label_new(_("Exogenous variables"));
    } else if (IV_MODEL(sr->ci)) {
	tmp = gtk_label_new(_("Instruments"));
    } else if (sr->ci == HECKIT) {
	tmp = gtk_label_new(_("Selection regressors"));
    } else if (sr->ci == BIPROBIT) {
	tmp = gtk_label_new(_("Equation 2 regressors"));
    } else if (FNPKG_CODE(sr->ci)) {
	tmp = gtk_label_new(_("Helper functions"));
    }

    if (tmp != NULL) {
	table_add_right(sr, tmp, 1);
    }

    hbox = gtk_hbox_new(FALSE, 5);

    sr->rvars2 = var_list_box_new(GTK_BOX(hbox), sr, SR_RVARS2);
    mod = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->rvars2));

    store = GTK_LIST_STORE(mod);
    gtk_list_store_clear(store);
    gtk_tree_model_get_iter_first(mod, &iter);

    if (USE_ZLIST(sr->ci) && instlist != NULL) {
	for (i=1; i<=instlist[0]; i++) {
	    if (instlist[i] < dataset->v) {
		list_append_var(mod, &iter, instlist[i], sr, SR_RVARS2);
	    }
	}
    } else if (USE_VECXLIST(sr->ci) && vecxlist != NULL) {
	set_varflag(UNRESTRICTED);
	for (i=1; i<=vecxlist[0]; i++) {
	    if (vecxlist[i] == LISTSEP) {
		set_varflag(RESTRICTED);
	    } else if (vecxlist[i] > 0) {
		list_append_var(mod, &iter, vecxlist[i], sr, SR_RVARS2);
		if (sr->lags_button != NULL) {
		    gtk_widget_set_sensitive(sr->lags_button, TRUE);
		}
	    }
	}
	set_varflag(UNRESTRICTED);
    } else if (!VEC_CODE(sr->ci) && !FNPKG_CODE(sr->ci)) {
	list_append_var(mod, &iter, 0, sr, SR_RVARS2);
    }

    if (sr->ci == VAR || sr->ci == VECM || sr->ci == VLAGSEL) {
	lptr = &sr->lags_button;
    }

    /* add push-pull buttons */
    push_pull_buttons(sr, add_to_rvars2_callback,
		      remove_from_rvars2_callback,
		      lptr, OPT_NONE);

    table_add_right(sr, hbox, 0);
}

static void make_tau_list (GtkWidget *box)
{
    combo_box_append_text(box, "0.25 0.50 0.75");
    combo_box_append_text(box, ".05, .25 .50 .75, .95");
    combo_box_append_text(box, ".1 .2 .3 .4 .5 .6 .7 .8 .9");
} 

static void tobit_limits_selector (selector *sr)
{
    const char *bstrs[] = {
	N_("left bound"),
	N_("right bound")
    };
    GtkWidget *hbox, *entry, *label;
    gchar *valstr;
    double val;
    int i;

    for (i=0; i<2; i++) {
	hbox = gtk_hbox_new(FALSE, 5);
	entry = gtk_entry_new();
	gtk_entry_set_width_chars(GTK_ENTRY(entry), 5);
	val = (i == 0)? tobit_lo : tobit_hi;
	valstr = na(val)? g_strdup("NA") : g_strdup_printf("%g", val);
	gtk_entry_set_text(GTK_ENTRY(entry), valstr);
	g_free(valstr);
	gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
	gtk_box_pack_end(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
	label = gtk_label_new(_(bstrs[i]));
	gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 5);
	table_add_right(sr, hbox, 1);
	sr->extra[i] = entry;
    }
}

static void add_np_controls (selector *sr)
{
    GtkAdjustment *adj;
    GtkWidget *hbox, *w;
    double b0, bmin, bmax;
    const char *optstr;
    int i = 1;

    if (sr->ci == LOESS) {
	hbox = gtk_hbox_new(FALSE, 5);
	w = gtk_label_new(_("Polynomial order"));
	gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 5);
	adj = (GtkAdjustment *) gtk_adjustment_new(1, 0, 2, 1, 1, 0);
    	sr->extra[1] = gtk_spin_button_new(adj, 1, 0);
	gtk_box_pack_end(GTK_BOX(hbox), sr->extra[1], FALSE, FALSE, 5);
	table_add_right(sr, hbox, 1);
	i = 2;
    }

    b0   = (sr->ci == LOESS)? 0.5 : pow(sample_size(dataset), -0.2);
    bmin = 0.01;
    bmax = 1.0;

    /* bandwidth spinner */
    hbox = gtk_hbox_new(FALSE, 5);
    w = gtk_label_new(_("Bandwidth"));
    gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 5);
    adj = (GtkAdjustment *) gtk_adjustment_new(b0, bmin, bmax, 0.01, 0.1, 0);
    sr->extra[i] = gtk_spin_button_new(adj, 0.01, 2);
    gtk_box_pack_end(GTK_BOX(hbox), sr->extra[i], FALSE, FALSE, 5);
    table_add_right(sr, hbox, 1);

    optstr = (sr->ci == LOESS)? N_("Use robust weights") : 
	N_("Use \"leave one out\"");

    /* option checkbox */
    hbox = gtk_hbox_new(FALSE, 5);
    sr->extra[i+1] = gtk_check_button_new_with_label(_(optstr));
    gtk_box_pack_start(GTK_BOX(hbox), sr->extra[i+1], FALSE, FALSE, 5);
    table_add_right(sr, hbox, 1);
}

static int maybe_set_entry_text (GtkWidget *w, const char *s)
{
    if (s != NULL && *s != '\0') {
	gtk_entry_set_text(GTK_ENTRY(w), s + (*s == ' '));
	return 1;
    } else {
	return 0;
    }
}

static void build_mid_section (selector *sr)
{
    const char *str = _(extra_var_string(sr->ci));
    GtkWidget *tmp;

    if (str != NULL) {
	tmp = gtk_label_new(str);
	table_add_right(sr, tmp, 1);
    }	

    if (sr->ci == HECKIT || sr->ci == BIPROBIT) {
	extra_var_box(sr);
	table_add_vwedge(sr);
	primary_rhs_varlist(sr);
    } else if (sr->ci == WLS || sr->ci == INTREG || 
	       sr->ci == COUNTMOD || sr->ci == DURATION ||
	       THREE_VARS_CODE(sr->ci)) { 
	extra_var_box(sr);
    } else if (NONPARAM_CODE(sr->ci)) {
	extra_var_box(sr);
	table_add_vwedge(sr);
	add_np_controls(sr);
    } else if (sr->ci == TOBIT) {
	tobit_limits_selector(sr);
	table_add_vwedge(sr);
    } else if (USE_ZLIST(sr->ci)) {
	primary_rhs_varlist(sr);
    } else if (sr->ci == AR) {
	sr->extra[0] = gtk_entry_new();
	maybe_set_entry_text(sr->extra[0], arlags);
	table_add_right(sr, sr->extra[0], 1);
    } else if (sr->ci == QUANTREG) {
	GtkWidget *child;

	sr->extra[0] = combo_box_text_new_with_entry();
	make_tau_list(sr->extra[0]);
	child = gtk_bin_get_child(GTK_BIN(sr->extra[0]));
	gtk_entry_set_text(GTK_ENTRY(child), "0.5");
	table_add_right(sr, sr->extra[0], 1);
    } else if (sr->ci == VAR || sr->ci == VLAGSEL) {
	lag_order_spin(sr, LAG_ONLY);
	table_add_vwedge(sr);
	primary_rhs_varlist(sr);
    } else if (sr->ci == VECM) {
	lag_order_spin(sr, LAG_AND_RANK);
	table_add_vwedge(sr);
	primary_rhs_varlist(sr);
    } else if (sr->ci == COINT2) {
	lag_order_spin(sr, LAG_ONLY);
	table_add_vwedge(sr);
	primary_rhs_varlist(sr);
    } else if (VEC_CODE(sr->ci)) {
	lag_order_spin(sr, LAG_ONLY);
    } else if (sr->ci == DPANEL || sr->ci == ARCH) {
	AR_order_spin(sr);
    } 
    
    table_add_vwedge(sr);
}

enum {
    SELECTOR_SIMPLE,
    SELECTOR_FULL
};

static GtkWidget *selector_dialog_new (selector *sr)
{
    GtkWidget *d = gtk_dialog_new();
    GtkWidget *base, *ca, *aa;

    g_signal_connect(G_OBJECT(d), "key-press-event", 
		     G_CALLBACK(esc_kills_window), NULL);

    base = gtk_dialog_get_content_area(GTK_DIALOG(d));
    gtk_box_set_homogeneous(GTK_BOX(base), FALSE);
    gtk_box_set_spacing(GTK_BOX(base), 5);

    ca = gtk_vbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(base), ca, TRUE, TRUE, 0);
    gtk_container_set_border_width(GTK_CONTAINER(ca), 5);
    gtk_box_set_spacing(GTK_BOX(ca), 5);
    
    aa = gtk_dialog_get_action_area(GTK_DIALOG(d));
    gtk_button_box_set_layout(GTK_BUTTON_BOX(aa), GTK_BUTTONBOX_END);
    gtk_box_set_spacing(GTK_BOX(aa), 10);
    gtk_container_set_border_width(GTK_CONTAINER(aa), 5);

    sr->vbox = ca;
    sr->action_area = aa;

    return d;
}

static void selector_init (selector *sr, guint ci, const char *title,
			   int (*callback)(), GtkWidget *parent, 
			   gpointer data, int selcode)
{
    double x;
    int dlgx = -1, dlgy = 340;
    int i;

    sr->row = 0;
    sr->n_rows = 1;

    sr->blocking = 0;
    sr->ci = ci;
    sr->opts = OPT_NONE;
    sr->data = data;
    
    if (MODEL_CODE(ci)) {
	if (dataset->v > 9) {
	    dlgy += 80;
	} 
    } 

    if (ci == ARMA) {
	dlgy += 80;
    } else if (ci == WLS || ci == INTREG || ci == COUNTMOD || 
	       ci == DURATION || ci == AR) {
	dlgy += 30;
    } else if (ci == HECKIT || ci == TOBIT) {
	dlgy += 80;
    } else if (ci == BIPROBIT) {
	dlgy += 110;
    } else if (IV_MODEL(ci)) {
	dlgy += 60;
    } else if (ci == ANOVA) {
	dlgy -= 60;
    } else if (ci == PANEL_WLS) {
	sr->opts |= OPT_W;
    } else if (VEC_CODE(ci)) {
	dlgy = 450;
	if (ci == VAR || ci == VECM) {
	    dlgy += 50;
	} else if (ci == VLAGSEL) {
	    dlgy += 40;
	}
    } 

    if (WANT_TOGGLES(ci) && ci != COINT) {
	dlgy += 40;
    }

    if (want_radios(sr)) {
	dlgy += 60;
    }

    if (want_combo(sr)) {
	dlgy += 20;
    }    

    if (ci == ARMA && dataset->pd > 1) {
	/* seasonal spinners */
	dlgy += 60;
    }

    if (ci == GARCH) {
	/* extra check boxes */
	dlgy += 50;
    }

    if (dataset_lags_ok(dataset)) {
	if (MODEL_CODE(ci) && ci != ARMA) {
	    /* lag selector button at foot */
	    dlgy += 30;
	}
    }

    if (ci == DPANEL) {
	/* lots of option buttons */
	dlgy += 50;
    }

    sr->lvars = NULL;
    sr->depvar = NULL;
    sr->rvars1 = NULL;
    sr->rvars2 = NULL;
    sr->default_check = NULL;
    sr->add_button = NULL;
    sr->remove_button = NULL;
    sr->lags_button = NULL;
    sr->hess_button = NULL;
    sr->x12a_button = NULL;
    sr->xdiff_button = NULL;
    sr->hccme_button = NULL;

    for (i=0; i<N_EXTRA; i++) {
	sr->extra[i] = NULL;
    }

    sr->cmdlist = NULL;
    sr->callback = callback;

    sr->active_var = 0;
    sr->error = 0;
    sr->n_left = 0;
    sr->state_pushed = 0;

    if (ci == ARMA && push_program_state() == 0) {
	if (model_opt & OPT_L) {
	    libset_set_bool(USE_LBFGS, 1);
	}
	sr->state_pushed = 1;
    }

    if (selcode == SELECTOR_SIMPLE) {
	sr->dlg = selector_dialog_new(sr);
	if (parent != NULL) {
	    gtk_window_set_transient_for(GTK_WINDOW(sr->dlg), 
					 GTK_WINDOW(parent));
	    gtk_window_set_destroy_with_parent(GTK_WINDOW(sr->dlg), 
					       TRUE);
	} else {
	    gtk_window_set_transient_for(GTK_WINDOW(sr->dlg), 
					 GTK_WINDOW(mdata->main));
	}
    } else {
	sr->dlg = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gretl_emulated_dialog_add_structure(sr->dlg, 
					    &sr->vbox,
					    &sr->action_area);
    }

    gtk_window_set_title(GTK_WINDOW(sr->dlg), title);
    open_selector = sr;

    x = (double) dlgy * gui_scale;
    dlgy = x;

    if (FNPKG_CODE(ci)) {
	x = (double) 460 * gui_scale;
	dlgx = x;
    }
    
    gtk_window_set_default_size(GTK_WINDOW(sr->dlg), dlgx, dlgy); 
#ifndef G_OS_WIN32
    set_wm_icon(sr->dlg);
#endif    

    g_signal_connect(G_OBJECT(sr->dlg), "destroy", 
		     G_CALLBACK(destroy_selector), 
		     sr);
} 

static void option_callback (GtkWidget *w, selector *sr)
{
    gint i = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "opt"));
    gretlopt opt = i;

    if (button_is_active(w)) {
	sr->opts |= opt;
    } else {
	sr->opts &= ~opt;
    }  

    if (sr->ci == PANEL) {
	GtkWidget *w = g_object_get_data(G_OBJECT(sr->dlg), "robust-button");
	
	if (w != NULL) {
	    gtk_widget_set_sensitive(w, !(sr->opts & OPT_U));
	}
    }
}

static void reverse_option_callback (GtkWidget *w, selector *sr)
{
    gint i = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "opt"));
    gretlopt opt = i;

    if (button_is_active(w)) {
	sr->opts &= ~opt;
    } else {
	sr->opts |= opt;
    }    
}

static void garch_spin_check (GtkSpinButton *b, selector *sr)
{
    int p = gtk_spin_button_get_value(GTK_SPIN_BUTTON(sr->extra[0]));
    int q = gtk_spin_button_get_value(GTK_SPIN_BUTTON(sr->extra[1]));
    int i = (GTK_WIDGET(b) == sr->extra[1])? 1 : 0;

    if (p + q > 5) {
	/* limit p + q to 5 */
	if (i == 0) {
	    gtk_spin_button_set_value(GTK_SPIN_BUTTON(sr->extra[1]),
				      (gdouble) --q);
	} else {
	    gtk_spin_button_set_value(GTK_SPIN_BUTTON(sr->extra[0]),
				      (gdouble) --p);
	}
    } else if (p > 0 && q == 0) {
	/* rule out pure AR in variance */
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(sr->extra[1]), 1);
    }
}

static void build_garch_spinners (selector *sr)
{
    GtkWidget *tmp, *hbox;
    GtkAdjustment *adj;
    gdouble val;
    const char *strs[] = {
	N_("GARCH p:"),
	N_("ARCH q:")
    };    
    int i;

    hbox = gtk_hbox_new(FALSE, 5);

    for (i=0; i<2; i++) {
	tmp = gtk_label_new(_(strs[i]));
	gtk_box_pack_start(GTK_BOX(hbox), tmp, FALSE, FALSE, 5);
	val = (i==0)? garch_p : garch_q;
	adj = (GtkAdjustment *) gtk_adjustment_new(val, 0, 4, 1, 1, 0);
	sr->extra[i] = gtk_spin_button_new(adj, 1, 0);
	gtk_box_pack_start(GTK_BOX(hbox), sr->extra[i], FALSE, FALSE, 5);
	g_signal_connect(GTK_SPIN_BUTTON(sr->extra[i]), "value-changed",
			 G_CALLBACK(garch_spin_check), sr);
    }

    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 5);
}

static GtkWidget *arma_aux_label (int i)
{
    GtkWidget *hbox;
    GtkWidget *lbl;
    const char *strs[] = {
	N_("Non-seasonal"),
	N_("Seasonal")
    };

    hbox = gtk_hbox_new(FALSE, 5);
    lbl = gtk_label_new(_(strs[i]));
    gtk_box_pack_start(GTK_BOX(hbox), lbl, FALSE, FALSE, 5);

    return hbox;
}

static void toggle_p (GtkWidget *w, selector *sr)
{
    gboolean s = button_is_active(w);

    gtk_widget_set_sensitive(sr->extra[0], !s);
    gtk_widget_set_sensitive(sr->extra[1], s);
}

static void toggle_q (GtkWidget *w, selector *sr)
{
    gboolean s = button_is_active(w);

    gtk_widget_set_sensitive(sr->extra[3], !s);
    gtk_widget_set_sensitive(sr->extra[4], s);
}

static void arima_callback (GtkWidget *w, selector *sr)
{
    gtk_widget_set_sensitive(sr->xdiff_button, arima_selected(sr) &&
			     rvars1_n_vars(sr) > 0);
}

static void build_arma_spinners (selector *sr)
{
    GtkWidget *lbl, *chk, *tab;
    GtkWidget *hbox;
    GtkAdjustment *adj;
    gdouble vmax, val;
    gboolean freeform;
#if 0
    const char *strs[] = {
	N_("AR"),
	N_("I"),
	N_("MA")
    };
#else
    const char *strs[] = {
	N_("AR order:"),
	N_("Difference:"),
	N_("MA order:")
    };
#endif
    int i, j;

    if (dataset->pd > 1) {
	vbox_add_vwedge(sr->vbox);
	lbl = arma_aux_label(0);
	gtk_box_pack_start(GTK_BOX(sr->vbox), lbl, FALSE, FALSE, 0);
    }

    tab = gtk_table_new(3, 4, FALSE);
    gtk_table_set_row_spacings(GTK_TABLE(tab), 5);
    gtk_table_set_col_spacings(GTK_TABLE(tab), 5);
    gtk_box_pack_start(GTK_BOX(sr->vbox), tab, FALSE, FALSE, 0);

    /* AR lags */
    lbl = gtk_label_new(_(strs[0]));
    gtk_table_attach_defaults(GTK_TABLE(tab), lbl, 0, 1, 0, 1);
    adj = (GtkAdjustment *) gtk_adjustment_new(arma_p, 0, 10, 1, 1, 0);
    sr->extra[0] = gtk_spin_button_new(adj, 1, 0);
    gtk_table_attach_defaults(GTK_TABLE(tab), sr->extra[0], 1, 2, 0, 1);
    chk = gtk_check_button_new_with_label(_("or specific lags"));
    g_signal_connect(G_OBJECT(chk), "clicked", G_CALLBACK(toggle_p), sr);
    gtk_table_attach_defaults(GTK_TABLE(tab), chk, 2, 3, 0, 1);

    /* or free-form lags */
    sr->extra[1] = gtk_entry_new();
    gtk_entry_set_max_length(GTK_ENTRY(sr->extra[1]), 16);
    freeform = maybe_set_entry_text(sr->extra[1], arlags);
    gtk_widget_set_sensitive(sr->extra[1], FALSE);
    gtk_table_attach_defaults(GTK_TABLE(tab), sr->extra[1], 3, 4, 0, 1);
    if (freeform) {
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk), TRUE);
    }

    /* order for differencing */
    lbl = gtk_label_new(_(strs[1]));
    gtk_table_attach_defaults(GTK_TABLE(tab), lbl, 0, 1, 1, 2);
    adj = (GtkAdjustment *) gtk_adjustment_new(arima_d, 0, 2, 1, 1, 0);
    sr->extra[2] = gtk_spin_button_new(adj, 1, 0);
    gtk_table_attach_defaults(GTK_TABLE(tab), sr->extra[2], 1, 2, 1, 2);
    g_signal_connect(G_OBJECT(sr->extra[2]), "value-changed",
		     G_CALLBACK(arima_callback), sr);

    /* MA lags */
    lbl = gtk_label_new(_(strs[2]));
    gtk_table_attach_defaults(GTK_TABLE(tab), lbl, 0, 1, 2, 3);
    adj = (GtkAdjustment *) gtk_adjustment_new(arma_q, 0, 10, 1, 1, 0);
    sr->extra[3] = gtk_spin_button_new(adj, 1, 0);
    gtk_table_attach_defaults(GTK_TABLE(tab), sr->extra[3], 1, 2, 2, 3);
    chk = gtk_check_button_new_with_label(_("or specific lags"));
    g_signal_connect(G_OBJECT(chk), "clicked", G_CALLBACK(toggle_q), sr);
    gtk_table_attach_defaults(GTK_TABLE(tab), chk, 2, 3, 2, 3);

    /* or free-form lags */
    sr->extra[4] = gtk_entry_new();
    gtk_entry_set_max_length(GTK_ENTRY(sr->extra[4]), 16);
    freeform = maybe_set_entry_text(sr->extra[4], malags);
    gtk_widget_set_sensitive(sr->extra[4], FALSE);
    gtk_table_attach_defaults(GTK_TABLE(tab), sr->extra[4], 3, 4, 2, 3);
    if (freeform) {
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk), TRUE);
    }

    j = 5;

    if (dataset->pd > 1) {
	vbox_add_vwedge(sr->vbox);

	lbl = arma_aux_label(1);
	gtk_box_pack_start(GTK_BOX(sr->vbox), lbl, FALSE, FALSE, 0);

	hbox = gtk_hbox_new(FALSE, 5);
	for (i=0; i<3; i++) {
	    lbl = gtk_label_new(_(strs[i]));
	    gtk_box_pack_start(GTK_BOX(hbox), lbl, FALSE, FALSE, 0);
	    val = (i==0)? arma_P : (i==1)? arima_D : arma_Q;
	    vmax = (i == 1)? 2 : 4;
	    adj = (GtkAdjustment *) gtk_adjustment_new(val, 0, vmax, 1, 1, 0);
	    sr->extra[j] = gtk_spin_button_new(adj, 1, 0);
	    if (i == 1) {
		g_signal_connect(G_OBJECT(sr->extra[j]), "value-changed",
				 G_CALLBACK(arima_callback), sr);
	    }
	    gtk_box_pack_start(GTK_BOX(hbox), sr->extra[j++], FALSE, FALSE, 5);
	}

	gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 5);
    }
}

static void reset_arma_spinners (selector *sr)
{
    if (sr->extra[0] != NULL && GTK_IS_SPIN_BUTTON(sr->extra[0])) {
	/* non-seasonal AR maxlag */
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(sr->extra[0]), 
				  (gdouble) arma_p);
    } 
    if (sr->extra[1] != NULL && GTK_IS_ENTRY(sr->extra[1])) {
	/* non-seasonal AR specific lags */
	gtk_entry_set_text(GTK_ENTRY(sr->extra[1]), "");
    } 
    if (sr->extra[2] != NULL && GTK_IS_SPIN_BUTTON(sr->extra[2])) {
	/* non-seasonal diff */
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(sr->extra[2]), 
				  (gdouble) arima_d);
    } 
    if (sr->extra[3] != NULL && GTK_IS_SPIN_BUTTON(sr->extra[3])) {
	/* non-seasonal MA maxlag */
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(sr->extra[3]), 
				  (gdouble) arma_q);
    } 
    if (sr->extra[4] != NULL && GTK_IS_ENTRY(sr->extra[4])) {
	/* non-seasonal MA specific lags */
	gtk_entry_set_text(GTK_ENTRY(sr->extra[4]), "");
    } 
    if (sr->extra[5] != NULL && GTK_IS_SPIN_BUTTON(sr->extra[5])) {
	/* seasonal AR maxlag */
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(sr->extra[5]), 
				  (gdouble) arma_P);
    } 
    if (sr->extra[6] != NULL && GTK_IS_SPIN_BUTTON(sr->extra[6])) {
	/* seasonal diff */
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(sr->extra[6]), 
				  (gdouble) arima_D);
    } 
    if (sr->extra[7] != NULL && GTK_IS_SPIN_BUTTON(sr->extra[7])) {
	/* seasonal MA maxlag */
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(sr->extra[7]), 
				  (gdouble) arma_Q);
    } 
}

static void hc_config (GtkWidget *w, selector *sr)
{
    if (offer_cluster_option(sr->ci)) {
	gboolean rconf = robust_conf(sr->ci);
	gretlopt c_opt = OPT_NONE;
	int resp;

	if (*cluster_var != '\0') {
	    int v = current_series_index(dataset, cluster_var);

	    if (v < 1) {
		*cluster_var = '\0';
	    } else if (cluster_option_is_active(sr)) {
		c_opt = OPT_C;
	    }
	}

	resp = hc_config_dialog(cluster_var, c_opt, rconf, sr->dlg);

	if (resp == 0) {
	    /* regular HCCME */
	    if (rconf) {
		options_dialog(TAB_VCV, NULL, sr->dlg);
	    } else {
		gtk_button_set_label(GTK_BUTTON(sr->hccme_button), 
				     get_default_hc_string(sr->ci));
	    }
	} else if (resp == 1) {
	    /* cluster option selected */
	    gtk_button_set_label(GTK_BUTTON(sr->hccme_button), _("Cluster"));
	}
    } else {
	options_dialog(TAB_VCV, NULL, sr->dlg);
    }
}

/* Callback for when the user selects something in the VCV
   tab of the preferences dialog: make sure that if there's
   a selection dialog open, and it's showing an HCCME
   button, the text on the button is synced with the
   user's selection.
*/

void selector_register_hc_choice (void)
{
    selector *sr = open_selector;

    if (sr != NULL && sr->hccme_button != NULL) {
	const char *txt = get_default_hc_string(sr->ci);
	GtkWidget *w = sr->hccme_button;

	if (GTK_IS_BUTTON(w)) {
	    gtk_button_set_label(GTK_BUTTON(w), txt);
	} else if (GTK_IS_COMBO_BOX(w)) {
	    gint i = gtk_combo_box_get_active(GTK_COMBO_BOX(w));

	    combo_box_remove(w, 0);
	    combo_box_prepend_text(w, txt);
	    gtk_combo_box_set_active(GTK_COMBO_BOX(w), i);
	}
    }
}

static GtkWidget *pack_switch (GtkWidget *b, selector *sr,
			       gboolean checked, gboolean reversed, 
			       gretlopt opt, int child)
{
    GtkWidget *hbox = gtk_hbox_new(FALSE, 5);
    gint offset = (child)? 15 : 0;
    gint i = opt;

    g_object_set_data(G_OBJECT(b), "opt", GINT_TO_POINTER(i));

    if (reversed) {
	g_signal_connect(G_OBJECT(b), "toggled", 
			 G_CALLBACK(reverse_option_callback), sr);
	if (checked) {
	    sr->opts &= ~opt;
	} else {
	    sr->opts |= opt;
	}
    } else {
	g_signal_connect(G_OBJECT(b), "toggled", 
			 G_CALLBACK(option_callback), sr);
	if (checked) {
	    sr->opts |= opt;
	} else {
	    sr->opts &= ~opt;
	}
    }

    gtk_box_pack_start(GTK_BOX(hbox), b, TRUE, TRUE, offset);
    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 0);

    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b), checked);

    return hbox;
}

static void pack_switch_in (GtkWidget *hbox, GtkWidget *b, selector *sr,
			    gboolean checked, gboolean reversed, 
			    gretlopt opt, int child)
{
    gint offset = (child)? 15 : 0;
    gint i = opt;

    g_object_set_data(G_OBJECT(b), "opt", GINT_TO_POINTER(i));

    if (reversed) {
	g_signal_connect(G_OBJECT(b), "toggled", 
			 G_CALLBACK(reverse_option_callback), sr);
	if (checked) {
	    sr->opts &= ~opt;
	} else {
	    sr->opts |= opt;
	}
    } else {
	g_signal_connect(G_OBJECT(b), "toggled", 
			 G_CALLBACK(option_callback), sr);
	if (checked) {
	    sr->opts |= opt;
	} else {
	    sr->opts &= ~opt;
	}
    }

    gtk_box_pack_start(GTK_BOX(hbox), b, TRUE, TRUE, offset);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b), checked);
}

static void call_iters_dialog (GtkWidget *w, GtkWidget *combo)
{
    static int optim = BFGS_MAX;
    selector *sr;
    int active, maxit, lmem = 0;
    double tol;
    int resp;

    if (w == NULL) {
	/* clean-up signal */
	optim = libset_get_bool(USE_LBFGS)? LBFGS_MAX : BFGS_MAX;
	return;
    }

    sr = g_object_get_data(G_OBJECT(combo), "selector");
    active = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));

    if (sr->ci == ARMA && active == 1) {
	maxit = libset_get_int(BHHH_MAXITER);
	tol = libset_get_double(BHHH_TOLER);
	optim = BHHH_MAX;
    } else {
	optim = BFGS_MAX;
	BFGS_defaults(&maxit, &tol, sr->ci);
	lmem = libset_get_int(LBFGS_MEM);
    }

    if (maxit <= 0) {
	maxit = 1000;
    }  

    if (optim == BFGS_MAX && libset_get_bool(USE_LBFGS)) {
	optim = LBFGS_MAX;
    }

    resp = iter_control_dialog(&optim, &maxit, &tol, 
			       &lmem, sr->dlg);

    if (!canceled(resp)) {
	int err = 0;

	if (optim == BHHH_MAX) {
	    err = libset_set_int(BHHH_MAXITER, maxit);
	    err += libset_set_double(BHHH_TOLER, tol);
	} else {
	    err = libset_set_int(BFGS_MAXITER, maxit);
	    err += libset_set_double(BFGS_TOLER, tol);
	}

	if (optim == LBFGS_MAX) {
	    libset_set_int(LBFGS_MEM, lmem);
	    sr->opts |= OPT_L;
	} else {
	    sr->opts &= ~OPT_L;
	}

	if (err) {
	    errbox("Error setting values");
	}
    }
}

#ifdef HAVE_X12A

static gboolean x12a_vs_native_opts (GtkWidget *w, selector *sr)
{
    gboolean s = button_is_active(w);

    if (sr->hess_button != NULL) {
	gtk_widget_set_sensitive(sr->hess_button, !s);
    }

    if (sr->xdiff_button != NULL) {
	if (s) {
	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sr->xdiff_button), s);
	}
	gtk_widget_set_sensitive(sr->xdiff_button, !s && arima_selected(sr));
    }

    return FALSE;
}

#endif

static void gui_set_mp_bits (GtkComboBox *cb, gpointer p)
{
    gint i = gtk_combo_box_get_active(cb);
    int j, b = 256;

    for (j=0; j<i; j++) {
	b *= 2;
    }

    set_mp_bits(b);
}

static GtkWidget *mpols_bits_selector (void)
{
    GtkWidget *w, *hbox, *combo;
    char bstr[8];
    int bits = get_mp_bits();
    int b, i, deflt = 0;

    hbox = gtk_hbox_new(FALSE, 5);
    w = gtk_label_new(_("Bits per floating-point value"));
    gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 5);

    combo = gtk_combo_box_text_new();

    i = 0;
    for (b=256; b<=4096; b*=2) {
	sprintf(bstr, "%d", b);
	combo_box_append_text(combo, bstr);
	if (b == bits) {
	    deflt = i;
	}
	i++;
    }

    gtk_combo_box_set_active(GTK_COMBO_BOX(combo), deflt);
    g_signal_connect(G_OBJECT(GTK_COMBO_BOX(combo)), "changed",
		     G_CALLBACK(gui_set_mp_bits), NULL);
    gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 5);

    return hbox;
}

void vbox_add_hwedge (GtkWidget *vbox)
{
    GtkWidget *h = gtk_hbox_new(FALSE, 0);
    
    gtk_box_pack_start(GTK_BOX(vbox), h, FALSE, FALSE, 2);
    gtk_widget_show(h);
}

static void build_selector_switches (selector *sr) 
{
    GtkWidget *hbox, *tmp;

    if (sr->ci == REPROBIT) {
	/* number of quadrature points for random effects probit */
	vbox_add_hwedge(sr->vbox);
	hbox = gtk_hbox_new(FALSE, 5);
	tmp = gtk_label_new(_("Quadrature points"));
	gtk_box_pack_start(GTK_BOX(hbox), tmp, FALSE, FALSE, 0);
	sr->extra[0] = tmp = gtk_spin_button_new_with_range(3, 64, 1);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(tmp), 8);
	gtk_box_pack_start(GTK_BOX(hbox), tmp, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 0);
	sr->opts |= OPT_G;
    }	

    if (sr->ci == OLS || sr->ci == WLS || sr->ci == INTREG ||
	sr->ci == GARCH || sr->ci == IVREG || sr->ci == VAR || 
	sr->ci == LOGIT || sr->ci == PROBIT || sr->ci == MLOGIT ||
	sr->ci == OLOGIT || sr->ci == OPROBIT || sr->ci == COUNTMOD ||
	sr->ci == DURATION || sr->ci == PANEL || sr->ci == QUANTREG || 
	sr->ci == HECKIT || sr->ci == BIPROBIT || sr->ci == TOBIT) {
	GtkWidget *b1;

	/* FIXME arma robust variant? (and REPROBIT should be here?) */

	vbox_add_hwedge(sr->vbox);

	if (sr->ci == QUANTREG) {
	    b1 = gtk_check_button_new_with_label(_("Robust standard errors/intervals"));
	} else {
	    b1 = gtk_check_button_new_with_label(_("Robust standard errors"));
	}

	g_object_set_data(G_OBJECT(b1), "opt", GINT_TO_POINTER(OPT_R));
	g_signal_connect(G_OBJECT(b1), "toggled",
			 G_CALLBACK(option_callback), sr);

	if (robust_conf(sr->ci) && using_hc_by_default()) {
	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b1), TRUE);
	} else if (model_opt & OPT_R) {
	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b1), TRUE);
	}

	if (sr->ci == PANEL) {
	    g_object_set_data(G_OBJECT(sr->dlg), "robust-button", b1);
	}

	hbox = gtk_hbox_new(FALSE, 5);
	gtk_box_pack_start(GTK_BOX(hbox), b1, FALSE, FALSE, 0);

	if (sr->ci == VAR) {
	    /* special: we don't use HAC by default */
	    GtkWidget *b2 = gtk_combo_box_text_new();

	    sr->hccme_button = b2;
	    combo_box_append_text(b2, get_default_hc_string(VAR));
	    combo_box_append_text(b2, "HAC");
	    gtk_combo_box_set_active(GTK_COMBO_BOX(b2), 0);
	    gtk_widget_set_sensitive(b2, using_hc_by_default());
	    sensitize_conditional_on(b2, b1);
	    if (model_opt & OPT_R) {
		gtk_widget_set_sensitive(b2, TRUE);
	    }
	    gtk_box_pack_start(GTK_BOX(hbox), b2, FALSE, FALSE, 0);
	} else if (robust_conf(sr->ci) || offer_cluster_option(sr->ci)) {
	    const char *deftxt;
	    GtkWidget *b2;

	    deftxt = get_default_hc_string(sr->ci);
	    sr->hccme_button = b2 = gtk_button_new_with_label(deftxt);
	    g_signal_connect(G_OBJECT(b2), "clicked",
			     G_CALLBACK(hc_config), sr);
	    gtk_widget_set_sensitive(b2, using_hc_by_default());
	    sensitize_conditional_on(b2, b1);
	    if (model_opt & OPT_R) {
		gtk_widget_set_sensitive(b2, TRUE);
	    }
	    gtk_box_pack_start(GTK_BOX(hbox), b2, FALSE, FALSE, 0);
	} 

	gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 0);
    }

    if (sr->ci == TOBIT || sr->ci == ARMA || sr->ci == GARCH ||
	sr->ci == LOGIT || sr->ci == PROBIT || sr->ci == HECKIT ||
	sr->ci == OLOGIT || sr->ci == OPROBIT || sr->ci == MLOGIT ||
	sr->ci == BIPROBIT || sr->ci == REPROBIT) {
	if (sr->ci == ARMA) {
	    vbox_add_vwedge(sr->vbox);
	    tmp = gtk_check_button_new_with_label(_("Include a constant"));
	    pack_switch(tmp, sr, arma_const, TRUE, OPT_N, 0);
	}
	if (sr->ci == GARCH) {
	    tmp = gtk_check_button_new_with_label(_("Include a constant"));
	    pack_switch(tmp, sr, garch_const, TRUE, OPT_N, 0);
	}	
	tmp = gtk_check_button_new_with_label(_("Show details of iterations"));
	pack_switch(tmp, sr, verbose, FALSE, OPT_V, 0);
    } else if (sr->ci == COINT2 || sr->ci == VECM || 
	       sr->ci == VAR || sr->ci == VLAGSEL) {
	if (sr->ci == VAR || sr->ci == VLAGSEL) {
	    tmp = gtk_check_button_new_with_label(_("Include a constant"));
	    pack_switch(tmp, sr, varconst, TRUE, OPT_N, 0);
	    tmp = gtk_check_button_new_with_label(_("Include a trend"));
	    pack_switch(tmp, sr, vartrend, FALSE, OPT_T, 0);
	} else {
	    tmp = gtk_check_button_new_with_label(_("Show details of regressions"));
	    pack_switch(tmp, sr, verbose, FALSE, OPT_V, 0);
	}
	tmp = gtk_check_button_new_with_label(_("Include seasonal dummies"));
	pack_switch(tmp, sr, 
		    want_seasonals && (dataset->pd == 4 || dataset->pd == 12),
		    FALSE, OPT_D, 0);
	if (dataset->pd != 4 && dataset->pd != 12) {
	    gtk_widget_set_sensitive(tmp, FALSE);
	}
    } else if (sr->ci == COINT) {
	tmp = gtk_check_button_new_with_label(_("Test down from maximum lag order"));
	pack_switch(tmp, sr, FALSE, FALSE, OPT_E, 0);
	tmp = gtk_check_button_new_with_label(_("Skip initial DF tests"));
	pack_switch(tmp, sr, FALSE, FALSE, OPT_S, 0);
    } else if (sr->ci == PANEL_WLS) {
	tmp = gtk_check_button_new_with_label(_("Iterated weighted least squares"));
	pack_switch(tmp, sr, FALSE, FALSE, OPT_T, 0);
    } else if (sr->ci == PANEL) {
	tmp = gtk_check_button_new_with_label(_("Include time dummies"));
	pack_switch(tmp, sr, (model_opt & OPT_D), FALSE, OPT_D, 0);
    } else if (sr->ci == DPANEL) {
	tmp = gtk_check_button_new_with_label(_("Include time dummies"));
	pack_switch(tmp, sr, (model_opt & OPT_D), FALSE, OPT_D, 0);
	tmp = gtk_check_button_new_with_label(_("Include levels equations (GMM-SYS)"));
	pack_switch(tmp, sr, (model_opt & OPT_L), FALSE, OPT_L, 0);
	tmp = gtk_check_button_new_with_label(_("Asymptotic standard errors"));
	pack_switch(tmp, sr, (dpd_asy || (model_opt & OPT_A)), FALSE, OPT_A, 0);
    } else if (sr->ci == XTAB) {
	tmp = gtk_check_button_new_with_label(_("Show zeros explicitly"));
	pack_switch(tmp, sr, FALSE, FALSE, OPT_Z, 0);
    } else if (sr->ci == SPEARMAN) {
	tmp = gtk_check_button_new_with_label(_("Show rankings"));
	pack_switch(tmp, sr, verbose, FALSE, OPT_V, 0);
    } else if (sr->ci == CORR) {	
	tmp = gtk_check_button_new_with_label(_("Ensure uniform sample size"));
	pack_switch(tmp, sr, verbose, FALSE, OPT_U, 0);
    } else if (sr->ci == GR_3D) {
	tmp = gtk_check_button_new_with_label(_("Make plot interactive"));
	pack_switch(tmp, sr, TRUE, FALSE, OPT_I, 0);
    }

    if (sr->ci == ARMA) {
	sr->hess_button = 
	    gtk_check_button_new_with_label(_("Parameter covariance matrix via Hessian"));
	pack_switch(sr->hess_button, sr, arma_hessian, TRUE, OPT_G, 0);
	sr->xdiff_button = gtk_check_button_new_with_label(_("Difference the independent variables"));
	pack_switch(sr->xdiff_button, sr, arima_xdiff, TRUE, OPT_Y, 0);
	gtk_widget_set_sensitive(sr->xdiff_button, (arima_d > 0 || arima_D > 0) && !arma_x12);
#ifdef HAVE_X12A   
	sr->x12a_button = gtk_check_button_new_with_label(_("Use X-12-ARIMA"));
	pack_switch(sr->x12a_button, sr, arma_x12, FALSE, OPT_X, 0);
	g_signal_connect(G_OBJECT(sr->x12a_button), "toggled",
			 G_CALLBACK(x12a_vs_native_opts), sr);
#endif
    } else if (sr->ci == GARCH) {
	tmp = gtk_check_button_new_with_label(_("Standardize the residuals"));
	pack_switch(tmp, sr, (model_opt & OPT_Z), FALSE, OPT_Z, 0);
	tmp = gtk_check_button_new_with_label(_("Use Fiorentini et al algorithm"));
	pack_switch(tmp, sr, libset_get_bool(USE_FCP), FALSE, OPT_F, 0);
    } else if (sr->ci == MPOLS) {
	hbox = mpols_bits_selector();
	gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 5);
    }
} 

static void unhide_lags_callback (GtkWidget *w, selector *sr)
{
    int i, imin, show_lags;
    GtkListStore *store;
    GtkTreeIter iter;

    store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(sr->lvars)));
    gtk_list_store_clear(store);
    gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);

    imin = (sr->ci == DEFINE_LIST)? 0 : 1;
    show_lags = button_is_active(w);

    for (i=imin; i<dataset->v; i++) {
	if (list_show_var(i, sr->ci, show_lags)) {
	    list_append_var_simple(store, &iter, i);
	}
    }
}

static void unhide_lags_switch (selector *sr) 
{
    GtkWidget *hbox;
    GtkWidget *b;

    vbox_add_vwedge(sr->vbox);

    b = gtk_check_button_new_with_label(_("Show lagged variables"));
    g_signal_connect(G_OBJECT(b), "toggled",
		     G_CALLBACK(unhide_lags_callback), sr);

    hbox = gtk_hbox_new(FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), b, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 0);
}

#if 0 /* not ready */ 

static void boot_switch_callback (GtkWidget *w, selector *sr)
{
    if (button_is_active(w)) {
	sr->opts |= OPT_P;
    } else {
	sr->opts &= ~OPT_P;
    }
}

static void test_boot_switch (selector *sr) 
{
    GtkWidget *hbox;
    GtkWidget *b;

    vbox_add_vwedge(sr->vbox);

    b = gtk_check_button_new_with_label(_("Use bootstrap"));
    g_signal_connect(G_OBJECT(b), "toggled",
		     G_CALLBACK(boot_switch_callback), sr);

    hbox = gtk_hbox_new(FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), b, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 0);
}

#endif 

static void pack_switch_with_extra (GtkWidget *b, selector *sr,
				    gboolean checked, gretlopt opt, 
				    int child, GtkWidget *extra,
				    const gchar *extra_text)
{
    GtkWidget *hbox = gtk_hbox_new(FALSE, 5);
    gint offset = (child)? 15 : 0;
    gint i = opt;

    g_object_set_data(G_OBJECT(b), "opt", GINT_TO_POINTER(i));
    g_signal_connect(G_OBJECT(b), "toggled", 
		     G_CALLBACK(option_callback), sr);
    if (checked) {
	sr->opts |= opt;
    } else {
	sr->opts &= ~opt;
    }
    gtk_box_pack_start(GTK_BOX(hbox), b, TRUE, TRUE, offset);

    if (extra_text != NULL) {
	GtkWidget *w = gtk_label_new(extra_text);

	gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 5);
    }

    gtk_box_pack_start(GTK_BOX(hbox), extra, TRUE, TRUE, offset);
    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 0);

    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b), checked);
}

static GtkWidget *alpha_spinner (double deflt, double minval)
{
    GtkAdjustment *adj;
    
    adj = (GtkAdjustment *) gtk_adjustment_new(deflt, minval, 0.99, 
					       0.01, 0.1, 0);
    return gtk_spin_button_new(adj, 1, 2);
}

static void build_quantreg_radios (selector *sr)
{
    GtkWidget *b1, *b2;
    GSList *group;

    b1 = gtk_radio_button_new_with_label(NULL, _("Compute standard errors"));
    pack_switch(b1, sr, TRUE, FALSE, OPT_NONE, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b1));
    b2 = gtk_radio_button_new_with_label(group, 
					 _("Compute confidence intervals"));
    sr->extra[1] = alpha_spinner(0.90, 0.70);
    pack_switch_with_extra(b2, sr, FALSE, OPT_I, 0, sr->extra[1], "1 - α =");
    gtk_widget_set_sensitive(sr->extra[1], FALSE);
    sensitize_conditional_on(sr->extra[1], b2);
}

static GtkWidget *ymax_spinner (void)
{
    GtkAdjustment *adj;
    
    adj = (GtkAdjustment *) gtk_adjustment_new(1.0, 0, 1e+10, 
					       0.01, 0.1, 0);
    return gtk_spin_button_new(adj, 1, 1);
}

static void build_logistic_radios (selector *sr)
{
    GtkWidget *b1, *b2;
    GSList *group;

    vbox_add_vwedge(sr->vbox);

    b1 = gtk_radio_button_new_with_label(NULL, _("Automatic maximum"));
    pack_switch(b1, sr, TRUE, FALSE, OPT_NONE, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b1));
    b2 = gtk_radio_button_new_with_label(group, 
					 _("Specified maximum"));
    sr->extra[1] = ymax_spinner();
    pack_switch_with_extra(b2, sr, FALSE, OPT_M, 0, sr->extra[1], NULL);
    gtk_widget_set_sensitive(sr->extra[1], FALSE);
    sensitize_conditional_on(sr->extra[1], b2);
}

static void build_ellipse_spinner (selector *sr)
{
    GtkWidget *hbox = gtk_hbox_new(FALSE, 5);
    GtkWidget *label;

    vbox_add_vwedge(sr->vbox);

    label = gtk_label_new("");
    gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);

    label = gtk_label_new(_("Confidence level"));
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);

    label = gtk_label_new("1 - α =");
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);

    sr->extra[0] = alpha_spinner(0.95, 0.70);
    gtk_box_pack_start(GTK_BOX(hbox), sr->extra[0], FALSE, FALSE, 0);

    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 0);
}

static void build_rankcorr_radios (selector *sr)
{
    GtkWidget *b1, *b2;
    GSList *group;

    b1 = gtk_radio_button_new_with_label(NULL, _("Spearman's rho"));
    pack_switch(b1, sr, TRUE, FALSE, OPT_NONE, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b1));
    b2 = gtk_radio_button_new_with_label(group, _("Kendall's tau"));
    pack_switch(b2, sr, FALSE, FALSE, OPT_K, 0);
}

static void build_pca_radios (selector *sr)
{
    GtkWidget *b1, *b2;
    GSList *group;

    b1 = gtk_radio_button_new_with_label(NULL, _("Use correlation matrix"));
    pack_switch(b1, sr, TRUE, FALSE, OPT_NONE, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b1));
    b2 = gtk_radio_button_new_with_label(group, _("Use covariance matrix"));
    pack_switch(b2, sr, FALSE, FALSE, OPT_C, 0);
}

static void build_pvalues_radios (selector *sr)
{
    GtkWidget *b1, *b2;
    GSList *group;

    b1 = gtk_radio_button_new_with_label(NULL, _("Show slopes at mean"));
    pack_switch(b1, sr, TRUE, FALSE, OPT_NONE, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b1));
    b2 = gtk_radio_button_new_with_label(group, _("Show p-values"));
    pack_switch(b2, sr, FALSE, FALSE, OPT_P, 0);

    if (lp_pvals) {
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b2), TRUE);
    } else {
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b1), TRUE);
    }
}

static void build_scatters_radios (selector *sr)
{
    GtkWidget *b1, *b2;
    GSList *group;

    b1 = gtk_radio_button_new_with_label(NULL, _("Use points"));
    pack_switch(b1, sr, TRUE, FALSE, OPT_NONE, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b1));
    b2 = gtk_radio_button_new_with_label(group, _("Use lines"));
    pack_switch(b2, sr, FALSE, FALSE, OPT_L, 0);
}

static void auto_omit_restrict_callback (GtkWidget *w, selector *sr)
{
    gboolean r = button_is_active(w);

    if (sr->lvars != NULL) {
	gtk_widget_set_sensitive(sr->lvars, r);
    }

    if (sr->rvars1 != NULL) {
	gtk_widget_set_sensitive(sr->rvars1, r);
    }

    gtk_widget_set_sensitive(sr->add_button, r);
    gtk_widget_set_sensitive(sr->remove_button, r);
}

static void auto_omit_callback (GtkWidget *w, selector *sr)
{
    gboolean s = button_is_active(w);
    gboolean arrows = !s;

    if (sr->extra[0] != NULL) {
	gtk_widget_set_sensitive(sr->extra[0], s);
    }

    if (sr->extra[1] != NULL) {
	if (button_is_active(sr->extra[1])) {
	    arrows = TRUE;
	}
	gtk_widget_set_sensitive(sr->extra[1], s);
    }

    if (sr->lvars != NULL) {
	gtk_widget_set_sensitive(sr->lvars, arrows);
    }

    if (sr->rvars1 != NULL) {
	gtk_widget_set_sensitive(sr->rvars1, arrows);
    }

    gtk_widget_set_sensitive(sr->add_button, arrows);
    gtk_widget_set_sensitive(sr->remove_button, arrows);
}

static void build_add_test_radios (selector *sr)
{
    GtkWidget *b1, *b2;
    GSList *group;

    vbox_add_vwedge(sr->vbox);

    b1 = gtk_radio_button_new_with_label(NULL, _("Estimate augmented model"));
    pack_switch(b1, sr, TRUE, FALSE, OPT_NONE, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b1));
    b2 = gtk_radio_button_new_with_label(group, _("LM test using auxiliary regression"));
    pack_switch(b2, sr, FALSE, FALSE, OPT_L, 0);
}

static void build_omit_test_radios (selector *sr)
{
    windata_t *vwin = (windata_t *) sr->data;
    GtkWidget *b1, *b2, *b3;
    GSList *group;
    int auto_ok = 0;

    vbox_add_vwedge(sr->vbox);

    b1 = gtk_radio_button_new_with_label(NULL, _("Estimate reduced model"));
    pack_switch(b1, sr, TRUE, FALSE, OPT_NONE, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b1));
    b2 = gtk_radio_button_new_with_label(group, _("Wald test, based on covariance matrix"));
    pack_switch(b2, sr, FALSE, FALSE, OPT_W, 0);

    if (sr->ci != VAROMIT) {
	MODEL *pmod = (MODEL *) vwin->data;

	if (pmod != NULL) {
	    auto_ok = (pmod->ci != PANEL);
	}
    }

    if (auto_ok) {
	group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b2));
	b3 = gtk_radio_button_new_with_label(group, _("Sequential elimination of variables\n"
						      "using two-sided p-value:"));
	g_signal_connect(G_OBJECT(b3), "toggled",
			 G_CALLBACK(auto_omit_callback), sr);

	sr->extra[0] = alpha_spinner(0.10, 0.01);
	pack_switch_with_extra(b3, sr, FALSE, OPT_A, 0, sr->extra[0], NULL);
	gtk_widget_set_sensitive(sr->extra[0], FALSE);

	sr->extra[1] = gtk_check_button_new_with_label(_("Test only selected variables"));
	pack_switch(sr->extra[1], sr, FALSE, FALSE, OPT_NONE, 1);
	gtk_widget_set_sensitive(sr->extra[1], FALSE);
	g_signal_connect(G_OBJECT(sr->extra[1]), "toggled",
			 G_CALLBACK(auto_omit_restrict_callback), sr);
    }
}

static void toggle_nerlove (GtkComboBox *box, selector *sr)
{
    if (gtk_combo_box_get_active(box)) {
	sr->opts |= OPT_N;
    } else {
	sr->opts &= ~OPT_N;
    }
}

static void build_panel_radios (selector *sr)
{
    GtkWidget *b1, *b2, *hbox, *w;
    GSList *group;
    gboolean fe = TRUE;

    if (sr->opts & OPT_W) {
	/* panel weighted least squares */
	return;
    }

    if (model_opt & OPT_U) {
	fe = FALSE;
    }

    b1 = gtk_radio_button_new_with_label(NULL, _("Fixed effects"));
    pack_switch(b1, sr, fe, FALSE, OPT_NONE, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b1));
    b2 = gtk_radio_button_new_with_label(group, _("Random effects"));
    hbox = pack_switch(b2, sr, !fe, FALSE, OPT_U, 0);

    /* random effects transformation selector */
    w = gtk_combo_box_text_new();
    combo_box_append_text(w, _("Swamy-Arora"));
    combo_box_append_text(w, _("Nerlove"));
    gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 0);
    gtk_combo_box_set_active(GTK_COMBO_BOX(w), 0);
    gtk_widget_set_sensitive(w, !fe);
    g_signal_connect(G_OBJECT(w), "changed",
		     G_CALLBACK(toggle_nerlove), sr);
    sensitize_conditional_on(w, b2);
}

static void build_dpanel_radios (selector *sr)
{
    GtkWidget *b1, *b2;
    GSList *group;
    gboolean twostep = FALSE;

    if (dpd_2step || (model_opt & OPT_T)) {
	twostep = TRUE;
    }  

    b1 = gtk_radio_button_new_with_label(NULL, _("One-step estimation"));
    pack_switch(b1, sr, !twostep, FALSE, OPT_NONE, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b1));
    b2 = gtk_radio_button_new_with_label(group, _("Two-step estimation"));
    pack_switch(b2, sr, twostep, FALSE, OPT_T, 0);
}

static void build_heckit_radios (selector *sr)
{
    GtkWidget *b1, *b2;
    GSList *group;
    gboolean ml = TRUE;

    if (model_opt & OPT_T) {
	ml = FALSE;
    }

    b1 = gtk_radio_button_new_with_label(NULL, _("Maximum likelihood estimation"));
    pack_switch(b1, sr, ml, FALSE, OPT_NONE, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b1));
    b2 = gtk_radio_button_new_with_label(group, _("2-step estimation"));
    pack_switch(b2, sr, !ml, FALSE, OPT_T, 0);
}

static void build_xtab_radios (selector *sr)
{
    GtkWidget *b1, *b2, *b3;
    GSList *group;

    b1 = gtk_radio_button_new_with_label(NULL, _("Plain numerical values"));
    pack_switch(b1, sr, TRUE, FALSE, OPT_NONE, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b1));
    b2 = gtk_radio_button_new_with_label(group, _("Show row percentages"));
    pack_switch(b2, sr, FALSE, FALSE, OPT_R, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b2));
    b3 = gtk_radio_button_new_with_label(group, _("Show column percentages"));
    pack_switch(b3, sr, FALSE, FALSE, OPT_C, 0);
}

static void build_ar1_radios (selector *sr)
{
    GtkWidget *b1, *b2, *b3, *b4, *hbox;
    GSList *group;

    b1 = gtk_radio_button_new_with_label(NULL, _("Cochrane-Orcutt"));
    pack_switch(b1, sr, TRUE, FALSE, OPT_NONE, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b1));
    b2 = gtk_radio_button_new_with_label(group, _("Prais-Winsten"));
    pack_switch(b2, sr, FALSE, FALSE, OPT_P, 0);

    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b2));
    b3 = gtk_radio_button_new_with_label(group, _("Hildreth-Lu"));
    hbox = pack_switch(b3, sr, FALSE, FALSE, OPT_H, 0);

    b4 = gtk_check_button_new_with_label(_("Fine-tune using Cochrane-Orcutt"));
    pack_switch_in(hbox, b4, sr, TRUE, TRUE, OPT_B, 0);
    gtk_widget_set_sensitive(b4, FALSE);
    sensitize_conditional_on(b4, b3);

    if (model_opt & OPT_P) {
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b2), TRUE);
    } else if (model_opt & OPT_H) {
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b3), TRUE);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b4), 
				     !(model_opt & OPT_B));
    } else {
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b1), TRUE);
    }	
}

static gboolean arma_estimator_switch (GtkComboBox *box, selector *sr)
{
    if (sr->hess_button != NULL) {
	GtkWidget *xb = sr->x12a_button;

	if (xb == NULL || !button_is_active(xb)) {
	    gchar *s = combo_box_get_active_text(box);

	    gtk_widget_set_sensitive(sr->hess_button, 
				     !strcmp(s, _("Exact Maximum Likelihood")));
	    g_free(s);
	}
    }

    return FALSE;
}

static void build_arma_combo (selector *sr)
{
    GtkWidget *hbox, *combo, *button;
    static const char *opt_strs[] = {
	N_("Exact Maximum Likelihood"),
	N_("Conditional Maximum Likelihood"),
	NULL
    };
    static gretlopt opts[] = { 
	OPT_NONE, 
	OPT_C, 
    };
    static combo_opts arma_opts;
    int deflt = 0;

    arma_opts.strs = opt_strs;
    arma_opts.vals = opts;
    arma_opts.optp = &sr->opts;

    if (model_opt & OPT_C) {
	deflt = 1;
    }

    combo = gretl_opts_combo_full(&arma_opts, deflt, NULL,
				  G_CALLBACK(arma_estimator_switch),
				  sr);
    g_object_set_data(G_OBJECT(combo), "selector", sr);

    hbox = gtk_hbox_new(FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 0);

    button = gtk_button_new_from_stock(GTK_STOCK_PREFERENCES);
    g_signal_connect(G_OBJECT(button), "clicked",
		     G_CALLBACK(call_iters_dialog), combo);
    gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);

    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 5);
}

static void build_coint_combo (selector *sr)
{
    GtkWidget *hbox, *combo;
    static const char *opt_strs[] = {
        N_("test without constant"),
        N_("test with constant"),
        N_("with constant and trend"),
        N_("with constant and quadratic trend"),
	NULL
    };
    static gretlopt opts[] = { 
	OPT_N, 
	OPT_NONE, 
	OPT_T, 
	OPT_R, 
    };
    static combo_opts coint_opts;
    int deflt = 1;

    coint_opts.strs = opt_strs;
    coint_opts.vals = opts;
    coint_opts.optp = &sr->opts;

    combo = gretl_opts_combo(&coint_opts, deflt);

    hbox = gtk_hbox_new(FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 5);
}

static void build_vecm_combo (selector *sr)
{
    GtkWidget *hbox, *combo;
    static const char *opt_strs[] = {
	N_("No constant"),
	N_("Restricted constant"),
	N_("Unrestricted constant"),
	N_("Restricted trend"),
	N_("Unrestricted trend"),
	NULL
    };
    static gretlopt opts[] = {
	OPT_N, 
	OPT_R, 
	OPT_NONE, 
	OPT_A, 
	OPT_T
    };    
    static combo_opts vecm_opts;

    vecm_opts.strs = opt_strs;
    vecm_opts.vals = opts;
    vecm_opts.optp = &sr->opts;

    combo = gretl_opts_combo(&vecm_opts, jcase);

    hbox = gtk_hbox_new(FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 0);

    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 5);
}

#define DSET_DB_OK(d) (d->pd == 1 || (d->structure == TIME_SERIES && \
				      (d->pd == 4 || d->pd == 12)))

static void build_data_export_combo (selector *sr)
{
    GtkWidget *hbox, *label, *combo;
    static const char *opt_strs[] = {
	N_("CSV"),
	N_("gretl datafile (.gdt)"),
	N_("gretl binary datafile (.gdtb)"),
	N_("gretl database (.bin)"),
	N_("GNU R"),
	N_("Octave"),
	N_("JMulTi"),
	N_("PcGive"),
	NULL
    };
    static gretlopt opts[] = {
	OPT_C, 
	OPT_Z,
	OPT_B,
	OPT_D, 
	OPT_R, 
	OPT_M,
	OPT_J,
	OPT_G
    };    
    static combo_opts export_opts;
    int deflt = 0;

    export_opts.strs = opt_strs;
    export_opts.vals = opts;
    export_opts.optp = &sr->opts;

    hbox = gtk_hbox_new(FALSE, 5);

    label = gtk_label_new(_("Select format"));
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);

    if (DSET_DB_OK(dataset)) {
	combo = gretl_opts_combo(&export_opts, deflt);
    } else {
	int masked[2] = {1, 3};
	
	combo = gretl_opts_combo_masked(&export_opts, deflt, masked);
    }
    gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 0);

    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 5);
}

static void build_selector_radios (selector *sr)
{
    if (sr->ci == PANEL) {
	build_panel_radios(sr);
    } else if (sr->ci == DPANEL) {
	build_dpanel_radios(sr);
    } else if (sr->ci == SCATTERS) {
	build_scatters_radios(sr);
    } else if (sr->ci == ADD) {
	build_add_test_radios(sr);
    } else if (sr->ci == OMIT || sr->ci == VAROMIT) {
	build_omit_test_radios(sr);
    } else if (sr->ci == LOGIT || sr->ci == PROBIT) {
	build_pvalues_radios(sr);
    } else if (sr->ci == HECKIT) {
	build_heckit_radios(sr);
    } else if (sr->ci == XTAB) {
	build_xtab_radios(sr);
    } else if (sr->ci == SPEARMAN) {
	build_rankcorr_radios(sr);
    } else if (sr->ci == PCA) {
	build_pca_radios(sr);
    } else if (sr->ci == QUANTREG) {
	build_quantreg_radios(sr);
    } else if (sr->ci == LOGISTIC) {
	build_logistic_radios(sr);
    } else if (sr->ci == AR1) {
	build_ar1_radios(sr);
    }
}

static void build_selector_combo (selector *sr)
{
    if (sr->ci == ARMA) {
	build_arma_combo(sr);
    } else if (sr->ci == COINT) {
	build_coint_combo(sr);
    } else if (sr->ci == VECM || sr->ci == COINT2) {
	build_vecm_combo(sr);
    } else if (sr->ci == IV_GMM) {
	build_gmm_popdown(sr);
    } else if (sr->ci == COUNTMOD) {
	build_count_data_popdown(sr);
    } else if (sr->ci == DURATION) {
	build_duration_popdown(sr);
    } else if (sr->ci == EXPORT) {
	build_data_export_combo(sr);
    }
}

static void lag_selector_button (selector *sr)
{
    GtkWidget *hbox = gtk_hbox_new(FALSE, 5);

    sr->lags_button = gtk_button_new_with_label(_("lags..."));

    g_signal_connect(G_OBJECT(sr->lags_button), "clicked", 
		     G_CALLBACK(lags_dialog_driver), sr);
    if (varlist_row_count(sr, SR_RVARS1, NULL) < 2) {
	gtk_widget_set_sensitive(sr->lags_button, FALSE);
    }

    gtk_box_pack_start(GTK_BOX(hbox), sr->lags_button, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 0);
}

static void selector_doit (GtkWidget *w, selector *sr)
{
    compose_cmdlist(sr);

    if (sr->error == 0) {
	int err;

#ifdef OS_OSX
	/* Hiding the selector window prevents the "next" window
	   (i.e. the one opened by sr->callback) from being hidden
	   behind gretl's main window, on Apple's X11
	*/
	if (open_selector != NULL) {
	    gtk_widget_hide(sr->dlg);
	}
#endif

	err = sr->callback(sr);

	if (!err && open_selector != NULL) {
	    gtk_widget_destroy(sr->dlg);
	}

#ifdef OS_OSX
	if (err && open_selector != NULL) {
	    gtk_widget_show(sr->dlg);
	}	
#endif
    } 
}

static void build_selector_buttons (selector *sr)
{
    GtkWidget *tmp;

    if (sr->ci != PRINT && sr->ci != SUMMARY && !FNPKG_CODE(sr->ci) &&
	sr->ci != DEFINE_LIST && sr->ci != DEFINE_MATRIX &&
	sr->ci != ELLIPSE && !SAVE_DATA_ACTION(sr->ci)) {
	/* add a Help button if appropriate */
	int ci = sr->ci;

	if (sr->ci == OLOGIT || sr->ci == MLOGIT) {
	    ci = LOGIT;
	} else if (sr->ci == OPROBIT) {
	    ci = PROBIT;
	} else if (IV_MODEL(sr->ci)) {
	    ci = IVREG;
	}

	context_help_button(sr->action_area, ci);
    }

    if (sr->ci != EDIT_FUNCTIONS) {
	tmp = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
	gtk_widget_set_can_default(tmp, TRUE);
	gtk_container_add(GTK_CONTAINER(sr->action_area), tmp);
	g_signal_connect(G_OBJECT(tmp), "clicked", 
			 G_CALLBACK(clear_vars), sr);
    }

    tmp = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
    gtk_widget_set_can_default(tmp, TRUE);
    gtk_container_add(GTK_CONTAINER(sr->action_area), tmp);
    g_signal_connect(G_OBJECT(tmp), "clicked",
		     G_CALLBACK(cancel_selector), sr);

    tmp = gtk_button_new_from_stock(GTK_STOCK_OK);
    gtk_widget_set_can_default(tmp, TRUE);
    gtk_container_add(GTK_CONTAINER(sr->action_area), tmp);
    g_signal_connect(G_OBJECT(tmp), "clicked", 
		     G_CALLBACK(selector_doit), sr);
    gtk_widget_grab_default(tmp);
}

static int list_show_var (int v, int ci, int show_lags)
{
    int ret = 1;

    lags_hidden = 0;

    if (v == 0 && (ci == DEFINE_LIST || ci == DEFINE_MATRIX)) {
	;
    } else if (v == 0 && (!MODEL_CODE(ci) || ci == ARMA || ci == GARCH)) {
	ret = 0;
    } else if (v == 0 && (ci == LOESS || ci == NADARWAT)) {
	ret = 0;
    } else if (series_is_hidden(dataset, v)) {
	ret = 0;
    } else if (is_panel_group_names_series(dataset, v)) {
	ret = 0;
    } else if (!show_lags && series_get_lag(dataset, v)) {
	lags_hidden = 1;
	ret = 0;
    } else if (ci == XTAB) {
	ret = 0;
	if (series_is_discrete(dataset, v) || 
	    gretl_isdiscrete(dataset->t1, dataset->t2, dataset->Z[v])) {
	    ret = 1;
	}
    }

    return ret;
}

/* callback from successful generation of a new variable or
   variables via click on the selector's "Add variable button"
*/

void selector_register_genr (int newvars, gpointer p)
{
    selector *sr = p;
    GtkListStore *store;
    GtkTreeIter iter;
    int i, v;

    store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(sr->lvars)));
    tree_model_get_iter_last(GTK_TREE_MODEL(store), &iter);

    for (i=0; i<newvars; i++) {
	v = dataset->v - newvars + i;
	if (list_show_var(v, sr->ci, 0)) {
	    list_append_var_simple(store, &iter, v);
	}
    }
}

static void new_var_callback (GtkWidget *w, selector *sr)
{
    edit_dialog(GENR, _("gretl: add var"), 
		_("Enter formula for new variable"),
		NULL, do_selector_genr, sr, 
		VARCLICK_INSERT_NAME, sr->dlg);  
}

static GtkWidget *add_var_button (selector *sr)
{
    GtkWidget *img = gtk_image_new_from_stock(GTK_STOCK_ADD, 
					      GTK_ICON_SIZE_MENU);
    GtkWidget *button = gtk_button_new();

    gtk_container_add(GTK_CONTAINER(button), img);
    gretl_tooltips_add(GTK_WIDGET(button), _("New variable"));
    g_signal_connect(button, "clicked", G_CALLBACK(new_var_callback), sr);

    return button;
}

static void selection_dialog_add_top_label (selector *sr)
{
    GtkWidget *label;
    gchar *s = NULL;
    int ci = sr->ci;

    if (MODEL_CODE(ci) || VEC_CODE(ci) || NONPARAM_CODE(ci)) {
	GtkWidget *hbox, *button;

	hbox = gtk_hbox_new(FALSE, 0); 
	button = add_var_button(sr);
	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
	s = est_str(ci);
	if (s != NULL) {
	    label = gtk_label_new(_(s));
	    gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, FALSE, 0);
	    label = gtk_label_new("");
	    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 16);
	}
	gtk_box_pack_start(GTK_BOX(sr->vbox), hbox, FALSE, FALSE, 0);
    } else {
	if (ci == GR_XY)
	    s = N_("XY scatterplot");
	else if (ci == GR_IMP)
	    s = N_("plot with impulses");
	else if (ci == GR_3D)
	    s = N_("3D plot");
	else if (ci == SCATTERS)
	    s = N_("multiple scatterplots");
	else if (ci == GR_DUMMY)
	    s = N_("factorized plot");
	else if (ci == GR_FBOX)
	    s = N_("factorized boxplot");
	else if (ci == GR_XYZ)
	    s = N_("scatterplot with control");
	else if (ci == SAVE_FUNCTIONS) 
	    s = N_("functions to package");
	else if (ci == ANOVA) 
	    s = N_("ANOVA");

	if (s != NULL) {
	    label = gtk_label_new(_(s));
	    gtk_box_pack_start(GTK_BOX(sr->vbox), label, FALSE, FALSE, 5);
	}
    }
}

static int has_0 (const int *list)
{
    int i;

    for (i=1; i<=list[0]; i++) {
	if (list[i] == 0) {
	    return 1;
	}
    }

    return 0;
}

static void primary_rhs_varlist (selector *sr)
{
    GtkTreeModel *mod;
    GtkListStore *store;
    GtkTreeIter iter;
    GtkWidget *hbox;
    GtkWidget *tmp = NULL;
    GtkWidget **lptr = NULL;
    int i;

    if (COINT_CODE(sr->ci)) {
	tmp = gtk_label_new(_("Variables to test"));
    } else if (VEC_CODE(sr->ci)) {
	tmp = gtk_label_new(_("Endogenous variables"));
    } else if (sr->ci == BIPROBIT) {
	tmp = gtk_label_new(_("Equation 1 regressors"));
    } else if (MODEL_CODE(sr->ci)) {
	tmp = gtk_label_new(_("Regressors"));
    } else if (sr->ci == GR_XY || sr->ci == GR_IMP) {
	tmp = gtk_label_new(_("Y-axis variables"));
    } else if (sr->ci == SCATTERS) {
	multiplot_label = tmp = gtk_label_new(_("X-axis variables"));
    } else if (FNPKG_CODE(sr->ci)) {
	tmp = gtk_label_new(_("Public functions"));
    }

    if (tmp != NULL) {
	table_add_right(sr, tmp, 1);
    }

    hbox = gtk_hbox_new(FALSE, 5);

    /* add the actual primary RHS listbox */
    sr->rvars1 = var_list_box_new(GTK_BOX(hbox), sr, SR_RVARS1);
    mod = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->rvars1));

    if (sr->ci == ARMA) {
	lptr = &sr->lags_button;
    } else if (sr->ci == VAR) { /* FIXME VECM? */
	lptr = &sr->extra[EXTRA_LAGS];
    }

    /* add push/pull buttons */
    push_pull_buttons(sr, add_to_rvars1_callback,
		      remove_from_rvars1_callback,
		      lptr, OPT_NONE);

    store = GTK_LIST_STORE(mod);
    gtk_list_store_clear(store);
    gtk_tree_model_get_iter_first(mod, &iter);

    if (MODEL_CODE(sr->ci)) {
	if (sr->ci == ARMA || sr->ci == GARCH || NONPARAM_CODE(sr->ci)) {
	    ; /* skip */
	} else if (xlist == NULL || has_0(xlist)) {
	    /* stick the constant in by default */
	    list_append_var(mod, &iter, 0, sr, SR_RVARS1);
	} 
	if (xlist != NULL) {
	    /* we have a saved list of regressors */
	    int nx = 0;

	    for (i=1; i<=xlist[0]; i++) {
		if (xlist[i] != 0) {
		    list_append_var(mod, &iter, xlist[i], sr, SR_RVARS1);
		    nx++;
		}
	    }
	    if (nx > 0 && sr->ci == ARMA) {
		gtk_widget_set_sensitive(sr->lags_button, TRUE);
	    }
	}
    } else if (VEC_CODE(sr->ci) && veclist != NULL && veclist[0] > 0) {
	for (i=1; i<=veclist[0]; i++) {
	    list_append_var(mod, &iter, veclist[i], sr, SR_RVARS1);
	}
	if (sr->extra[EXTRA_LAGS] != NULL) {
	    gtk_widget_set_sensitive(sr->extra[EXTRA_LAGS], TRUE);
	}
    }

    table_add_right(sr, hbox, 0);
}

/* On opening the selector dialog, select (if possible) the
   first relevant item in the listbox on the left.
*/

static void selector_set_focus (selector *sr)
{
    if (sr->lvars != NULL) {
	GtkTreeModel *mod = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->lvars));
	GtkTreeSelection *sel;
	GtkTreeIter iter;
	gboolean do_sel;
	int v;

	gtk_widget_grab_focus(sr->lvars);
	do_sel = gtk_tree_model_get_iter_first(mod, &iter);
	if (do_sel) {
	    if (!FNPKG_CODE(sr->ci) && list_show_var(0, sr->ci, 0)) {
		/* don't select the constant: skip a row */
		do_sel = gtk_tree_model_iter_next(mod, &iter);
	    }
	    while (do_sel && MODEL_CODE(sr->ci)) {
		/* skip named lists? */
		gtk_tree_model_get(mod, &iter, COL_ID, &v, -1);
		if (v > 0) {
		    break;
		}
		do_sel = gtk_tree_model_iter_next(mod, &iter);
	    }
	}
	if (do_sel) {
	    sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(sr->lvars));
	    gtk_tree_selection_select_iter(sel, &iter);
	}
    }
}

static void list_append_named_lists (GtkListStore *store,
				     GtkTreeIter *iterp)
{
    GList *llist = user_var_names_for_type(GRETL_TYPE_LIST);
    GList *tail = llist;

    while (tail != NULL) {
	gtk_list_store_append(store, iterp);
	gtk_list_store_set(store, iterp, COL_ID, -1, COL_LAG, 0,
			   COL_NAME, tail->data, -1);
	tail = tail->next;
    }

    g_list_free(llist);    
}

selector *selection_dialog (int ci, const char *title, int (*callback)())
{
    GtkListStore *store;
    GtkTreeIter iter;
    GtkWidget *left_box;
    selector *sr;
    int preselect;
    int yvar = 0;
    int i;

    preselect = presel;
    presel = 0;

    if (open_selector != NULL) {
	gtk_window_present(GTK_WINDOW(open_selector->dlg));
	return open_selector;
    }

    sr = mymalloc(sizeof *sr);

    if (sr == NULL) {
	return NULL;
    }

    selector_init(sr, ci, title, callback, NULL, NULL, SELECTOR_FULL);
    selection_dialog_add_top_label(sr);

    /* the following encloses LHS lvars, depvar and indepvar stuff */
    sr->table = gtk_table_new(sr->n_rows, 3, FALSE);

    /* LHS: list of elements to choose from */
    left_box = gtk_hbox_new(FALSE, 0);
    sr->lvars = var_list_box_new(GTK_BOX(left_box), sr, SR_LVARS);
    store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(sr->lvars)));
    gtk_list_store_clear(store);
    gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);

    if (FNPKG_CODE(ci)) {
	functions_list(sr);
    } else {
	for (i=0; i<dataset->v; i++) {
	    if (i == 1 && (MODEL_CODE(ci) || VEC_CODE(ci))) {
		list_append_named_lists(store, &iter);
	    }
	    if (list_show_var(i, ci, 0)) {
		list_append_var_simple(store, &iter, i);
	    }
	}
    }

    if (MODEL_CODE(ci) || NONPARAM_CODE(ci) || ci == ANOVA) { 
	/* models: top right -> dependent variable */
	yvar = build_depvar_section(sr, preselect);
    } else if (ci == GR_XY || ci == GR_IMP || ci == GR_DUMMY ||
	       ci == SCATTERS || ci == GR_3D || ci == GR_XYZ ||
	       ci == GR_FBOX) {
	/* graphs: top right -> x-axis variable or equivalent */
	build_x_axis_section(sr, preselect);
    } else if (FNPKG_CODE(ci)) {
	primary_rhs_varlist(sr);
    }

    /* middle right: used for some estimators and factored plot */
    if (ci == WLS || ci == AR || ci == ARCH || USE_ZLIST(ci) ||
	VEC_CODE(ci) || ci == COUNTMOD || ci == DURATION || 
	ci == QUANTREG || ci == INTREG || ci == TOBIT ||
	ci == DPANEL || THREE_VARS_CODE(ci) || NONPARAM_CODE(ci)) {
	build_mid_section(sr);
    }
    
    if (ci == GR_FBOX || THREE_VARS_CODE(ci)) {
	/* choose extra var for plot */
	extra_plotvar_box(sr);
    } else if (AUX_LAST(ci)) {
	secondary_rhs_varlist(sr);
    } else if (!NONPARAM_CODE(ci)) {
	/* all other uses: list of vars */
	primary_rhs_varlist(sr);
    }

    /* add left-hand column now we know how tall it should be */
    table_add_left(sr, left_box);

    /* pack the whole central section into the dialog's vbox */
    gtk_box_pack_start(GTK_BOX(sr->vbox), sr->table, TRUE, TRUE, 0);

    if (ci == ARMA) {
	/* AR, D, MA for ARIMA */
	build_arma_spinners(sr);
    } else if (ci == GARCH) { 
	/* P and Q for GARCH */
	build_garch_spinners(sr);
    }

    /* toggle switches for some cases */
    if (WANT_TOGGLES(ci)) {
	build_selector_switches(sr);
    }

#if !defined(MAC_NATIVE) || !defined(PKGBUILD)
    if (ci == GR_3D) {
	build_selector_switches(sr);
    }
#endif

    /* radio buttons for some */
    if (want_radios(sr)) {
	build_selector_radios(sr);
    }

    /* drop-down selector for some */
    if (want_combo(sr)) {
	build_selector_combo(sr);
    }    

    /* plus lag selection stuff, if relevant */
    if (dataset_lags_ok(dataset)) {
	if (ci == GR_XY || ci == GR_IMP || ci == GR_DUMMY || \
	    ci == SCATTERS || ci == GR_XYZ) {
	    unhide_lags_switch(sr);
	}
	if (MODEL_CODE(ci) && ci != ARMA) {
	    lag_selector_button(sr);
	} 
	if (select_lags_depvar(ci) && yvar > 0) {
	    maybe_activate_depvar_lags(sr->depvar, sr);
	    maybe_insert_depvar_lags(sr, yvar, 0);
	}
    } 

    /* buttons: Help, Clear, Cancel, OK */
    build_selector_buttons(sr);

    gtk_widget_show_all(sr->dlg);
    selector_set_focus(sr);

    return sr;
}

static char *get_topstr (int cmdnum)
{
    switch (cmdnum) {    
    case LOGS:
	return N_("Select variables for logging");
    case LAGS:
	return N_("Select variables for lagging");
    case SQUARE:
	return N_("Select variables to square");
    case DIFF:
	return N_("Select variables to difference");
    case LDIFF:
	return N_("Select variables to log-difference");
    case ADD:
	return N_("Select variables to add");
    case OMIT:
    case VAROMIT:
	return N_("Select variables to omit");
    case COEFFSUM:
	return N_("Select coefficients to sum");
    case SPEARMAN:
	return N_("Select two variables");
    case QQPLOT:
	return N_("Select one or two variables");
    case ELLIPSE:
	return N_("Confidence region: select two variables");
    case PRINT:
	return N_("Select variables to display");
    case GR_PLOT: 
    case GR_BOX: 
    case TSPLOTS:
	return N_("Select variables to plot");
    case SAVE_DATA:
    case SAVE_DATA_AS:
    case EXPORT_CSV:
    case EXPORT_R:
    case EXPORT_OCTAVE:
    case EXPORT_JM:
    case EXPORT_DAT:
    case EXPORT:
	return N_("Select variables to save");
    case COPY_CSV:
	return N_("Select variables to copy");
    case DEFINE_LIST:
	return N_("Define named list");
    default:
	return NULL;
    }
}

static int add_omit_list (gpointer p, selector *sr)
{
    windata_t *vwin = (windata_t *) p;
    GRETL_VAR *var = NULL;
    MODEL *pmod = NULL;
    GtkListStore *store;
    GtkTreeIter iter;
    int i, nvars = 0;

    if (sr->ci == VAROMIT) {
	var = (GRETL_VAR *) vwin->data;
    } else {
	pmod = (MODEL *) vwin->data;
    }

    store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(sr->lvars)));
    gtk_list_store_clear(store);
    gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);

    if (sr->ci == ELLIPSE) {
	char pname[VNAMELEN];
	int nc = gretl_model_get_int(pmod, "base-coeffs"); /* FIXME? */

	if (nc == 0) {
	    nc = pmod->ncoeff;
	}

	for (i=0; i<nc; i++) {
	    gretl_model_get_param_name(pmod, dataset, i, pname);
	    gtk_list_store_append(store, &iter);
	    gtk_list_store_set(store, &iter, 
			       COL_ID, i, COL_LAG, 0,
			       COL_NAME, pname,
			       -1);
	    nvars++;
	}
	g_object_set_data(G_OBJECT(sr->lvars), "keep-names", 
			  GINT_TO_POINTER(1));
    } else if (sr->ci == OMIT || sr->ci == ADD || sr->ci == COEFFSUM) {
	int *xlist = gretl_model_get_x_list(pmod);

	if (xlist == NULL) {
	    return 0;
	}

	if (sr->ci == ADD) {
	    int dv = gretl_model_get_depvar(pmod);

	    for (i=0; i<dataset->v; i++) {
		if (!in_gretl_list(xlist, i) && i != dv &&
		    !series_is_hidden(dataset, i)) {
		    gtk_list_store_append(store, &iter);
		    gtk_list_store_set(store, &iter, 
				       COL_ID, i, COL_LAG, 0, 
				       COL_NAME, dataset->varname[i],
				       -1);
		    nvars++;
		}
	    }
	} else {	    
	    for (i=1; i<=xlist[0]; i++) {
		gtk_list_store_append(store, &iter);
		gtk_list_store_set(store, &iter, 
				   COL_ID, xlist[i], COL_LAG, 0,
				   COL_NAME, dataset->varname[xlist[i]],
				   -1);
		nvars++;
	    }
	} 
	free(xlist);
    } else if (sr->ci == VAROMIT) {
	const int *xlist;

	xlist = gretl_VAR_get_exo_list(var);
	if (xlist != NULL) {
	    for (i=1; i<=xlist[0]; i++) {
		gtk_list_store_append(store, &iter);
		gtk_list_store_set(store, &iter, 
				   COL_ID, xlist[i], COL_LAG, 0,
				   COL_NAME, dataset->varname[xlist[i]],
				   -1);
		nvars++;
	    }	    
	}
    } 

    return nvars;
}

static void functions_list (selector *sr)
{
    GtkListStore *store;
    GtkTreeIter iter;
    const char *fnname;
    int idx;

    store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(sr->lvars)));
    gtk_list_store_clear(store);
    gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);

    function_names_init();
    
    while ((fnname = next_available_function_name(&idx)) != NULL) {
	gtk_list_store_append(store, &iter);
	gtk_list_store_set(store, &iter,
			   COL_ID, idx, COL_LAG, 0,
			   COL_NAME, fnname, -1);
    }

    g_object_set_data(G_OBJECT(sr->lvars), "keep-names",
		      GINT_TO_POINTER(1));
}

static GtkWidget *simple_selection_top_label (int ci, const char *title)
{
    const char *s = get_topstr(ci);
    GtkWidget *label = NULL;
    GtkWidget *hbox = NULL;

    if (s != NULL && *s != '\0') {
	label = gtk_label_new(_(s));
    } else if (title != NULL) {
	if (!strncmp(title, "gretl: ", 7)) {
	    title += 7;
	}
	label = gtk_label_new(title);
    }

    if (label != NULL) {
	hbox = gtk_hbox_new(FALSE, 5);
	gtk_container_add(GTK_CONTAINER(hbox), label);
    }

    return hbox;
}

static void 
add_to_rvars1_from_named_list (selector *sr, const int *list,
			       int clear)
{
    GtkTreeModel *mod;
    GtkTreeIter iter;
    int i, v;

    mod = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->rvars1));
    if (mod == NULL) {
	return;
    }

    gtk_tree_model_get_iter_first(mod, &iter);

    if (clear) {
	gtk_list_store_clear(GTK_LIST_STORE(mod));
    }

    for (i=1; i<=list[0]; i++) {
	v = list[i];
	gtk_list_store_append(GTK_LIST_STORE(mod), &iter);
	gtk_list_store_set(GTK_LIST_STORE(mod), &iter, 
			   COL_ID, v, COL_LAG, 0, 
			   COL_NAME, dataset->varname[v], -1);
    }
}

static void maybe_set_tsplot_vars (selector *sr)
{
    int mc = mdata_selection_count();

    if (mc > 1 && mc < 7) {
	set_vars_from_main(sr);
    }
}

/* the following is called on start-up when defining a list */

static void maybe_set_listdef_vars (selector *sr)
{
    const char *lname = selector_entry_text(sr);

    if (lname != NULL && *lname != 0) {
	int *list = get_list_by_name(lname);

	if (list != NULL) {
	    add_to_rvars1_from_named_list(sr, list, 0);
	}
    } else if (sr->data == NULL) {
	/* called from main window */
	int mc = mdata_selection_count();

	if (mc > 1) {
	    set_vars_from_main(sr);
	}	
    }
}

/* callback from change in combo selector, when defining or editing
   a list */

static void listdef_vars_callback (GtkComboBox *b, selector *sr)
{
    const char *lname = selector_entry_text(sr);

    if (lname != NULL && *lname != '\0') {
	int *list = get_list_by_name(lname);

	if (list != NULL) {
	    add_to_rvars1_from_named_list(sr, list, 1);
	}
    } 
}

static void selector_add_top_entry (selector *sr)
{
    const char *lname = NULL;
    GtkWidget *src = NULL;
    GList *lnames = NULL;
    GtkWidget *combo = NULL;
    GtkWidget *label;
    GtkWidget *entry;

    if (sr->data != NULL) {
	/* called from function call dialog */
	src = GTK_WIDGET(sr->data);
	lname = gtk_entry_get_text(GTK_ENTRY(src));
    } else {
	lnames = user_var_names_for_type(GRETL_TYPE_LIST);
    }

    label = gtk_label_new(_("Name of list"));
    table_add_right(sr, label, 1);

    if (lnames != NULL) {
	combo = combo_box_text_new_with_entry();
	set_combo_box_strings_from_list(combo, lnames);
	entry = gtk_bin_get_child(GTK_BIN(combo));
	g_signal_connect(G_OBJECT(combo), "changed",
			 G_CALLBACK(listdef_vars_callback), sr);
    } else {
	entry = gtk_entry_new();
    }

    sr->extra[0] = entry;

    gtk_entry_set_max_length(GTK_ENTRY(entry), VNAMELEN);
    gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);

    if (src != NULL) {
	if (lname != NULL && *lname != '\0' && strcmp(lname, "null")) {
	    gtk_entry_set_text(GTK_ENTRY(entry), lname);
	} else {
	    int argnum = widget_get_int(src, "argnum");
	    gchar *tmp = g_strdup_printf("arg%d", argnum + 1);

	    gtk_entry_set_text(GTK_ENTRY(entry), tmp);
	    g_free(tmp);
	}
	gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
    }

    if (combo != NULL) {
	table_add_right(sr, combo, 1);
    } else {
	table_add_right(sr, entry, 1);
    }

    table_add_vwedge(sr);
}

#if 0 /* not ready */
static int ols_omit_select (windata_t *vwin)
{
    if (vwin->role == VIEW_MODEL) {
	MODEL *pmod = vwin->data;

	return bootstrap_ok(pmod->ci);
    } else {
	return 0;
    }
}
#endif

static void maybe_prefill_RHS (selector *sr)
{
    int *list = main_window_selection_as_list();

    if (list != NULL && list[0] >= 2) {
	GtkListStore *store;
	GtkTreeIter iter;
	int i;

	store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(sr->rvars1)));
	gtk_list_store_clear(store);
	gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);

	for (i=1; i<=list[0]; i++) {
	    list_append_var_simple(store, &iter, list[i]);
	}
    }

    free(list);
}

selector *
simple_selection_with_data (int ci, const char *title, int (*callback)(), 
			    GtkWidget *parent, gpointer data) 
{
    GtkListStore *store;
    GtkTreeIter iter;
    GtkWidget *left_box, *right_box;
    GtkWidget *tmp;
    selector *sr;
    int nleft = 0;
    int i;

    if (open_selector != NULL) {
	gtk_window_present(GTK_WINDOW(open_selector->dlg));
	return open_selector;
    }

    sr = mymalloc(sizeof *sr);
    if (sr == NULL) {
	return NULL;
    }

    selector_init(sr, ci, title, callback, parent, data, SELECTOR_SIMPLE);

    tmp = simple_selection_top_label(ci, title);
    if (tmp != NULL) {
	gtk_box_pack_start(GTK_BOX(sr->vbox), tmp, FALSE, FALSE, 0);
    } 

    /* tables to hold vboxes and buttons */
    sr->table = gtk_table_new(1, 3, FALSE);

    /* holds list of elements available for selection */
    left_box = gtk_vbox_new(FALSE, 5);

    sr->lvars = var_list_box_new(GTK_BOX(left_box), sr, SR_LVARS);
    store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(sr->lvars)));
    gtk_list_store_clear(store);
    gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);

    if (ci == OMIT || ci == ADD || ci == COEFFSUM ||
	ci == ELLIPSE || ci == VAROMIT) {
        nleft = add_omit_list(data, sr);
    } else {
	int start = (ci == DEFINE_LIST || ci == DEFINE_MATRIX)? 0 : 1;

	for (i=start; i<dataset->v; i++) {
	    if (i == 1 && SHOW_LISTS_CODE(ci)) {
		list_append_named_lists(store, &iter);
	    }
	    if (list_show_var(i, ci, 0)) {
		list_append_var_simple(store, &iter, i);
		nleft++;
	    }
	}
    }

    sr->n_left = nleft;

    right_box = gtk_vbox_new(FALSE, 5);
    sr->rvars1 = var_list_box_new(GTK_BOX(right_box), sr, SR_RVARS1);

    /* pre-fill RHS box? Only if we have 2 or more vars selected in the
       main window and if the command is "suitable"
    */
    if (RHS_PREFILL(ci)) {
	maybe_prefill_RHS(sr);
    }

    /* entry field for some uses */
    if (ci == DEFINE_LIST) {
	selector_add_top_entry(sr);
    }

    /* put buttons into mid-section */
    push_pull_buttons(sr, add_to_rvars1_callback,
		      remove_from_rvars1_callback,
		      NULL, OPT_A | OPT_R);

    /* pack RHS */
    table_add_right(sr, right_box, 0);

    /* pack left-hand stuff */
    table_add_left(sr, left_box);

    /* pack the whole central section into the dialog's vbox */
    gtk_box_pack_start(GTK_BOX(sr->vbox), sr->table, TRUE, TRUE, 0);

    /* unhide lags check box? */
    if ((sr->ci == DEFINE_LIST || sr->ci == EXPORT || SAVE_DATA_ACTION(sr->ci)) 
	&& lags_hidden) {
	unhide_lags_switch(sr);
    }

    /* radio buttons? */
    if (want_radios(sr)) {
	build_selector_radios(sr);
    }

    /* toggle switches for some cases */
    if (WANT_TOGGLES(ci)) {
	build_selector_switches(sr);
    }

    /* combo/dropdown list? */
    if (want_combo(sr)) {
	build_selector_combo(sr);
    }

    if (ci == ELLIPSE) {
	build_ellipse_spinner(sr);
    } 

#if 0 /* not ready */
    /* bootstrap check box? */
    if (ci == OMIT && ols_omit_select(p)) {
	test_boot_switch(sr);
    }
#endif

    /* buttons: Help, Clear, Cancel, OK */
    build_selector_buttons(sr);

    if (TWO_VARS_CODE(sr->ci) && sr->ci != ELLIPSE &&
	mdata_selection_count() == 2) {
	set_vars_from_main(sr);
    } else if (nleft == 1) {
	select_singleton(sr);
    } else if (sr->ci == DEFINE_LIST) {
	maybe_set_listdef_vars(sr);
    } else if (sr->ci == TSPLOTS) {
	maybe_set_tsplot_vars(sr);
    }

    if (nleft == 0) {
	gtk_widget_destroy(sr->dlg);
	warnbox(_("No suitable data are available"));
    } else if ((ci == COEFFSUM || ci == ELLIPSE) && nleft < 2) {
	gtk_widget_destroy(sr->dlg);
	warnbox(_("No suitable data are available"));
    } else {
	gtk_widget_show_all(sr->dlg);
    }

    if (sr->ci == DEFINE_MATRIX) {
	selector_set_blocking(sr, 1);
    }

    return sr;
}

selector *simple_selection (int ci, const char *title, int (*callback)(), 
			    GtkWidget *parent)
{
    return simple_selection_with_data(ci, title, callback, parent, NULL);
}


static void restore_vwin_menu (GtkWidget *w, windata_t *vwin)
{
    if (vwin != NULL && vwin->mbar != NULL) {
	gtk_widget_set_sensitive(vwin->mbar, TRUE);
    }
}

selector *
simple_selection_for_viewer (int ci, const char *title, int (*callback)(), 
			     windata_t *vwin)
{
    selector *sr;

    sr = simple_selection_with_data(ci, title, callback, 
				    vwin_toplevel(vwin),
				    vwin);

    if (sr != NULL && vwin->mbar != NULL) {
	if (window_is_tab(vwin)) {
	    tabwin_register_dialog(sr->dlg, vwin_toplevel(vwin));
	} else {
	    gtk_widget_set_sensitive(vwin->mbar, FALSE);
	    g_signal_connect(sr->dlg, "destroy", 
			     G_CALLBACK(restore_vwin_menu), 
			     vwin);
	}
    }	

    return sr;
}

struct list_maker {
    char *liststr;
    size_t len;
    int overflow;
};

static void selection_strlen (GtkTreeModel *model, GtkTreePath *path,
			      GtkTreeIter *iter, struct list_maker *lmkr)
{
    gchar *varnum = NULL;

    gtk_tree_model_get(model, iter, COL_ID, &varnum, -1);
    lmkr->len += strlen(varnum) + 1;
    g_free(varnum);

    if (lmkr->len > MAXLEN - 32) {
	lmkr->overflow = 1;
    }
}

static void selection_add_item (GtkTreeModel *model, GtkTreePath *path,
				GtkTreeIter *iter, struct list_maker *lmkr)
{
    gchar *varnum = NULL;

    gtk_tree_model_get(model, iter, COL_ID, &varnum, -1);
    strcat(lmkr->liststr, " ");
    strcat(lmkr->liststr, varnum);
    g_free(varnum);
}

char *main_window_selection_as_string (void) 
{
    struct list_maker lmkr = {NULL, 1, 0};
    GtkTreeSelection *select;
    char *ret = NULL;

    select = gtk_tree_view_get_selection(GTK_TREE_VIEW(mdata->listbox));

    gtk_tree_selection_selected_foreach(select, 
					(GtkTreeSelectionForeachFunc) 
					selection_strlen,
					&lmkr); 
    if (lmkr.overflow) {
	errbox(_("Too many items were selected"));
    } else {
	lmkr.liststr = mymalloc(lmkr.len);
	if (lmkr.liststr != NULL) {
	    lmkr.liststr[0] = '\0';
	    gtk_tree_selection_selected_foreach(select, 
						(GtkTreeSelectionForeachFunc) 
						selection_add_item,
						&lmkr);
	    ret = lmkr.liststr;
	} 
    }	

    return ret;
}

static gchar *get_or_set_storelist (const char *s, int reset)
{
    static gchar *storelist;

    if (s != NULL) {
	/* set storelist */
	g_free(storelist);
	if (*s == '\0') {
	    storelist = NULL;
	} else {
	    storelist = g_strdup(s);
	}
	return NULL;
    } else if (reset) {
	g_free(storelist);
	storelist = NULL;
	return NULL;
    } else {
	/* retrieve storelist (and NULL-ify it) */
	gchar *ret = storelist;

	storelist = NULL;
	return ret;
    }
}

gchar *get_selector_storelist (void)
{
    return get_or_set_storelist(NULL, 0);
}

void set_selector_storelist (const char *s)
{
    if (s == NULL) {
	/* forces a reset */
	get_or_set_storelist(NULL, 1);
    } else {
	get_or_set_storelist(s, 0);
    }
}

static int pkg_add_remove_callback (selector *sr)
{
    void *p = sr->data;

    set_selector_storelist(sr->cmdlist);
    gtk_widget_destroy(sr->dlg);
    revise_function_package(p);

    return 0;
}

static int functions_selected_callback (selector *sr)
{
    if ((sr->cmdlist == NULL || *sr->cmdlist == '\0') && sr->n_left == 0) {
	/* nothing selected (can't happen?) */
	return 0;
    }

    if (sr->cmdlist != NULL) {
	set_selector_storelist(sr->cmdlist);
    }

    gtk_widget_destroy(sr->dlg);
    edit_new_function_package();

    return 0;
}

static int data_export_selection_callback (selector *sr)
{
    int ci = sr->ci;

    if ((sr->cmdlist == NULL || *sr->cmdlist == '\0') && sr->n_left == 0) {
	warnbox(_("No variables are selected"));
	/* return non-zero to block closing of dialog */
	return 1;
    }

    if (ci == EXPORT) {
	/* set the specific export format based on the
	   option from the series selector
	*/
	if (sr->opts & OPT_C) {
	    ci = EXPORT_CSV;
	} else if (sr->opts & OPT_R) {
	    ci = EXPORT_R;
	} else if (sr->opts & OPT_M) {
	    ci = EXPORT_OCTAVE;
	} else if (sr->opts & OPT_J) {
	    ci = EXPORT_JM;
	} else if (sr->opts & OPT_G) {
	    ci = EXPORT_DAT;
	} else if (sr->opts & OPT_D) {
	    ci = EXPORT_DB;
	} else if (sr->opts & OPT_B) {
	    ci = EXPORT_GDTB;
	} else {
	    ci = EXPORT_GDT;
	}
    }

    if (sr->cmdlist != NULL) {
	set_selector_storelist(sr->cmdlist);
    }

    gtk_widget_destroy(sr->dlg);

    if (ci == EXPORT_CSV) {
	int resp = csv_options_dialog(EXPORT_CSV, GRETL_OBJ_DSET,
				      NULL);

	if (canceled(resp)) {
	    set_selector_storelist(NULL);
	    return 0;
	} 
    }

    if (ci != COPY_CSV) {
	file_selector(ci, FSEL_DATA_NONE, NULL);
    }

    return 0;
}

void data_export_selection_wrapper (int file_ci)
{
    selector *sr;

    set_selector_storelist(NULL);

    sr = simple_selection(file_ci, (file_ci == COPY_CSV)? 
			  _("Copy data") : _("Export data"), 
			  data_export_selection_callback, 
			  NULL);
    if (sr != NULL) {
	selector_set_blocking(sr, 0);
    }
}

void functions_selection_wrapper (void)
{
    int err;

    set_selector_storelist(NULL);

    err = no_user_functions_check();

    if (!err) {
	selector *sr;

	sr = selection_dialog(SAVE_FUNCTIONS, _("Save functions"), 
			      functions_selected_callback);
	if (sr != NULL) {
	    selector_set_blocking(sr, 1);
	}
    }
}

void add_remove_functions_dialog (char **pubnames, int npub,
				  char **privnames, int npriv,
				  void *p1, void *p2)
{
    fnpkg *pkg = p1;
    void *finfo = p2;
    const char *title = NULL;
    selector *sr = NULL;

    if (pkg != NULL) {
	title = function_package_get_name(pkg);
    }

    if (title == NULL || *title == '\0') {
	title = _("Edit package list");
    }

    sr = selection_dialog(EDIT_FUNCTIONS, title, 
			  pkg_add_remove_callback); 

    if (sr != NULL) {
	GtkWidget *w;
	GtkTreeModel *model;
	GtkListStore *store;
	GtkListStore *lstore = NULL;
	GtkTreeIter iter, liter;
	char **names;
	int i, j, n, idx;

	sr->data = finfo;

	if (pkg != NULL) {
	    /* some functions will be attached to pkg, and we'll
	       want to ad these on the left, below
	    */
	    model = gtk_tree_view_get_model(GTK_TREE_VIEW(sr->lvars));
	    lstore = GTK_LIST_STORE(model);
	    tree_model_get_iter_last(model, &liter);
	}

	for (j=0; j<2; j++) {
	    if (j == 0) {
		/* public functions */
		w = sr->rvars1;
		names = pubnames;
		n = npub;
	    } else {
		/* private functions */
		w = sr->rvars2;
		names = privnames;
		n = npriv;
	    }

	    if (n == 0) continue;

	    model = gtk_tree_view_get_model(GTK_TREE_VIEW(w));
	    store = GTK_LIST_STORE(model);
	    gtk_tree_model_get_iter_first(model, &iter);

	    for (i=0; i<n; i++) {
		idx = user_function_index_by_name(names[i], pkg);
		if (idx < 0) continue;
		gtk_list_store_append(store, &iter);
		gtk_list_store_set(store, &iter,
				   COL_ID, idx, COL_LAG, 0,
				   COL_NAME, names[i], -1);
		if (lstore != NULL) {
		    gtk_list_store_append(lstore, &liter);
		    gtk_list_store_set(lstore, &liter,
				       COL_ID, idx, COL_LAG, 0,
				       COL_NAME, names[i], -1);
		}
	    }
	}
	
	selector_set_blocking(sr, 1);
    }    
}

/* accessor functions */

int selector_code (const selector *sr)
{
    return (sr->ci == PANEL_WLS || sr->ci == PANEL_B)? 
	PANEL : sr->ci;
}

const char *selector_list (const selector *sr)
{
    const char *ret = NULL;

    if (sr->cmdlist != NULL && *sr->cmdlist != '\0') {
	ret = sr->cmdlist;
    }

    return ret;
}

int selector_list_hasconst (const selector *sr)
{
    int hc = sr->cmdlist != NULL && 
	strstr(sr->cmdlist, " 0") != NULL;

    return hc;
}

gpointer selector_get_data (const selector *sr)
{
    return sr->data;
}

gretlopt selector_get_opts (const selector *sr)
{
    gretlopt ret;

    if (sr->ci == PANEL_B) {
	ret = sr->opts | OPT_B;
    } else {
	ret = sr->opts;
    }

    if (sr->ci == VECM || sr->ci == COINT2) {
	/* record Johansen case */
	if (ret & OPT_A) {
	    /* --crt */
	    jcase = J_REST_TREND;
	} else if (ret & OPT_N) {
	    /* --nc */
	    jcase = J_NO_CONST;
	} else if (ret & OPT_R) {
	    /* --rc */
	    jcase = J_REST_CONST;
	} else if (ret & OPT_T) {
	    /* --ct */
	    jcase = J_UNREST_TREND;
	} else {
	    /* unrestricted constant */
	    jcase = J_UNREST_CONST;
	}
    }

    return ret;
}

const char *selector_entry_text (const selector *sr)
{
    g_return_val_if_fail(GTK_IS_ENTRY(sr->extra[0]), NULL);

    return gtk_entry_get_text(GTK_ENTRY(sr->extra[0]));
}

int selector_error (const selector *sr)
{
    return sr->error;
}

void maybe_clear_selector (const int *dlist)
{
    int i, j;

    if (xlist != NULL) {
	for (i=1; i<=xlist[0]; i++) {
	    for (j=1; j<=dlist[0]; j++) {
		if (xlist[i] >= dlist[j]) {
		    clear_selector();
		    return;
		}
	    }
	}
    }
}

void selector_cleanup (void)
{
    clear_selector();

    if (open_selector != NULL) {
	gtk_widget_destroy(open_selector->dlg);
    }
}

/* ------------- lag selection apparatus -------------- */

#define NOT_LAG 66666

typedef struct var_lag_info_ var_lag_info;

struct var_lag_info_ {
    int v;              /* variable ID number or VDEFLT for default */
    int pos;            /* starting position in overall array of lag setters */
    int nvl;            /* number of siblings in array */
    int lmin;           /* minimum lag */
    int lmax;           /* maximum lag */
    char context;       /* LAG_X, LAG_W, ... */
    char *lspec;        /* string specification of particular lags */
    GtkWidget *spin1;   /* spinner for minimum lag */
    GtkWidget *spin2;   /* spinner for maximum lag */
    GtkWidget *entry;   /* text entry for specific lags */
    GtkWidget *toggle;  /* button to switch between spinners and entry */
    var_lag_info *vlp;  /* parent array */
};

#define depvar_row(c) (c == LAG_Y_X || c == LAG_Y_W)

/* handle action in the "specific lags" entry box */

static void lag_entry_callback (GtkWidget *w, gpointer p)
{
    const gchar *s = gtk_entry_get_text(GTK_ENTRY(w));
    var_lag_info *vlinfo = (var_lag_info *) p;

    free(vlinfo->lspec);
    vlinfo->lspec = g_strdup(s);

    if (vlinfo->v == VDEFLT) {
	/* set the default for this context */
	var_lag_info *vlset = vlinfo->vlp;
	int i;

	for (i=vlinfo->pos+1; i<vlinfo->nvl; i++) {
	    if (vlset[i].context == vlinfo->context && vlset[i].entry != NULL) {
		gtk_entry_set_text(GTK_ENTRY(vlset[i].entry), s);
	    }
	}
    }
}

/* retrieve the min or max lag from a spinner and process
   the result */

static void lag_set_callback (GtkWidget *w, gpointer p)
{
    var_lag_info *vlinfo;
    int lag, *plag;

    vlinfo = (var_lag_info *) g_object_get_data(G_OBJECT(w), "vlinfo");

    plag = (w == vlinfo->spin1)? &vlinfo->lmin : &vlinfo->lmax;
    lag = *plag = spinner_get_int(w);

    /* force consistency if need be */

    if (w == vlinfo->spin1 && vlinfo->spin2 != NULL) {
	if (spinner_get_int(vlinfo->spin2) < lag) {
	    gtk_spin_button_set_value(GTK_SPIN_BUTTON(vlinfo->spin2), lag);
	}
    } else if (w == vlinfo->spin2 && vlinfo->spin1 != NULL) {
	if (spinner_get_int(vlinfo->spin1) > lag) {
	    gtk_spin_button_set_value(GTK_SPIN_BUTTON(vlinfo->spin1), lag);
	}
    }	

    if (vlinfo->v == VDEFLT) {
	/* set the default value for this context */
	var_lag_info *vlset = vlinfo->vlp;
	GtkWidget *s;
	int i;

	for (i=vlinfo->pos; i<vlinfo->nvl; i++) {
	    if (vlset[i].context == vlinfo->context) {
		s = (w == vlinfo->spin1)? vlset[i].spin1 : vlset[i].spin2;
		if (s != NULL) {
		    gtk_spin_button_set_value(GTK_SPIN_BUTTON(s), lag);
		}
	    }
	}
    }
}

/* switch from using min/max spinners to using the "specific lags"
   text entry, or vice versa */

static void activate_specific_lags (GtkWidget *w, var_lag_info *vlinfo)
{
    gboolean active = button_is_active(w);

    if (active) {
	gtk_widget_set_sensitive(vlinfo->entry, TRUE);
	gtk_widget_set_sensitive(vlinfo->spin1, FALSE);
	gtk_widget_set_sensitive(vlinfo->spin2, FALSE);
    } else {
	gtk_widget_set_sensitive(vlinfo->entry, FALSE);
	gtk_widget_set_sensitive(vlinfo->spin1, TRUE);
	gtk_widget_set_sensitive(vlinfo->spin2, TRUE);
    } 

    if (vlinfo->v == VDEFLT) {
	/* set the default per context */
	var_lag_info *vlset = vlinfo->vlp;
	int i;

	for (i=vlinfo->pos+1; i<vlinfo->nvl; i++) {
	    if (vlset[i].context == vlinfo->context) {
		if (vlset[i].toggle != NULL) {
		    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vlset[i].toggle), 
						 active);
		}
	    }
	}
    } 
}

static void lag_toggle_register (GtkWidget *w, var_lag_info *vlinfo)
{
    var_lag_info *vlset = vlinfo->vlp;
    gboolean active;
    int i;

    if (vlset == NULL) return;

    for (i=0; i<vlinfo->nvl; i++) {
	if (depvar_row(vlset[i].context)) {
	    if (!gtk_widget_is_sensitive(vlset[i].spin1) &&
		!gtk_widget_is_sensitive(vlset[i].entry)) {
		/* dependent var lags are disabled */
		vlset[i].lmin = vlset[i].lmax = NOT_LAG; /* ?? */
		free(vlset[i].lspec);
		vlset[i].lspec = NULL;
		continue;
	    }
	}
	active = button_is_active(vlset[i].toggle);
	if (active) {
	    vlset[i].lmin = vlset[i].lmax = NOT_LAG; 
	} else {
	    free(vlset[i].lspec);
	    vlset[i].lspec = NULL;
	}
    }
}

static void activate_y_lags (GtkWidget *w, var_lag_info *vlinfo)
{
    gboolean active = button_is_active(w);

    gtk_widget_set_sensitive(vlinfo->spin1, active);
    gtk_widget_set_sensitive(vlinfo->spin2, active);
    gtk_widget_set_sensitive(vlinfo->toggle, active);

    if (active) {
	vlinfo->lmin = spinner_get_int(vlinfo->spin1);
	vlinfo->lmax = spinner_get_int(vlinfo->spin2);
    }

    if (vlinfo->context == LAG_Y_X) {
	y_x_lags_enabled = active;
    } else {
	y_w_lags_enabled = active;
    }
}

static void lagsel_spin_connect (GtkWidget *button)
{
    g_signal_connect(G_OBJECT(button), "value-changed", 
		     G_CALLBACK(lag_set_callback), NULL);
    gtk_entry_set_activates_default(GTK_ENTRY(button), TRUE);
}

static void resensitize_selector (GtkWidget *w, gpointer p)
{
    if (open_selector != NULL) {
	gtk_widget_set_sensitive(open_selector->dlg, TRUE);
    }
}

/* The actual lag selection dialog: we provide spinners for a lag
   range and also a free-form entry field for non-contiguous lags.  In
   some circumstances we allow specification of lags for the dependent
   variable as well as the independent vars.
*/

static int 
lags_dialog (const int *list, var_lag_info *vlinfo, selector *sr) 
{
    GtkWidget *lbl, *dialog, *myvbox;
    GtkWidget *tbl, *tmp, *vbox, *hbox;
    GtkWidget *y_check = NULL;
    gint tbl_len;
    double lmax;
    int VAR_special, insts;
    int i, j;
    int ret = GRETL_CANCEL;

    dialog = gretl_dialog_new(_("lag order"), sr->dlg, 
			      GRETL_DLG_BLOCK | GRETL_DLG_RESIZE);

    g_signal_connect(G_OBJECT(dialog), "destroy",
		     G_CALLBACK(resensitize_selector), NULL);

    myvbox = gtk_vbox_new(FALSE, 5);

    VAR_special = (vlinfo[0].v == VDEFLT && vlinfo[0].context == LAG_Y_V);
    insts = in_gretl_list(list, LAG_W);

    lmax = (dataset->t2 - dataset->t1) / list[0];

    if (VAR_special) {
	/* allow for gaps in VAR lag order */
	lbl = gtk_label_new(_("Lags of endogenous variables"));
	gtk_box_pack_start(GTK_BOX(myvbox), lbl, FALSE, FALSE, 0);
    } 

    /* allow for additional label row */
    tbl_len = list[0] + 1;

    tbl = gtk_table_new(tbl_len, 7, FALSE);
    gtk_table_set_row_spacings(GTK_TABLE(tbl), 5);
    gtk_table_set_col_spacings(GTK_TABLE(tbl), 5);
    gtk_box_pack_start(GTK_BOX(myvbox), tbl, FALSE, FALSE, 0);

    /* row 0 of table: heading(s) */

    if (insts) {
	/* there's an instruments section to follow */
	lbl = gtk_label_new(_("Regressors"));
	gtk_table_attach(GTK_TABLE(tbl), lbl, 0, 7, 0, 1, 0, 0, 0, 5);
    } else {
	if (!VAR_special) {
	    lbl = gtk_label_new(_("Variable"));
	    gtk_table_attach_defaults(GTK_TABLE(tbl), lbl, 0, 1, 0, 1);
	    lbl = gtk_label_new(_("lags"));
	    gtk_table_attach_defaults(GTK_TABLE(tbl), lbl, 1, 4, 0, 1);
	    lbl = gtk_label_new("  ");
	    gtk_table_attach_defaults(GTK_TABLE(tbl), lbl, 4, 5, 0, 1);
	}

	lbl = gtk_label_new(_("or"));
	gtk_table_attach_defaults(GTK_TABLE(tbl), lbl, 5, 6, 0, 1);
	lbl = gtk_label_new(_("specific lags"));
	gtk_table_attach_defaults(GTK_TABLE(tbl), lbl, 6, 7, 0, 1);  
    }

    j = 0;
    for (i=1; i<=list[0]; i++) {
	var_lag_info *vlj;
	int li = list[i];
	int lmin = 0;

	if (list_lag_special(li)) {
	    if (li == LAG_W) {
		tmp = gtk_label_new(_("Instruments"));
		gtk_table_attach_defaults(GTK_TABLE(tbl), tmp, 0, 7, i, i+1);
	    } else if (depvar_row(li)) { 
		y_check = gtk_check_button_new_with_label(_("Lags of dependent variable"));
		gtk_table_attach_defaults(GTK_TABLE(tbl), y_check, 0, 7, i, i+1);
	    } else {
		tmp = gtk_hseparator_new();
		gtk_table_attach_defaults(GTK_TABLE(tbl), tmp, 0, 7, i, i+1);
	    }
	    continue;
	}

	if (!VAR_special) {
	    if (li == VDEFLT) {
		lbl = gtk_label_new(_("default"));
	    } else {
		lbl = gtk_label_new(dataset->varname[li]);
	    }
	    gtk_table_attach_defaults(GTK_TABLE(tbl), lbl, 0, 1, i, i+1);
	}

	vlj = &vlinfo[j++];

	if (depvar_row(vlj->context) || vlj->context == LAG_Y_V) {
	    lmin = 1;
	} 

	/* min. lag spinner */
	vlj->spin1 = gtk_spin_button_new_with_range(lmin, lmax, 1);
	gtk_table_attach_defaults(GTK_TABLE(tbl), vlj->spin1, 1, 2, i, i+1);
	g_object_set_data(G_OBJECT(vlj->spin1), "vlinfo", vlj);
	lagsel_spin_connect(vlj->spin1);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(vlj->spin1), vlj->lmin);

	lbl = gtk_label_new(_("to"));
	gtk_table_attach_defaults(GTK_TABLE(tbl), lbl, 2, 3, i, i+1);

	/* max. lag spinner */
	vlj->spin2 = gtk_spin_button_new_with_range(lmin, lmax, 1);
	gtk_table_attach_defaults(GTK_TABLE(tbl), vlj->spin2, 3, 4, i, i+1);
	g_object_set_data(G_OBJECT(vlj->spin2), "vlinfo", vlj);
	lagsel_spin_connect(vlj->spin2);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(vlj->spin2), vlj->lmax);

	/* spacer column */
	lbl = gtk_label_new("  ");
	gtk_table_attach_defaults(GTK_TABLE(tbl), lbl, 4, 5, i, i+1);

	/* toggle button for activating entry of specific lags */
	vlj->toggle = gtk_check_button_new();
	gtk_table_attach_defaults(GTK_TABLE(tbl), vlj->toggle, 5, 6, i, i+1);
	g_signal_connect(G_OBJECT(vlj->toggle), "toggled", 
			 G_CALLBACK(activate_specific_lags), vlj);

	/* text entry widget for specific lags */
	vlj->entry = gtk_entry_new();
	gtk_entry_set_width_chars(GTK_ENTRY(vlj->entry), 16);
	gtk_table_attach_defaults(GTK_TABLE(tbl), vlj->entry, 6, 7, i, i+1);
	g_signal_connect(G_OBJECT(vlj->entry), "changed", 
			 G_CALLBACK(lag_entry_callback), vlj);
	gtk_entry_set_activates_default(GTK_ENTRY(vlj->entry), TRUE);
	if (vlj->lspec != NULL && *vlj->lspec != '\0') {
	    /* got a saved non-contiguous lag spec string: apply it */
	    gtk_entry_set_text(GTK_ENTRY(vlj->entry), vlj->lspec);
	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vlj->toggle), 
					 TRUE);
	} else {
	    /* disable this by default */
	    gtk_widget_set_sensitive(vlj->entry, FALSE);
	}

	/* set sensitivity of dependent variable lags apparatus */
	if (y_check != NULL && depvar_row(vlj->context)) {
	    g_signal_connect(G_OBJECT(y_check), "toggled", 
			     G_CALLBACK(activate_y_lags), vlj);
	    if ((vlj->context == LAG_Y_X && y_x_lags_enabled) ||
		(vlj->context == LAG_Y_W && y_w_lags_enabled)) {
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(y_check),
					     TRUE);
	    } else {
		gtk_widget_set_sensitive(vlj->spin1, FALSE);
		gtk_widget_set_sensitive(vlj->spin2, FALSE);
		gtk_widget_set_sensitive(vlj->toggle, FALSE);
		gtk_widget_set_sensitive(vlj->entry, FALSE);
	    }
	}
    }

    hbox = gtk_hbox_new(FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), myvbox, TRUE, TRUE, 5);

    vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));

    if (list[0] > 10) {
	GtkWidget *scroller = gtk_scrolled_window_new(NULL, NULL);

	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
				       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroller), 
					      hbox);
	gtk_box_pack_start(GTK_BOX(vbox), scroller, TRUE, TRUE, 5);
	gtk_widget_set_size_request(scroller, -1, 360);
    } else {
	gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
    }
    
    hbox = gtk_dialog_get_action_area(GTK_DIALOG(dialog));
	
    /* "Cancel" button */
    tmp = cancel_delete_button(hbox, dialog);
    g_signal_connect(G_OBJECT(tmp), "clicked", 
		     G_CALLBACK(delete_widget), dialog);

    /* "OK" button */
    tmp = ok_validate_button(hbox, &ret, NULL);
    g_signal_connect(G_OBJECT(tmp), "clicked",
		     G_CALLBACK(lag_toggle_register), &vlinfo[0]);
    g_signal_connect(G_OBJECT(tmp), "clicked", 
		     G_CALLBACK(delete_widget), dialog);
    gtk_widget_grab_default(tmp);

    /* "Help" button */
    context_help_button(hbox, LAGS_DIALOG);

    gtk_widget_set_sensitive(sr->dlg, FALSE);
    gtk_widget_show_all(dialog);   

    return ret;
}

/* based on the lags dialog, revise the representation of a given
   variable within the selection boxes in the model specification
   dialog, using varlist_insert_var_full().
*/

static void 
revise_var_string (var_lag_info *vlinfo, selector *sr, int locus)
{
    GtkWidget *w;
    GtkTreeModel *mod;
    GtkTreeIter iter;
    int modv;

    w = (locus == SR_RVARS2)? sr->rvars2 : sr->rvars1;
    mod = gtk_tree_view_get_model(GTK_TREE_VIEW(w));
    gtk_tree_model_get_iter_first(mod, &iter);

    do {
	gtk_tree_model_get(mod, &iter, COL_ID, &modv, -1);
	if (modv == vlinfo->v) {
#if VLDEBUG
	    fprintf(stderr, "revise_var_string: calling varlist_insert_var_full\n");
#endif
	    varlist_insert_var_full(vlinfo->v, mod, &iter, sr, locus);
	    break;
	}
    } while (gtk_tree_model_iter_next(mod, &iter));
}

/* go through a list box and find the vars that are candidates for
   lag selection: this excludes constant and trend, and also
   list entries that are themselves just dummy placeholders
   for already selected lags of a variable */

static int get_laggable_vars (GtkWidget *w, int context, int *list, int *i)
{
    GtkTreeModel *model;
    GtkTreeIter iter;
    gint v, lag;
    int n = 0;

    if (!GTK_IS_TREE_VIEW(w)) {
	return 0;
    }

    model = gtk_tree_view_get_model(GTK_TREE_VIEW(w));
    if (model == NULL) {
	return 0;
    }

    if (gtk_tree_model_get_iter_first(model, &iter)) {
	do {
	    int laggable = 1;

	    gtk_tree_model_get(model, &iter, COL_ID, &v, COL_LAG, &lag, -1);

	    if (v == 0 || !strcmp(dataset->varname[v], "time") ||
		!strcmp(dataset->varname[v], "timesq")) {
		laggable = 0;
	    } else if (is_lag_dummy(v, lag, context)) {
		laggable = 0;
	    }

	    if (laggable) { 
		if (list != NULL) {
		    list[*i] = v;
		    *i += 1;
		}
		n++;
	    }
	} while (gtk_tree_model_iter_next(model, &iter));
    }

    return n;
}

/* Construct a special list which encodes the information needed
   for building the lag selection dialog
*/

static int *sr_get_stoch_list (selector *sr, int *pnset, int *pcontext)
{
    GtkWidget *listw[2] = { NULL, NULL };
    gint ynum = 0;
    int nv[2] = {0};
    int nset, nsep;
    int context;
    int i, j;
    int *slist = NULL;

    if (sr->ci != ARMA && sr->ci != VAR &&
	sr->ci != VECM && sr->ci != VLAGSEL &&
	sr->ci != DPANEL) { 
	ynum = selector_get_depvar_number(sr);
    }

    listw[0] = (select_lags_primary(sr->ci))? sr->rvars1 : sr->rvars2;

    if (USE_ZLIST(sr->ci)) {
	listw[1] = sr->rvars2;
    } 

    if (listw[0] != NULL) {
	nv[0] = get_laggable_vars(listw[0], LAG_X, NULL, NULL);
	if (nv[0] == 0) {
	    listw[0] = NULL;
	}
    }

    if (listw[1] != NULL) {
	nv[1] = get_laggable_vars(listw[1], LAG_W, NULL, NULL);
	if (nv[1] == 0) {
	    listw[1] = NULL;
	}	
    }    

    nset = nsep = 0;

#if LDEBUG
    fprintf(stderr, "sr_get_stoch_list: ynum = %d, nv[0] = %d, nv[1] = %d\n",
	    ynum, nv[0], nv[1]);
#endif

    if (ynum < 0 && nv[0] == 0 && nv[1] == 0) {
	/* no vars to deal with */
	errbox("Please add some variables to the model first");
	return NULL;
    }

    /* first pass: figure out how many elements the list should have */

    if (nv[0] > 0) {
	*pcontext = LAG_X;
	nset += nv[0] + 1; /* the Xs plus their defaults */
	if (ynum > 0) {
	    nsep++; /* depvar heading */
	    nset++; /* depvar row */
	}
    }

    if (nv[1] > 0) {
	if (nv[0] > 0) {
	    nsep++; /* separator from Xs */
	} else {
	    *pcontext = LAG_W;
	}
	nsep++;            /* "instruments" heading */
	nset += nv[1] + 1; /* instruments plus their defaults */
	if (ynum > 0) {
	    nsep++; /* depvar heading */
	    nset++; /* depvar row */
	}	    
    }

    if (nv[0] == 0 && nv[1] == 0) {
	/* only the dependent variable is present */
	*pcontext = LAG_Y_X;
	nsep++; /* depvar heading */
	nset++; /* depvar row */
	if (USE_ZLIST(sr->ci)) {
	    nsep++; /* list separator for insts */
	    nsep++; /* "instruments" heading */
	    nsep++; /* depvar heading */
	    nset++; /* depvar row */
	}
    }

    /* allocate the list */

    slist = gretl_list_new(nset + nsep);
    if (slist == NULL) {
	return NULL;
    }

    /* second pass: actually build the list, inserting special
       separators if needed
    */

    i = 1;
    for (j=0; j<2; j++) {
	if (listw[j] == NULL) {
	    continue;
	}
	context = (j == 1)? LAG_W : LAG_X;
	if (j == 1) {
	    if (nv[0] > 0) {
		slist[i++] = LISTSEP;
	    }
	    slist[i++] = LAG_W;
	}
	slist[i++] = VDEFLT;
	get_laggable_vars(listw[j], context, slist, &i);
	if (ynum > 0) {
	    slist[i++] = (j > 0)? LAG_Y_W : LAG_Y_X;
	    slist[i++] = ynum;
	}
    }

    /* special case where the dependent variable is the only laggable
       variable selected so far */

    if (nv[0] == 0 && nv[1] == 0) {
	slist[i++] = LAG_Y_X;
	slist[i++] = ynum;
	if (USE_ZLIST(sr->ci)) {
	    slist[i++] = LISTSEP;
	    slist[i++] = LAG_W;
	    slist[i++] = LAG_Y_W;
	    slist[i++] = ynum;
	}
    }

    *pnset = nset;

    return slist;
}

static void maybe_revise_var_string (var_lag_info *vlinfo, selector *sr)
{
    int locus = 0;

#if VLDEBUG
    fprintf(stderr, "maybe_revise_var_string: v = %d, context = %d\n",
	    vlinfo->v, vlinfo->context);
#endif

    if (vlinfo->context == LAG_X) {
	locus = (sr->ci == VAR || 
		 sr->ci == VECM ||
		 sr->ci == VLAGSEL)? SR_RVARS2 : SR_RVARS1;
    } else if (vlinfo->context == LAG_W) {
	locus = SR_RVARS2;
    } else if (depvar_row(vlinfo->context)) { 
#if VLDEBUG
	fprintf(stderr, " calling maybe_revise_depvar_lags, context = %d\n",
		vlinfo->context);
#endif
	maybe_revise_depvar_lags(sr, vlinfo->v, vlinfo->context);
    }

    if (locus > 0) {
#if VLDEBUG
	fprintf(stderr, " calling revise_var_string, locus = %d\n", locus);
#endif
	revise_var_string(vlinfo, sr, locus);
    } 
}

/* return 1 if lags changed, else 0 */

static int set_lags_for_var (var_lag_info *vlinfo, int yxlags, int ywlags)
{
    int *llist = NULL;
    int changed = 0;
    int err = 0;

#if LDEBUG
    fprintf(stderr, "set_lags_for_var: v=%d, lmin=%d, lmax=%d, lspec=%p\n",
	    vlinfo->v, vlinfo->lmin, vlinfo->lmax, (void *) vlinfo->lspec);
#endif

    if (vlinfo->lspec != NULL && *vlinfo->lspec != 0) {
	gretl_charsub(vlinfo->lspec, ',', ' ');
	llist = gretl_list_from_string(vlinfo->lspec, &err);
	if (!err) {
	    err = set_lag_prefs_from_list(vlinfo->v, llist, vlinfo->context,
					  &changed);
	    if (err) {
		free(llist);
	    }
	}
    } else if (vlinfo->lmin != NOT_LAG && vlinfo->lmax != NOT_LAG) {
	set_lag_prefs_from_minmax(vlinfo->v, vlinfo->lmin, vlinfo->lmax, 
				  vlinfo->context, &changed);
    } else {
	set_null_lagpref(vlinfo->v, vlinfo->context, &changed);
    }

    if (vlinfo->context == LAG_Y_X && yxlags != y_x_lags_enabled) {
	changed = 1;
    } else if (vlinfo->context == LAG_Y_W && ywlags != y_w_lags_enabled) {
	changed = 1;
    }

    return changed;
}

/* Keep the global "lag order" spinner in sync with lag specifications
   entered via the the (more complex) lags dialog.  We set the value
   shown in the spinner to the maximum lag; in addition we desensitize
   the global spinner if the user has specified gaps in the lag
   structure.
*/

static void sync_lag_order_spinner (var_lag_info *vlinfo,
				    selector *sr)
{
    const int *list = NULL;
    int lmin = 0, lmax = 0;

    get_lag_preference(vlinfo->v, &lmin, &lmax, &list,
		       vlinfo->context, sr);

    if (list != NULL) {
	lmin = list[1];
	lmax = list[list[0]];
    }

    if (lmax != 0 && lmax != spinner_get_int(sr->extra[0])) {
	/* update max lag as shown by spinner */
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(sr->extra[0]),
				  lmax);
    }

    if (lmin > 1 || list != NULL) {
	/* not 1-based consecutive lags: so disable spinner */
	gtk_widget_set_sensitive(sr->extra[0], FALSE);
    } else {
	gtk_widget_set_sensitive(sr->extra[0], TRUE);
    }
}

#if LDEBUG > 2
static void print_vlinfo (var_lag_info *vlj)
{
    fprintf(stderr, "\nCreated vlinfostruct:\n");
    fprintf(stderr, " v = %d\n", vlj->v);
    fprintf(stderr, " pos = %d\n", vlj->pos);
    fprintf(stderr, " nvl = %d\n", vlj->nvl);
    fprintf(stderr, " context = %d\n", (int) vlj->context);
    fprintf(stderr, " vlp = %p\n", (void *) vlj->vlp);
    
    fprintf(stderr, " lmin = %d\n", vlj->lmin);
    fprintf(stderr, " lmax = %d\n", vlj->lmax);

    if (vlj->lspec != NULL) {
	fprintf(stderr, " lspec = '%s'\n", vlj->lspec);
    } else {
	fprintf(stderr, " lspec = NULL\n");
    }
}
#endif

/* Respond to the user clicking the "Lags..." button: here we build
   the information needed to make the lag selection dialog, run
   the dialog, then implement any changes made via the GUI.
 */

static gboolean lags_dialog_driver (GtkWidget *w, selector *sr)
{
    var_lag_info *vlinfo;
    int yxlags = y_x_lags_enabled;
    int ywlags = y_w_lags_enabled;
    int context = 0;
    int i, j, resp, nvl;
    int *list;

    if (w == sr->extra[EXTRA_LAGS]) {
	/* VAR: use a dummy entry for all endogenous vars */
	list = gretl_list_new(1);
	if (list == NULL) {
	    return FALSE;
	}
	list[1] = VDEFLT;
	nvl = 1;
	context = LAG_Y_V;
    } else {
	/* get the list of stochastic (laggable) variables */
	list = sr_get_stoch_list(sr, &nvl, &context);
	if (list == NULL) {
	    return FALSE;
	}
    }

#if LDEBUG
    printlist(list, "stochastic vars list");
    fprintf(stderr, "number of setters = %d\n", nvl);
#endif

    /* allocate array of vlinfo, one per laggable var */
    vlinfo = mymalloc(nvl * sizeof *vlinfo);
    if (vlinfo == NULL) {
	free(list);
	return FALSE;
    }

    j = 0;
    for (i=1; i<=list[0]; i++) {
	const int *laglist = NULL;
	var_lag_info *vlj;
	int vi = list[i];

	if (list_lag_special(vi)) {
	    /* LISTSEP is used to switch "lag context" */
	    context = (vi == LISTSEP)? 0 : vi;
#if LDEBUG
	    fprintf(stderr, "list[%d] = %d: set context = %d\n",
		    i, vi, context);
#endif
	    continue;
	}

	vlj = &vlinfo[j++];

	/* pick up any saved preferences (including saved defaults) */
	get_lag_preference(vi, &vlj->lmin, &vlj->lmax, 
			   &laglist, context, sr);

	if (laglist != NULL) {
	    /* we got a list of specific lags for variable vi: convert
	       to a string for putting into a text entry box */
	    vlj->lspec = gretl_list_to_string(laglist);
	    vlj->lmin = laglist[1];
	    vlj->lmax = laglist[laglist[0]];
	} else {
	    vlj->lspec = NULL;
	}

	vlj->v = vi;
	vlj->pos = j;
	vlj->nvl = nvl;
	vlj->context = context;
	vlj->spin1 = NULL;
	vlj->spin2 = NULL;
	vlj->entry = NULL;
	vlj->toggle = NULL;
	vlj->vlp = vlinfo;
#if LDEBUG > 2
	print_vlinfo(vlj);
#endif
    }

    /* show the user the lags dialog box */
    resp = lags_dialog(list, vlinfo, sr);

    if (canceled(resp)) {
	y_x_lags_enabled = yxlags;
	y_w_lags_enabled = ywlags;
    } else {
	/* register any changes made by the user */
	for (j=0; j<nvl; j++) {
	    int changed = set_lags_for_var(&vlinfo[j], yxlags, ywlags);

	    if (vlinfo[j].v != VDEFLT && changed) {
		maybe_revise_var_string(&vlinfo[j], sr);
	    }
	}
	if (context == LAG_Y_V) {
	    sync_lag_order_spinner(&vlinfo[0], sr);
	}
    }

    /* clean up any allocated lag spec strings */
    for (j=0; j<nvl; j++) {
	if (vlinfo[j].lspec != NULL) {
	    free(vlinfo[j].lspec);
	}
    }

    free(list);
    free(vlinfo);

    return FALSE;
}
