/*
 *  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/>.
 *
 */

#include "gretl.h"
#include "var.h"
#include "johansen.h"
#include "varprint.h"
#include "forecast.h"
#include "objstack.h"
#include "gretl_xml.h"
#include "gretl_func.h"
#include "system.h"
#include "matrix_extra.h"
#include "bootstrap.h"
#include "gretl_foreign.h"
#include "gretl_typemap.h"
#include "uservar.h"
#include "gretl_string_table.h"
#include "gretl_panel.h"
#include "csvdata.h"
#include "kalman.h"
#include "libset.h"

#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#include "model_table.h"
#include "series_view.h"
#include "session.h"
#include "textbuf.h"
#include "textutil.h"
#include "cmdstack.h"
#include "filelists.h"
#include "menustate.h"
#include "dlgutils.h"
#include "ssheet.h"
#include "datafiles.h"
#include "gpt_control.h"
#include "fileselect.h"
#include "toolbar.h"
#include "winstack.h"
#include "fnsave.h"
#include "datawiz.h"
#include "selector.h"
#include "guiprint.h"
#include "fncall.h"
#include "tabwin.h"
#include "join-gui.h"
#include "gretl_ipc.h"

#ifdef G_OS_WIN32
# include <windows.h>
# include "gretlwin32.h"
#endif

static void set_up_model_view_menu (windata_t *vwin);
static void add_system_menu_items (windata_t *vwin, int role);
static void add_x12_output_menu_item (windata_t *vwin);
static gint check_model_menu (GtkWidget *w, GdkEventButton *eb,
			      gpointer data);
static gint check_VAR_menu (GtkWidget *w, GdkEventButton *eb,
			    gpointer data);
static void model_copy_callback (GtkAction *action, gpointer p);
static int set_sample_from_model (void *ptr, int role);
static gboolean maybe_set_sample_from_model (windata_t *vwin);

static void close_model (GtkAction *action, gpointer data)
{
    windata_t *vwin = (windata_t *) data;

    gretl_viewer_destroy(vwin);
}

static int arma_by_x12a (const MODEL *pmod)
{
    int ret = 0;

    if (pmod->ci == ARMA) {
	int acode = gretl_model_get_int(pmod, "arma_flags");

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

    return ret;
}

int latex_is_ok (void)
{
    static int latex_ok = -1;

    if (latex_ok == -1) {
	latex_ok = check_for_program(latex);
    }

    return latex_ok;
}

static void model_output_save (GtkAction *action, gpointer p)
{
    copy_format_dialog((windata_t *) p, W_SAVE);
}

static gretlopt tex_eqn_opt;

static void set_tex_eqn_opt (GtkRadioAction *action)
{
    int v = gtk_radio_action_get_current_value(action);

    tex_eqn_opt = (v)? OPT_T : OPT_NONE;
}

gretlopt get_tex_eqn_opt (void)
{
    return tex_eqn_opt;
}

static int model_get_t1_t2 (void *ptr, int role, int *t1, int *t2)
{
    int err = 0;

    if (role == VIEW_MODEL) {
	MODEL *pmod = ptr;

	*t1 = pmod->smpl.t1;
	*t2 = pmod->smpl.t2;
    } else if (role == VAR || role == VECM) {
	GRETL_VAR *var = ptr;

	err = gretl_var_get_sample(var, t1, t2);
    } else if (role == SYSTEM) {
	equation_system *sys = ptr;

	err = gretl_system_get_sample(sys, t1, t2);
    }

    return err;
}

/* Called from menu in model window, but not necessarily
   a single-equation model */

static void model_revise_callback (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    int ok = 1, t1 = 0, t2 = 0;
    int err = 0;

    if (vwin->role == VIEW_MODEL) {
	MODEL *pmod = vwin->data;

	err = model_sample_problem(pmod, dataset);
    }

    if (!err) {
	err = model_get_t1_t2(vwin->data, vwin->role, &t1, &t2);
    }

    if (!err && (t1 != dataset->t1 || t2 != dataset->t2)) {
	ok = maybe_set_sample_from_model(vwin);
    }

    if (ok) {
	selector_from_model(vwin);
    }
}

gchar *gretl_window_title (const char *s)
{
    if (s != NULL) {
	return g_strdup_printf("gretl: %s", s);
    } else {
	return g_strdup("gretl: untitled");
    }
}

static void text_eqn_callback (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    MODEL *pmod = vwin->data;
    PRN *prn;
    int err;

    if (bufopen(&prn)) {
	return;
    }

    err = text_print_equation(pmod, dataset, OPT_NONE, prn);

    if (err) {
	gui_errmsg(err);
    } else {
	gchar *title = gretl_window_title(_("equation"));

	view_buffer_with_parent(vwin, prn, 78, 200, title, PRINT, NULL);
	g_free(title);
    }
}

static GtkActionEntry model_items[] = {
    { "File", NULL, N_("_File"), NULL, NULL, NULL },
    { "SaveAs", GTK_STOCK_SAVE_AS, N_("_Save as..."), NULL, NULL, G_CALLBACK(model_output_save) },
    { "SaveAsIcon", NULL, N_("Save to session as _icon"), NULL, NULL, G_CALLBACK(model_add_as_icon) },
    { "SaveAndClose", NULL, N_("Save as icon and cl_ose"), NULL, NULL, G_CALLBACK(model_add_as_icon) },
    { "Print", GTK_STOCK_PRINT, N_("_Print..."), NULL, NULL, G_CALLBACK(window_print) },
    { "TextEqn", NULL, N_("View as equation"), NULL, NULL, G_CALLBACK(text_eqn_callback) },
    { "Close", GTK_STOCK_CLOSE, N_("_Close"), NULL, NULL, G_CALLBACK(close_model) },
    { "Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
    { "Copy", GTK_STOCK_COPY, N_("_Copy"), NULL, NULL, G_CALLBACK(model_copy_callback) },
    { "Revise", GTK_STOCK_EDIT, N_("_Modify model..."), NULL, NULL,
      G_CALLBACK(model_revise_callback) },
#if 0
    { "Restore", NULL, N_("_Restore model sample"), NULL, NULL, G_CALLBACK(model_sample_callback) },
#endif
    { "Tests", NULL, N_("_Tests"), NULL, NULL, NULL },
    { "Save", NULL, N_("_Save"), NULL, NULL, NULL },
    { "Graphs", NULL, N_("_Graphs"), NULL, NULL, NULL },
    { "ResidPlot", NULL, N_("_Residual plot"), NULL, NULL, NULL },
    { "FittedActualPlot", NULL, N_("_Fitted, actual plot"), NULL, NULL, NULL },
    { "Analysis", NULL, N_("_Analysis"), NULL, NULL, NULL },
    { "DisplayAFR", NULL, N_("_Display actual, fitted, residual"), NULL, NULL,
      G_CALLBACK(display_fit_resid) },
    { "Forecasts", NULL, N_("_Forecasts..."), NULL, NULL, G_CALLBACK(gui_do_forecast) },
    { "ConfIntervals", NULL, N_("_Confidence intervals for coefficients"), NULL, NULL,
      G_CALLBACK(do_coeff_intervals) },
    { "ConfEllipse", NULL, N_("Confidence _ellipse..."), NULL, NULL, G_CALLBACK(selector_callback) },
    { "Covariance", NULL, N_("Coefficient covariance _matrix"), NULL, NULL, G_CALLBACK(do_outcovmx) },
    { "Collinearity", NULL, N_("_Collinearity"), NULL, NULL, G_CALLBACK(do_collin) },
    { "Leverage", NULL, N_("_Influential observations"), NULL, NULL, G_CALLBACK(do_leverage) },
    { "ANOVA", NULL, N_("_ANOVA"), NULL, NULL, G_CALLBACK(do_anova) },
    { "Bootstrap", NULL, N_("_Bootstrap..."), NULL, NULL, G_CALLBACK(do_bootstrap) }
};

static GtkActionEntry model_test_items[] = {
    { "omit", NULL, N_("_Omit variables"), NULL, NULL, G_CALLBACK(selector_callback) },
    { "add", NULL, N_("_Add variables"), NULL, NULL, G_CALLBACK(selector_callback) },
    { "coeffsum", NULL, N_("_Sum of coefficients"), NULL, NULL, G_CALLBACK(selector_callback) },
    { "restrict", NULL, N_("_Linear restrictions"), NULL, NULL, G_CALLBACK(gretl_callback) },
    { "modtest:s", NULL, N_("Non-linearity (s_quares)"), NULL, NULL, G_CALLBACK(do_modtest) },
    { "modtest:l", NULL, N_("Non-linearity (_logs)"), NULL, NULL, G_CALLBACK(do_modtest) },
    { "reset", NULL, N_("_Ramsey's RESET"), NULL, NULL, G_CALLBACK(do_reset) },
    { "Hsk", NULL, N_("_Heteroskedasticity"), NULL, NULL, NULL },
    { "modtest:n", NULL, N_("_Normality of residual"), NULL, NULL, G_CALLBACK(do_resid_freq) },
    { "chow", NULL, N_("_Chow test"), NULL, NULL, G_CALLBACK(do_chow_cusum) },
    { "modtest:a", NULL, N_("_Autocorrelation"), NULL, NULL, G_CALLBACK(do_autocorr) },
    { "dwpval", NULL, N_("_Durbin-Watson p-value"), NULL, NULL, G_CALLBACK(do_dwpval) },
    { "modtest:h", NULL, N_("A_RCH"), NULL, NULL, G_CALLBACK(do_arch) },
    { "bds", NULL, N_("Non-linearity (_BDS)"), NULL, NULL, G_CALLBACK(do_resid_freq) },
    { "qlrtest", NULL, N_("_QLR test"), NULL, NULL, G_CALLBACK(do_chow_cusum) },
    { "cusum", NULL, N_("_CUSUM test"), NULL, NULL, G_CALLBACK(do_chow_cusum) },
    { "cusum:r", NULL, N_("CUSUM_SQ test"), NULL, NULL, G_CALLBACK(do_chow_cusum) },
    { "modtest:c", NULL, N_("_Common factor"), NULL, NULL, G_CALLBACK(do_modtest) },
    { "modtest:d", NULL, N_("_Cross-sectional dependence"), NULL, NULL, G_CALLBACK(do_modtest) },
    { "panspec", NULL, N_("_Panel specification"), NULL, NULL, G_CALLBACK(do_panel_tests) }
};

static GtkActionEntry base_hsk_items[] = {
    { "White", NULL, N_("White's test"), NULL, NULL, G_CALLBACK(do_modtest) },
    { "WhiteSquares", NULL, N_("White's test (squares only)"), NULL, NULL, G_CALLBACK(do_modtest) },
    { "BreuschPagan", NULL, "Breusch-Pagan", NULL, NULL, G_CALLBACK(do_modtest) },
    { "Koenker", NULL, "Koenker", NULL, NULL, G_CALLBACK(do_modtest) }
};

static GtkActionEntry panel_hsk_items[] = {
    { "White", NULL, N_("White's test"), NULL, NULL, G_CALLBACK(do_modtest) },
    { "Groupwise", NULL, N_("_groupwise"), NULL, NULL, G_CALLBACK(do_modtest) }
};

static GtkActionEntry ivreg_hsk_items[] = {
    { "White", NULL, N_("Pesaran-Taylor test"), NULL, NULL, G_CALLBACK(do_modtest) }
};

const gchar *model_tex_ui =
    "<ui>"
    "  <menubar>"
    "    <menu action='LaTeX'>"
    "      <menu action='TeXView'>"
    "        <menuitem action='TabView'/>"
    "        <menuitem action='EqnView'/>"
    "      </menu>"
    "      <menu action='TeXCopy'>"
    "        <menuitem action='TabCopy'/>"
    "        <menuitem action='EqnCopy'/>"
    "      </menu>"
    "      <menu action='TeXSave'>"
    "        <menuitem action='TabSave'/>"
    "        <menuitem action='EqnSave'/>"
    "      </menu>"
    "      <menu action='EqnOpts'>"
    "        <menuitem action='TeXstderrs'/>"
    "        <menuitem action='TeXtratios'/>"
    "      </menu>"
    "      <menuitem action='TabOpts'/>"
    "    </menu>"
    "  </menubar>"
    "</ui>";

const gchar *missing_tex_ui =
    "<ui>"
    "  <menubar>"
    "    <menu action='LaTeX'>"
    "      <menuitem action='notex'/>"
    "    </menu>"
    "  </menubar>"
    "</ui>";

static GtkActionEntry model_tex_items[] = {
    { "LaTeX",   NULL, N_("_LaTeX"), NULL, NULL, NULL },
    { "TeXView", NULL, N_("_View"), NULL, NULL, NULL },
    { "TabView", NULL, N_("_Tabular"), NULL, NULL, G_CALLBACK(model_tex_view) },
    { "EqnView", NULL, N_("_Equation"), NULL, NULL, G_CALLBACK(model_tex_view) },
    { "TeXCopy", NULL, N_("_Copy"), NULL, NULL, NULL },
    { "TabCopy", NULL, N_("_Tabular"), NULL, NULL, G_CALLBACK(model_tex_copy) },
    { "EqnCopy", NULL, N_("_Equation"), NULL, NULL, G_CALLBACK(model_tex_copy) },
    { "TeXSave", NULL, N_("_Save"), NULL, NULL, NULL },
    { "TabSave", NULL, N_("_Tabular"), NULL, NULL, G_CALLBACK(model_tex_save) },
    { "EqnSave", NULL, N_("_Equation"), NULL, NULL, G_CALLBACK(model_tex_save) },
    { "EqnOpts", NULL, N_("_Equation options"), NULL, NULL, NULL },
    { "TabOpts", NULL, N_("_Tabular options..."), NULL, NULL, G_CALLBACK(tex_format_dialog) }
};

static GtkRadioActionEntry tex_eqn_items[] = {
    { "TeXstderrs", NULL, N_("Show _standard errors"), NULL, NULL, 0 },
    { "TeXtratios", NULL, N_("Show _t-ratios"), NULL, NULL, 1 },
};

static GtkActionEntry missing_tex_items[] = {
    { "LaTeX", NULL, N_("_LaTeX"), NULL, NULL, NULL },
    { "notex", NULL, "No TeX", NULL, NULL, G_CALLBACK(dummy_call) }
};

static GtkActionEntry system_items[] = {
    { "File", NULL, N_("_File"), NULL, NULL, NULL },
    { "SaveAs", GTK_STOCK_SAVE_AS, N_("_Save as..."), NULL, NULL, G_CALLBACK(model_output_save) },
    { "SaveAsIcon", NULL, N_("Save to session as _icon"), NULL, NULL, G_CALLBACK(model_add_as_icon) },
    { "SaveAndClose", NULL, N_("Save as icon and cl_ose"), NULL, NULL, G_CALLBACK(model_add_as_icon) },
    { "Print", GTK_STOCK_PRINT, N_("_Print..."), NULL, NULL, G_CALLBACK(window_print) },
    { "Close", GTK_STOCK_CLOSE, N_("_Close"), NULL, NULL, G_CALLBACK(close_model) },
    { "Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
    { "Copy", GTK_STOCK_COPY, N_("_Copy"), NULL, NULL, G_CALLBACK(model_copy_callback) },
    { "Revise", GTK_STOCK_EDIT, N_("_Revise specification..."), NULL, NULL,
      G_CALLBACK(model_revise_callback) },
    { "Save", NULL, N_("_Save"), NULL, NULL, NULL },
    { "Tests", NULL, N_("_Tests"), NULL, NULL, NULL },
    { "Graphs", NULL, N_("_Graphs"), NULL, NULL, NULL },
    { "Analysis", NULL, N_("_Analysis"), NULL, NULL, NULL },
    { "Forecasts", NULL, N_("_Forecasts"), NULL, NULL, NULL },
};

static gint n_system_items = G_N_ELEMENTS(system_items);

static GtkActionEntry sys_tex_items[] = {
    { "LaTeX",   NULL, N_("_LaTeX"), NULL, NULL, NULL },
    { "TeXView", NULL, N_("_View"),  NULL, NULL, G_CALLBACK(model_tex_view) },
    { "TeXCopy", NULL, N_("_Copy"),  NULL, NULL, G_CALLBACK(model_tex_copy) },
    { "TeXSave", NULL, N_("_Save"),  NULL, NULL, G_CALLBACK(model_tex_save) },
};

static const gchar *sys_ui =
    "<ui>"
    "  <menubar>"
    "    <menu action='File'>"
    "      <menuitem action='SaveAs'/>"
    "      <menuitem action='SaveAsIcon'/>"
    "      <menuitem action='SaveAndClose'/>"
    "      <menuitem action='Print'/>"
    "      <menuitem action='Close'/>"
    "    </menu>"
    "    <menu action='Edit'>"
    "      <menuitem action='Copy'/>"
    "      <menuitem action='Revise'/>"
    "    </menu>"
    "    <menu action='Tests'/>"
    "    <menu action='Save'/>"
    "    <menu action='Graphs'/>"
    "    <menu action='Analysis'>"
    "      <menu action='Forecasts'/>"
    "    </menu>"
    "  </menubar>"
    "</ui>";

static void model_copy_callback (GtkAction *action, gpointer p)
{
    copy_format_dialog((windata_t *) p, W_COPY);
}

int copyfile (const char *src, const char *dest)
{
    FILE *srcfd, *destfd;
    char buf[GRETL_BUFSIZE];
    size_t n;

    if (!strcmp(src, dest)) {
	return 0;
    }

    if ((srcfd = gretl_fopen(src, "rb")) == NULL) {
	file_read_errbox(src);
	return E_FOPEN;
    }

    if ((destfd = gretl_fopen(dest, "wb")) == NULL) {
	file_write_errbox(dest);
	fclose(srcfd);
	return E_FOPEN;
    }

    while ((n = fread(buf, 1, sizeof buf, srcfd)) > 0) {
	fwrite(buf, 1, n, destfd);
    }

    fclose(srcfd);
    fclose(destfd);

    return 0;
}

FILE *gretl_tempfile_open (char *fname)
{
    FILE *fp;

    strcat(fname, ".XXXXXX");
    fp = gretl_mktemp(fname, "w+");

    if (fp == NULL) {
	errbox(_("Couldn't open temp file"));
    }

    return fp;
}

static void delete_file (GtkWidget *widget, char *fname)
{
    gretl_remove(fname);
    g_free(fname);
}

void delete_widget (GtkWidget *widget, gpointer data)
{
    gtk_widget_destroy(GTK_WIDGET(data));
}

void set_wait_cursor (GdkWindow **pcwin)
{
    GdkDisplay *disp = gdk_display_get_default();
    GdkWindow *w;

    if (*pcwin == NULL) {
	gint x, y;

	*pcwin = w = gdk_display_get_window_at_pointer(disp, &x, &y);
    } else {
	w = *pcwin;
    }

    if (w != NULL) {
	GdkCursor *cursor = gdk_cursor_new(GDK_WATCH);

	if (cursor != NULL) {
	    gdk_window_set_cursor(w, cursor);
	    gdk_display_sync(disp);
	    gdk_cursor_unref(cursor);
	}
    }
}

void unset_wait_cursor (GdkWindow *cwin)
{
    if (cwin != NULL) {
	gdk_window_set_cursor(cwin, NULL);
    }
}

static gboolean not_space (gunichar c, gpointer p)
{
    return !g_unichar_isspace(c);
}

int vwin_subselection_present (windata_t *vwin)
{
    GtkTextIter selstart, selend;
    GtkTextBuffer *buf;
    int ret = 0;

    buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));

    if (gtk_text_buffer_get_selection_bounds(buf, &selstart, &selend)) {
	GtkTextIter start, end;

	gtk_text_buffer_get_bounds(buf, &start, &end);
	if (gtk_text_iter_equal(&selstart, &start) &&
	    gtk_text_iter_equal(&selend, &end)) {
	    ret = 0;
	} else {
	    gtk_text_iter_forward_find_char(&start, not_space,
					    NULL, &selstart);
	    gtk_text_iter_backward_find_char(&end, not_space,
					     NULL, &selend);
	    if (!gtk_text_iter_equal(&selstart, &start) ||
		!gtk_text_iter_equal(&selend, &end)) {
		ret = 1;
	    }
	}
    }

    return ret;
}

int vwin_is_editing (windata_t *vwin)
{
    if (vwin != NULL && vwin->text != NULL) {
	return gtk_text_view_get_editable(GTK_TEXT_VIEW(vwin->text));
    } else {
	return 0;
    }
}

gboolean vwin_copy_callback (GtkWidget *w, windata_t *vwin)
{
    if (vwin_subselection_present(vwin)) {
	window_copy(vwin, GRETL_FORMAT_SELECTION);
    } else if (vwin_is_editing(vwin)) {
	window_copy(vwin, GRETL_FORMAT_TXT);
    } else {
	copy_format_dialog(vwin, W_COPY);
    }

    return TRUE;
}

static int numeric_keyval (guint key)
{
    if (key >= GDK_1 && key <= GDK_9) {
	return key - GDK_0;
    } else if (key >= GDK_KP_1 && key <= GDK_KP_9) {
	return key - GDK_KP_0;
    } else {
	return 0;
    }
}

#define nav_key(k) (k==GDK_Up || k==GDK_Down || \
		    k==GDK_Page_Up || k==GDK_Page_Down || \
		    k==GDK_End || k==GDK_Begin || k==GDK_Home)

static gint jump_to_finder (guint keyval, windata_t *vwin)
{
    if (!nav_key(keyval)) {
	gchar *letter = gdk_keyval_name(keyval);

	if (letter != NULL) {
	    /* snap to search box */
	    gtk_widget_grab_focus(vwin->finder);
	    gtk_entry_set_text(GTK_ENTRY(vwin->finder), letter);
	    gtk_editable_set_position(GTK_EDITABLE(vwin->finder), -1);
	    return TRUE; /* handled */
	}
    }

    return FALSE;
}

static void vwin_select_all (windata_t *vwin)
{
    if (vwin != NULL && vwin->text != NULL) {
	GtkTextBuffer *tbuf;

	tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));

	if (tbuf != NULL) {
	    GtkTextIter start, end;

	    gtk_text_buffer_get_start_iter(tbuf, &start);
	    gtk_text_buffer_get_end_iter(tbuf, &end);
	    gtk_text_buffer_select_range(tbuf, &start, &end);
	}
    }
}

/* GDK_KEY_A 0x041 .. GDK_KEY_Z 0x05a
   GDK_KEY_a 0x061 .. GDK_KEY_z 0x07a
*/

struct greek_map {
    guint key; /* Latin letter key */
    guint grk; /* "corresponding" Greek letter */
};

static struct greek_map greek_keys[] = {
    { GDK_A, 0x91 }, /* alpha */
    { GDK_B, 0x92 }, /* beta */
    { GDK_C, 0xa7 }, /* chi */
    { GDK_D, 0x94 }, /* delta */
    { GDK_E, 0x95 }, /* epsilon */
    { GDK_F, 0xa6 }, /* phi */
    { GDK_G, 0x93 }, /* gamma */
    { GDK_H, 0x97 }, /* eta */
    { GDK_I, 0x99 }, /* iota */
    { GDK_J, 0xa8 }, /* psi */
    { GDK_K, 0x9a }, /* kappa */
    { GDK_L, 0x9b }, /* lambda */
    { GDK_M, 0x9c }, /* mu */
    { GDK_N, 0x9d }, /* nu */
    { GDK_O, 0x9f }, /* omicron */
    { GDK_P, 0xa0 }, /* pi */
    { GDK_Q, 0x98 }, /* theta */
    { GDK_R, 0xa1 }, /* rho */
    { GDK_S, 0xa3 }, /* sigma */
    { GDK_T, 0xa4 }, /* tau */
    { GDK_U, 0xa5 }, /* upsilon */
    { GDK_V, 0x9d }, /* nu (again) */
    { GDK_W, 0xa9 }, /* omega */
    { GDK_X, 0x9e }, /* xi */
    { GDK_Y, 0xa5 }, /* upsilon (again) */
    { GDK_Z, 0x96 }  /* zeta */
};

/* Note: exclude Greek capital letters that are indistinguishable from
   Latin caps.
*/

#define ok_greek_cap(k) (k == GDK_D || k == GDK_F || k == GDK_G || \
			 k == GDK_J || k == GDK_L || k == GDK_P || \
			 k == GDK_Q || k == GDK_S || k == GDK_U || \
			 k == GDK_W || k == GDK_X || k == GDK_Y)

static int maybe_insert_greek (guint key, windata_t *vwin)
{
    guint lc = 0, ukey = 0;

    if (key >= GDK_a && key <= GDK_z) {
	ukey = gdk_keyval_to_upper(key);
	lc = 1;
    } else if (key >= GDK_A && key <= GDK_Z) {
	if (ok_greek_cap(key)) {
	    ukey = key;
	} else {
	    /* insert the look-alike? */
	    textview_insert_text(vwin->text, gdk_keyval_name(key));
	    return 1;
	}
    }

    if (ukey > 0) {
	int i, n = G_N_ELEMENTS(greek_keys);
	unsigned char g, ins[3] = {0};

	for (i=0; i<n; i++) {
	    if (ukey == greek_keys[i].key) {
		g = greek_keys[i].grk;
		if (lc) {
		    ins[0] = g > 0x9f ? 0xCF : 0xCE;
		    ins[1] = g > 0x9f ? g - 0x20 : g + 0x20;
		} else {
		    ins[0] = 0xCE;
		    ins[1] = g;
		}
		textview_insert_text(vwin->text, (char *) ins);
		return 1;
	    }
	}
    }

    return 0;
}

/* Signal attached to editor/viewer windows. Note that @w is
   generally the top-level GtkWidget vwin->main; exceptions
   are (a) tabbed windows, where @w is the embedding window,
   and (b) help windows, where @w is the text area.
*/

gint catch_viewer_key (GtkWidget *w, GdkEventKey *event,
		       windata_t *vwin)
{
    int Ctrl = (event->state & GDK_CONTROL_MASK);
    int Alt = (event->state & GDK_MOD1_MASK);
    guint upkey = event->keyval;
    int editing = vwin_is_editing(vwin);
    int console = vwin->role == CONSOLE;

    if (vwin_is_busy(vwin)) {
	return TRUE;
    }

#if 0
    fprintf(stderr, "HERE catch_viewer_key\n"
	    " editing=%d, console=%d, Ctrl=%d, Alt=%d, key=%s\n",
	    editing, console, Ctrl, Alt, gdk_keyval_name(upkey));
#endif

    if (editing && Alt && !Ctrl) {
	/* "Alt" specials for editor */
	if (maybe_insert_greek(upkey, vwin)) {
	    return TRUE;
	} else if (upkey == GDK_minus) {
	    textview_insert_text(vwin->text, "~");
	    return TRUE;
	}
    }

    if (is_control_key(event->keyval)) {
	return FALSE;
    }

    if (!gdk_keyval_is_upper(event->keyval)) {
	upkey = gdk_keyval_to_upper(event->keyval);
    }

#ifdef OS_OSX
    if (!Ctrl && cmd_key(event)) {
	/* treat Command as Ctrl */
	Ctrl = 1;
    }
#endif

    if (Ctrl && !Alt) {
	if (upkey == GDK_F) {
	    text_find(NULL, vwin);
	    return TRUE;
	} else if (upkey == GDK_G) {
	    text_find_again(NULL, vwin);
	    return TRUE;
	} else if (upkey == GDK_C) {
	    /* Ctrl-C: copy */
	    return vwin_copy_callback(NULL, vwin);
	} else if (editing && !console) {
	    /* note that the standard Ctrl-key sequences for editing
	       are handled by GTK, so we only need to put our own
	       "specials" here
	    */
	    if (upkey == GDK_H) {
		text_replace(NULL, vwin);
		return TRUE;
	    } else if (upkey == GDK_S) {
		/* Ctrl-S: save */
		vwin_save_callback(NULL, vwin);
		return TRUE;
	    } else if (upkey == GDK_Q || upkey == GDK_W) {
		if (!window_is_tab(vwin)) {
		    /* Ctrl-Q or Ctrl-W, quit: but not for tabbed windows */
		    if (vwin_content_changed(vwin)) {
			/* conditional: we have unsaved changes */
			if (query_save_text(NULL, NULL, vwin) == FALSE) {
			    gtk_widget_destroy(w);
			}
		    } else {
			/* unconditional */
			gtk_widget_destroy(w);
		    }
		    return TRUE;
		}
	    } else if (upkey == GDK_T && window_is_tab(vwin)) {
		/* Ctrl-T: open new tab */
		do_new_script(vwin->role, NULL);
		return TRUE;
	    }
	}
	if (window_is_tab(vwin)) {
	    /* note: still conditional on Ctrl */
	    if (upkey == GDK_greater || upkey == GDK_less ||
		upkey == GDK_Page_Up || upkey == GDK_Page_Down) {
		tabwin_navigate(vwin, upkey);
		return TRUE;
	    }
	} else if (upkey == GDK_Q || upkey == GDK_W) {
	    gtk_widget_destroy(vwin->main);
	    return TRUE;
	}
    } else if (Alt && !console) {
	if (upkey == GDK_C && vwin->role == SCRIPT_OUT) {
	    cascade_session_windows();
	    return TRUE;
	} else if (window_is_tab(vwin)) {
	    int k = numeric_keyval(upkey);

	    if (k > 0) {
		tabwin_navigate(vwin, k);
		return TRUE;
	    }
	}
    }

    if (editing || (vwin->finder != NULL && gtk_widget_has_focus(vwin->finder))) {
	/* we set up "special" responses to some plain keystrokes
	   below: this won't do if we're in editing/typing mode
	*/
	return FALSE;
    }

    if (!event->state && vwin->finder != NULL && GTK_IS_ENTRY(vwin->finder)) {
	if (jump_to_finder(event->keyval, vwin)) {
	    /* FIXME is this really wanted? */
	    return TRUE;
	}
    }

    if (!Alt) {
	if (upkey == GDK_A && Ctrl) {
	    vwin_select_all(vwin);
	    return TRUE;
	} else if (upkey == GDK_Q || (upkey == GDK_W && Ctrl)) {
	    if (w == vwin->main) {
		gtk_widget_destroy(w);
	    }
	} else if (upkey == GDK_S && data_status && vwin->role == VIEW_MODEL) {
	    model_add_as_icon(NULL, vwin);
	}
    }

    return FALSE;
}

void nomem (void)
{
    errbox(_("Out of memory!"));
}

void *mymalloc (size_t size)
{
    void *mem;

    if ((mem = malloc(size)) == NULL) {
	nomem();
    }

    return mem;
}

void *myrealloc (void *ptr, size_t size)
{
    void *mem;

    if ((mem = realloc(ptr, size)) == NULL) {
	nomem();
    }

    return mem;
}

void mark_dataset_as_modified (void)
{
    data_status |= MODIFIED_DATA;
    set_sample_label(dataset);

    if (session_file_is_open()) {
	mark_session_changed();
    }
}

const char *path_last_slash_const (const char *path)
{
    return (const char *) strrslash(path);
}

char *gretl_basename (char *dest, const char *src, int addscore)
{
    const char *p = path_last_slash_const(src);
    const char *s = (p != NULL)? p + 1 : src;

    if (dest == NULL) {
	/* allocate return value */
	size_t len = strlen(s) + 1;

	dest = calloc(len, 1);
	addscore = 0;
    }

    if (dest != NULL) {
	strcpy(dest, s);
    }

    if (addscore) {
	/* double any underscores in @dest */
	char mod[MAXSTR];
	int n = strlen(dest);
	int i, j = 0;

	for (i=0; i<=n; i++) {
	    if (dest[i] == '_') {
		mod[j++] = '_';
	    }
	    mod[j++] = dest[i];
	}
	strcpy(dest, mod);
    }

    return dest;
}

static void gui_record_data_opening (const char *fname,
				     const int *list)
{
    const char *recname = (fname != NULL)? fname : datafile;

    if (data_status & BOOK_DATA) {
	/* don't print the (platform-dependent) full
	   path to a datafile packaged with gretl
	*/
	char basename[MAXSTR];

	gretl_basename(basename, recname, 0);
	lib_command_sprintf("open %s", basename);
    } else if (strchr(recname, ' ') != NULL) {
	lib_command_sprintf("open \"%s\"", recname);
    } else {
	lib_command_sprintf("open %s", recname);
    }

    if (list != NULL && list[0] == 3) {
	/* record spreadsheet parameters */
	char parm[32];

	if (list[1] > 1) {
	    sprintf(parm, " --sheet=%d", list[1]);
	    lib_command_strcat(parm);
	}
	if (list[2] > 0) {
	    sprintf(parm, " --coloffset=%d", list[2]);
	    lib_command_strcat(parm);
	}
	if (list[3] > 0) {
	    sprintf(parm, " --rowoffset=%d", list[3]);
	    lib_command_strcat(parm);
	}
    }

    record_command_verbatim();

    if (*datafile != '\0') {
	mkfilelist(FILE_LIST_DATA, datafile, 0);
    }
}

#define file_opened(f) (f & (DATAFILE_OPENED | OPENED_VIA_CLI | OPENED_VIA_SESSION))

static void real_register_data (int flags, const char *user_fname,
				const int *list)
{
    /* basic accounting */
    data_status |= HAVE_DATA;
    orig_vars = dataset->v;

    /* set appropriate data_status bits */
    if (file_opened(flags)) {
	if (!(data_status & IMPORT_DATA)) {
	    /* we opened a native data file */
	    if (has_system_prefix(datafile, DATA_SEARCH)) {
		data_status |= BOOK_DATA;
		data_status &= ~USER_DATA;
	    } else {
		data_status &= ~BOOK_DATA;
		data_status |= USER_DATA;
	    }
	    if (is_gzipped(datafile)) {
		data_status |= GZIPPED_DATA;
	    } else {
		data_status &= ~GZIPPED_DATA;
	    }
	    if (flags & OPENED_VIA_SESSION) {
		data_status |= SESSION_DATA;
	    } else {
		data_status &= ~SESSION_DATA;
	    }
	}
    } else {
	/* we modified the current dataset somehow */
	data_status |= GUI_DATA;
	mark_dataset_as_modified();
    }

    /* sync main window with datafile */
    if (mdata != NULL) {
	populate_varlist();
	set_sample_label(dataset);
	dataset_menubar_state(TRUE);
	session_menu_state(TRUE);
    }

    /* Record the opening of the data file in the GUI recent files
       list and command log; note that we don't do this if the file
       was opened via script or console, or if it was opened as a
       side effect of re-opening a saved session. And we can't do
       it if the data file was opened via the initial command line,
       and the gretl GUI is not yet built.
    */
    if (mdata != NULL && (flags & DATAFILE_OPENED)) {
	gui_record_data_opening(user_fname, list);
    }

    if (mdata != NULL) {
	if (!(flags & (OPENED_VIA_CLI | FOCUS_CONSOLE))) {
	    /* focus the data window */
	    gtk_widget_grab_focus(mdata->listbox);
	}
	/* invalidate "remove extra obs" menu item */
	drop_obs_state(FALSE);
    }
}

void register_data (int flags)
{
    real_register_data(flags, NULL, NULL);
}

void register_startup_data (const char *fname)
{
    real_register_data(DATAFILE_OPENED, fname, NULL);
}

static void maybe_offer_daily_options (void)
{
    gretlopt purge_opt = 0;
    PRN *prn = NULL;

    bufopen(&prn);

    if (prn != NULL) {
	int chk = analyse_daily_import(dataset, prn);
	const char *buf = NULL;
	int resp;

	if (chk > 0) {
	    buf = gretl_print_get_buffer(prn);
	}

	if (chk == 1) {
	    const char *opts[] = {
		N_("Leave the dataset as it is"),
		N_("Delete the weekend rows")
	    };

	    resp = radio_dialog("gretl", _(buf), opts, 2,
				1, DAILY_PURGE, NULL);
	    if (resp == 1) {
		purge_opt = OPT_W;
	    }
	} else if (chk == 2) {
	    const char *opts[] = {
		N_("Leave the dataset as it is"),
		N_("Delete the weekend rows"),
		N_("Delete all blank rows")
	    };

	    resp = radio_dialog("gretl", _(buf), opts, 3,
				2, DAILY_PURGE, NULL);
	    if (resp == 1) {
		purge_opt = OPT_W;
	    } else if (resp == 2) {
		purge_opt = OPT_A;
	    }
	} else if (chk == 3) {
	    const char *opts[] = {
		N_("Leave the dataset as it is"),
		N_("Delete all blank rows")
	    };

	    resp = radio_dialog("gretl", _(buf), opts, 2,
				1, DAILY_PURGE, NULL);
	    if (resp == 1) {
		purge_opt = OPT_A;
	    }
	}

	gretl_print_destroy(prn);
    }

    if (purge_opt) {
	purge_opt |= OPT_T;
	bool_subsample(NULL, purge_opt, NULL);
    }
}

static void finalize_data_open (const char *fname, int ftype,
				int import, int append,
				const int *list)
{
    if (import) {
	if (ftype == GRETL_CSV || ftype == GRETL_DTA ||
	    ftype == GRETL_SAV || ftype == GRETL_SAS ||
	    ftype == GRETL_XLSX || ftype == GRETL_ODS ||
	    ftype == GRETL_GNUMERIC || ftype == GRETL_XLS) {
	    maybe_display_string_table();
	}
	data_status |= IMPORT_DATA;
    }

    if (append) {
	register_data(DATA_APPENDED);
	return;
    }

    if (strstr(fname, CLIPTEMP_TXT) || strstr(fname, CLIPTEMP_GDT)) {
	real_register_data(DATA_PASTED, NULL, list);
    } else {
	if (fname != datafile) {
	    strcpy(datafile, fname);
	}
	real_register_data(DATAFILE_OPENED, NULL, list);
    }

    if (import && !dataset_is_time_series(dataset) &&
	!dataset_is_panel(dataset) && ftype != GRETL_MAP &&
	mdata != NULL) {
	int resp;

	resp = yes_no_dialog(_("gretl: open data"),
			     _("The imported data have been interpreted as undated\n"
			       "(cross-sectional).  Do you want to give the data a\n"
			       "time-series or panel interpretation?"),
			     NULL);
	if (resp == GRETL_YES) {
	    data_structure_dialog();
	}
    } else if (import && dated_daily_data(dataset) &&
	       !dataset->markers) {
	maybe_offer_daily_options();
    }
}

static int datafile_missing (const char *fname)
{
    FILE *fp = gretl_fopen(fname, "r");
    int err = 0;

    if (fp == NULL) {
	delete_from_filelist(FILE_LIST_DATA, fname);
	file_read_errbox(fname);
	err = E_FOPEN;
    } else {
	fclose(fp);
    }

    return err;
}

/* below: get data of a sort that requires an import plugin */

int get_imported_data (char *fname, int ftype, int append)
{
    PRN *prn = NULL;
    int list[4] = {3, 0, 0, 0};
    int *plist = NULL;
    int (*ss_importer) (const char *, int *, char *, DATASET *,
			gretlopt, PRN *, GtkWidget *);
    int (*misc_importer) (const char *, DATASET *,
			  gretlopt, PRN *);
    int err = 0;

    if (datafile_missing(fname)) {
	return E_FOPEN;
    }

    import_na_init();
    ss_importer = NULL;
    misc_importer = NULL;

    if (ftype == GRETL_XLS) {
	ss_importer = gui_get_plugin_function("xls_get_data");
	plist = list;
    } else if (ftype == GRETL_XLSX) {
	ss_importer = gui_get_plugin_function("xlsx_get_data");
	plist = list;
    } else if (ftype == GRETL_GNUMERIC) {
	ss_importer = gui_get_plugin_function("gnumeric_get_data");
	plist = list;
    } else if (ftype == GRETL_ODS) {
	ss_importer = gui_get_plugin_function("ods_get_data");
	plist = list;
    } else if (ftype == GRETL_DTA) {
	misc_importer = gui_get_plugin_function("dta_get_data");
    } else if (ftype == GRETL_SAV) {
	misc_importer = gui_get_plugin_function("sav_get_data");
    } else if (ftype == GRETL_SAS) {
	misc_importer = gui_get_plugin_function("xport_get_data");
     } else if (ftype == GRETL_JMULTI) {
	misc_importer = gui_get_plugin_function("jmulti_get_data");
    } else if (ftype == GRETL_WF1) {
	misc_importer = gui_get_plugin_function("wf1_get_data");
    } else if (ftype == GRETL_MAP) {
	misc_importer = gui_get_plugin_function("map_get_data");
    } else {
	errbox(_("Unrecognized data type"));
	err = 1;
	goto bailout;
    }

    if (!err && ss_importer == NULL && misc_importer == NULL) {
	/* failed to open plugin */
        err = 1;
	goto bailout;
    }

    if (bufopen(&prn)) {
        err = 1;
	goto bailout;
    }

    /* call the actual importer function */
    if (SPREADSHEET_IMPORT(ftype)) {
	GtkWidget *parent = (mdata == NULL)? NULL : mdata->main;

	err = (*ss_importer)(fname, plist, NULL, dataset,
			     OPT_G, prn, parent);
    } else {
	err = (*misc_importer)(fname, dataset, OPT_G, prn);
    }

    if (err == -1) {
	fprintf(stderr, "data import canceled\n");
	err = E_CANCEL;
	goto bailout;
    } else {
	const char *buf = gretl_print_get_buffer(prn);

	if (err) {
	    if (buf != NULL && *buf != '\0') {
		errbox(buf);
	    } else {
		gui_errmsg(err);
	    }
	    if (err == E_FOPEN) {
		delete_from_filelist(FILE_LIST_DATA, fname);
	    }
	} else {
	    /* Do we want to be showing this? There's no error
	       but it might look sort of scary!
	    */
#if 0 /* masked out 2020-07-11 */
	    if (buf != NULL && *buf != '\0') {
		infobox(buf);
	    }
#endif
	    finalize_data_open(fname, ftype, 1, append, plist);
	}
    }

 bailout:

    gretl_print_destroy(prn);

    return err;
}

/* get "CSV" (or more generally, ASCII) data or GNU octave data:
   plugin is not required
*/

static int get_csv_data (char *fname, int ftype, int append)
{
    windata_t *vwin;
    PRN *prn;
    gchar *title = NULL;
    gretlopt opt = OPT_NONE;
    int err = 0;

    if (datafile_missing(fname)) {
	return E_FOPEN;
    }

    if (ftype == GRETL_CSV) {
	const char *msg =
	    N_("Usually gretl tries to interpret the first column of a CSV\n"
	       "data file as containing observation information (for example,\n"
	       "dates), if the column heading looks suitable.\n\n"
	       "Do you want to force gretl to treat the first column as just\n"
	       "an ordinary data series (regardless of the column heading)?");

	if (no_yes_dialog(NULL, _(msg)) == GRETL_YES) {
	    opt = OPT_A;
	}
    }

    if (bufopen(&prn)) {
	return 1;
    }

    if (ftype == GRETL_OCTAVE) {
	err = import_other(fname, ftype, dataset, opt, prn);
	title = g_strdup_printf(_("gretl: import %s data"), "Octave");
    } else {
	err = import_csv(fname, dataset, opt, prn);
	title = g_strdup_printf(_("gretl: import %s data"), "CSV");
    }

    /* show details regarding the import */
    vwin = view_buffer(prn, 78, 350, title, IMPORT, NULL);
    gtk_window_set_transient_for(GTK_WINDOW(vwin->main),
				 GTK_WINDOW(mdata->main));
    g_free(title);

    if (err) {
	delete_from_filelist(FILE_LIST_DATA, fname);
    } else {
	finalize_data_open(fname, ftype, 1, append, NULL);
    }

    return err;
}

static int get_native_data (char *fname, int ftype, int append,
			    windata_t *fwin)
{
    PRN *prn = NULL;
    char *buf = NULL;
    int err;

    if (bufopen(&prn)) {
	return 1;
    }

    if (ftype == GRETL_XML_DATA) {
	err = gretl_read_gdt(fname, dataset, OPT_B, prn);
    } else {
	err = gretl_get_data(fname, dataset, OPT_NONE, prn);
    }

    buf = gretl_print_steal_buffer(prn);
    gretl_print_destroy(prn);

    if (fwin != NULL) {
	/* close the files browser window that launched the query */
	gtk_widget_destroy(fwin->main);
    }

    if (err) {
	if (err == E_FOPEN) {
	    file_read_errbox(fname);
	} else if (buf != NULL && *buf != '\0') {
	    errbox(buf);
	} else {
	    gui_errmsg(err);
	}
	delete_from_filelist(FILE_LIST_DATA, fname);
    } else {
#if 0
	if (check_gretl_warning()) {
	    gui_warnmsg(0);
	}
#endif
	finalize_data_open(fname, ftype, 0, append, NULL);
	if (append) {
	    infobox(_("Data appended OK\n"));
	}
	fputs(buf, stderr);
    }

    free(buf);

    return err;
}

static int maybe_use_join (void)
{
    const char *opts[] = {
	N_("simple append"),
	N_("\"join\" (advanced)")
    };

    return radio_dialog(_("gretl: append data"), NULL,
		        opts, 2, 0, 0, NULL);
}

/* The gretl.c variable @tryfile will contain the name
   of the data file to be opened.
*/

gboolean do_open_data (windata_t *fwin, int code)
{
    int append = (code == APPEND_DATA);
    char *fname = get_tryfile();
    char tmp[MAXLEN];
    GretlFileType ftype;
    int use_join = 0;
    int err = 0;

    if (g_path_is_absolute(fname)) {
	strcpy(tmp, fname);
	ftype = data_file_type_from_name(tmp);
    } else {
	strcpy(tmp, fname);
	ftype = detect_filetype(tmp, OPT_P);
    }

    /* destroy the current data set, etc., unless we're
       explicitly appending */
    if (!append) {
	close_session(OPT_NONE); /* FIXME opt? */
    }

    if (append && (ftype == GRETL_CSV || ftype == GRETL_XML_DATA ||
		   ftype == GRETL_BINARY_DATA)) {
	use_join = maybe_use_join();
	if (use_join < 0) {
	    /* canceled */
	    return 0;
	}
    }

    if (use_join) {
	err = gui_join_data(tmp, ftype);
    } else if (ftype == GRETL_CSV || ftype == GRETL_OCTAVE) {
	err = get_csv_data(tmp, ftype, append);
    } else if (SPREADSHEET_IMPORT(ftype) || OTHER_IMPORT(ftype)) {
	err = get_imported_data(tmp, ftype, append);
    } else {
	err = get_native_data(tmp, ftype, append, fwin);
    }

    return !err;
}

/* give user choice of not opening selected datafile, if there's
   already a datafile open */

gboolean verify_open_data (windata_t *vwin, int code)
{
    if (dataset_locked()) {
	return FALSE;
    }

    if (data_status) {
	int resp =
	    yes_no_dialog(_("gretl: open data"),
			  _("Opening a new data file will automatically\n"
			    "close the current one.  Any unsaved work\n"
			    "will be lost.  Proceed to open data file?"),
			  vwin_toplevel(vwin));

	if (resp != GRETL_YES) {
	    return FALSE;
	}
    }

    return do_open_data(vwin, code);
}

/* give user choice of not opening session file, if there's already a
   datafile open */

gboolean verify_open_session (void)
{
    char *fname = get_tryfile();

    if (!gretl_is_pkzip_file(fname)) {
	/* not a zipped session file */
	return do_open_script(EDIT_HANSL);
    }

    if (data_status) {
	int resp =
	    yes_no_dialog(_("gretl: open session"),
			  _("Opening a new session file will automatically\n"
			    "close the current session.  Any unsaved work\n"
			    "will be lost.  Proceed to open session file?"),
			  NULL);

	if (resp != GRETL_YES) {
	    return FALSE;
	}
    }

    return do_open_session();
}

void mark_vwin_content_changed (windata_t *vwin)
{
    if (vwin->active_var == 0) {
	GtkWidget *w = g_object_get_data(G_OBJECT(vwin->mbar), "save_button");

	if (w != NULL) {
	    gtk_widget_set_sensitive(w, TRUE);
	}
	vwin->flags |= VWIN_CONTENT_CHANGED;
	if (window_is_tab(vwin)) {
	    tabwin_tab_set_status(vwin);
	}
    }
}

void mark_vwin_content_saved (windata_t *vwin)
{
    GtkWidget *w = g_object_get_data(G_OBJECT(vwin->mbar), "save_button");

    if (w != NULL) {
	gtk_widget_set_sensitive(w, FALSE);
    }

    vwin->flags &= ~VWIN_CONTENT_CHANGED;
    if (window_is_tab(vwin)) {
	tabwin_tab_set_status(vwin);
    }

    w = g_object_get_data(G_OBJECT(vwin->mbar), "save_as_button");
    if (w != NULL) {
	gtk_widget_set_sensitive(w, TRUE);
    }
}

/* save content function for an editor window that is hooked
   up to a given text buffer rather than in the business of
   saving to file
*/

static void buf_edit_save (GtkWidget *w, windata_t *vwin)
{
    char **pbuf = (char **) vwin->data;
    gchar *text;

    text = textview_get_text(vwin->text);

    if (text == NULL || *text == '\0') {
	errbox(_("Buffer is empty"));
	g_free(text);
	return;
    }

    /* swap the edited text into the buffer */
    free(*pbuf);
    *pbuf = text;

    if (vwin->role == EDIT_HEADER) {
	mark_vwin_content_saved(vwin);
	mark_dataset_as_modified();
    } else if (vwin->role == EDIT_NOTES) {
	mark_vwin_content_saved(vwin);
	mark_session_changed();
    }
}

static void file_edit_save (GtkWidget *w, windata_t *vwin)
{
    if (vwin->role == EDIT_PKG_SAMPLE) {
	/* function package editor, sample script window */
	update_sample_script(vwin);
    } else if (vwin->role == EDIT_PKG_CODE) {
	/* function package editor, function code window */
	update_func_code(vwin);
    } else if (vwin->role == EDIT_PKG_HELP ||
	       vwin->role == EDIT_PKG_GHLP) {
	/* function package editor, help text window */
	update_gfn_help_text(vwin);
    } else if (*vwin->fname == '\0' || strstr(vwin->fname, "script_tmp")) {
	/* no real filename is available yet */
	if (vwin->role == EDIT_HANSL) {
	    file_selector(SAVE_SCRIPT, FSEL_DATA_VWIN, vwin);
	} else if (vwin->role == EDIT_GP) {
	    file_selector(SAVE_GP_CMDS, FSEL_DATA_VWIN, vwin);
	} else if (vwin->role == EDIT_R) {
	    file_selector(SAVE_R_CMDS, FSEL_DATA_VWIN, vwin);
	} else if (vwin->role == EDIT_OX) {
	    file_selector(SAVE_OX_CMDS, FSEL_DATA_VWIN, vwin);
	} else if (vwin->role == EDIT_OCTAVE) {
	    file_selector(SAVE_OCTAVE_CMDS, FSEL_DATA_VWIN, vwin);
	} else if (vwin->role == EDIT_PYTHON) {
	    file_selector(SAVE_PYTHON_CMDS, FSEL_DATA_VWIN, vwin);
	} else if (vwin->role == EDIT_JULIA) {
	    file_selector(SAVE_JULIA_CODE, FSEL_DATA_VWIN, vwin);
	} else if (vwin->role == EDIT_STATA) {
	    file_selector(SAVE_STATA_CMDS, FSEL_DATA_VWIN, vwin);
	} else if (vwin->role == EDIT_DYNARE) {
	    file_selector(SAVE_DYNARE_CODE, FSEL_DATA_VWIN, vwin);
	} else if (vwin->role == EDIT_LPSOLVE) {
	    file_selector(SAVE_LPSOLVE_CODE, FSEL_DATA_VWIN, vwin);
	} else if (vwin->role == CONSOLE) {
	    file_selector(SAVE_CONSOLE, FSEL_DATA_VWIN, vwin);
	}
    } else if ((vwin->flags & VWIN_SESSION_GRAPH) &&
	       vwin->role == EDIT_GP) {
	/* "auto-save" of session graph file */
	gchar *text = textview_get_text(vwin->text);

	dump_plot_buffer(text, vwin->fname, 0, NULL);
	g_free(text);
	mark_vwin_content_saved(vwin);
	mark_session_changed();
    } else {
	FILE *fp = gretl_fopen(vwin->fname, "wb");

	if (fp == NULL) {
	    file_write_errbox(vwin->fname);
	} else {
	    gchar *text = textview_get_text(vwin->text);

	    system_print_buf(text, fp);
	    fclose(fp);
	    g_free(text);
	    mark_vwin_content_saved(vwin);
	}
    }
}

void vwin_save_callback (GtkWidget *w, windata_t *vwin)
{
    if (vwin_editing_buffer(vwin->role)) {
	buf_edit_save(w, vwin);
    } else {
	file_edit_save(w, vwin);
    }
}

/* Hook up child and parent viewers: this is used to help organize the
   window list menu (with, e.g., model-related output windows being
   marked as children of the model window itself).  It's also used for
   some more specialized cases, such as marking a script output window
   as child of the originating script window.
*/

void vwin_add_child (windata_t *parent, windata_t *child)
{
    int n = parent->n_gretl_children;
    int i, done = 0, err = 0;

    for (i=0; i<n; i++) {
	if (parent->gretl_children[i] == NULL) {
	    /* reuse a vacant slot */
	    parent->gretl_children[i] = child;
	    done = 1;
	    break;
	}
    }

    if (!done) {
	windata_t **children;

	children = myrealloc(parent->gretl_children, (n + 1) * sizeof *children);

	if (children != NULL) {
	    parent->gretl_children = children;
	    parent->gretl_children[n] = child;
	    parent->n_gretl_children += 1;
	} else {
	    err = 1;
	}
    }

    if (!err) {
	child->gretl_parent = parent;
    }
}

static void vwin_nullify_child (windata_t *parent, windata_t *child)
{
    int i;

    for (i=0; i<parent->n_gretl_children; i++) {
	if (child == parent->gretl_children[i]) {
	    parent->gretl_children[i] = NULL;
	}
    }
}

windata_t *vwin_first_child (windata_t *vwin)
{
    int i;

    for (i=0; i<vwin->n_gretl_children; i++) {
	if (vwin->gretl_children[i] != NULL) {
	    return vwin->gretl_children[i];
	}
    }

    return NULL;
}

void free_windata (GtkWidget *w, gpointer data)
{
    windata_t *vwin = (windata_t *) data;

#if 0
    fprintf(stderr, "free_windata: vwin %p, gtk_window %p, role %d\n",
	    data, (void *) vwin->main, vwin->role);
#endif

    if (vwin != NULL) {
	/* notify parent, if any, that child is gone */
	if (vwin->gretl_parent != NULL) {
	    vwin_nullify_child(vwin->gretl_parent, vwin);
	}

	/* notify children, if any, that parent is gone */
	if (vwin->n_gretl_children > 0) {
	    int i;

	    for (i=0; i<vwin->n_gretl_children; i++) {
		if (vwin->gretl_children[i] != NULL) {
		    vwin->gretl_children[i]->gretl_parent = NULL;
		}
	    }
	    free(vwin->gretl_children);
	}

	/* menu stuff */
	if (vwin->popup != NULL) {
	    gtk_widget_destroy(vwin->popup);
	}
	if (vwin->ui != NULL) {
	    g_object_unref(vwin->ui);
	}

	/* toolbar popups? */
	if (vwin->mbar != NULL) {
	    vwin_free_toolbar_popups(vwin);
	}

	/* tabbed toolbar */
	if (window_is_tab(vwin) && vwin->mbar != NULL) {
	    g_object_unref(vwin->mbar);
	}

	/* data specific to certain windows */
	if (vwin->role == SUMMARY) {
	    free_summary(vwin->data);
	} else if (vwin->role == CORR || vwin->role == PCA ||
		   vwin->role == COVAR) {
	    free_vmatrix(vwin->data);
	} else if (vwin->role == FCAST || vwin->role == AFR) {
	    free_fit_resid(vwin->data);
	} else if (vwin->role == COEFFINT) {
	    free_coeff_intervals(vwin->data);
	} else if (vwin->role == VIEW_SERIES) {
	    free_series_view(vwin->data);
	} else if (vwin->role == VIEW_MODEL) {
	    gretl_object_unref(vwin->data, GRETL_OBJ_EQN);
	} else if (vwin->role == VAR || vwin->role == VECM) {
	    gretl_object_unref(vwin->data, GRETL_OBJ_VAR);
	} else if (vwin->role == LEVERAGE) {
	    gretl_matrix_free(vwin->data);
	} else if (vwin->role == MAHAL) {
	    free_mahal_dist(vwin->data);
	} else if (vwin->role == XTAB) {
	    free_xtab(vwin->data);
	} else if (vwin->role == COINT2) {
	    gretl_VAR_free(vwin->data);
	} else if (vwin->role == SYSTEM) {
	    gretl_object_unref(vwin->data, GRETL_OBJ_SYS);
	} else if (vwin->flags & VWIN_MULTI_SERIES) {
	    free_series_view(vwin->data);
	} else if (help_role(vwin->role)) {
	    g_free(vwin->data); /* help file text */
	} else if (vwin->role == VIEW_BUNDLE ||
		   vwin->role == VIEW_DBNOMICS) {
	    if (!get_user_var_by_data(vwin->data)) {
		gretl_bundle_destroy(vwin->data);
	    }
	} else if (vwin->role == LOESS || vwin->role == NADARWAT) {
	    gretl_bundle_destroy(vwin->data);
	}

	if (window_delete_filename(vwin)) {
	    /* there's a temporary file associated */
	    gretl_remove(vwin->fname);
	}

	free(vwin);
    }
}

gboolean text_popup_handler (GtkWidget *w, GdkEventButton *event, gpointer p)
{
    if (right_click(event)) {
	windata_t *vwin = (windata_t *) p;

	if (vwin->popup != NULL) {
	    gtk_widget_destroy(vwin->popup);
	    vwin->popup = NULL;
	}

	vwin->popup = build_text_popup(vwin);

	if (vwin->popup != NULL) {
	    gtk_menu_popup(GTK_MENU(vwin->popup), NULL, NULL, NULL, NULL,
			   event->button, event->time);
	    g_signal_connect(G_OBJECT(vwin->popup), "destroy",
			     G_CALLBACK(gtk_widget_destroyed),
			     &vwin->popup);
	}

	return TRUE;
    }

    return FALSE;
}

gchar *title_from_filename (const char *fname,
			    int role,
			    gboolean prepend)
{
    gchar *base, *title = NULL;

    base = g_path_get_basename(fname);

    if (!strcmp(base, "session.inp") || !strncmp(base, "script_tmp.", 11)) {
	if (role == EDIT_GP) {
	    title = g_strdup(_("gretl: edit plot commands"));
	} else if (role == EDIT_R) {
	    title = g_strdup(_("gretl: edit R script"));
	} else if (role == EDIT_OX) {
	    title = g_strdup(_("gretl: edit Ox program"));
	} else if (role == EDIT_OCTAVE) {
	    title = g_strdup(_("gretl: edit Octave script"));
	} else if (role == EDIT_PYTHON) {
	    title = g_strdup(_("gretl: edit Python script"));
	} else if (role == EDIT_JULIA) {
	    title = g_strdup(_("gretl: edit Julia program"));
	} else if (role == EDIT_STATA) {
	    title = g_strdup(_("gretl: edit Stata program"));
	} else if (role == EDIT_DYNARE) {
	    title = g_strdup(_("gretl: edit Dynare script"));
	} else if (role == EDIT_LPSOLVE) {
	    title = g_strdup(_("gretl: edit lpsolve script"));
	} else if (role == EDIT_SPEC) {
	    title = g_strdup(_("gretl: edit package spec file"));
	} else {
	    title = g_strdup(_("gretl: untitled"));
	}
    } else if (prepend) {
	title = g_strdup_printf("gretl: %s", base);
    } else {
	title = base;
	base = NULL; /* don't free */
    }

    g_free(base);

    return title;
}

/* called when replacing a script in (non-tabbed) script
   editor, via the editor's "Open" button */

void vwin_set_filename (windata_t *vwin, const char *fname)
{
    gchar *title = title_from_filename(fname, vwin->role, TRUE);

    gtk_window_set_title(GTK_WINDOW(vwin->main), title);
    g_free(title);
    strcpy(vwin->fname, fname);
}

static gchar *script_output_title (void)
{
    int n = get_script_output_number();

    if (n > 0) {
	return g_strdup_printf(_("gretl: script output %d"), n+1);
    } else {
	return g_strdup(_("gretl: script output"));
    }
}

static gchar *title_from_data (gpointer data)
{
    gretl_bundle *b = data;
    const char *s = gretl_bundle_get_creator(b);

    if (s != NULL) {
	return g_strdup_printf("gretl: %s bundle", s);
    } else {
	return NULL;
    }
}

static gchar *make_viewer_title (int role, const char *fname,
				 gpointer data)
{
    gchar *title = NULL;

    switch (role) {
    case CMD_HELP:
	title = g_strdup(_("gretl: command reference")); break;
    case GUI_HELP:
	title = g_strdup(_("gretl: help")); break;
    case FUNC_HELP:
	title = g_strdup(_("gretl: function reference")); break;
    case CMD_HELP_EN:
	title = g_strdup("gretl: command reference"); break;
    case GUI_HELP_EN:
	title = g_strdup("gretl: help"); break;
    case FUNC_HELP_EN:
	title = g_strdup("gretl: function reference"); break;
    case VIEW_LOG:
	title = g_strdup(_("gretl: command log")); break;
    case EDIT_HANSL:
    case VIEW_SCRIPT:
    case VIEW_FILE:
    case VIEW_CODEBOOK:
    case VIEW_DOC:
    case EDIT_GP:
    case EDIT_R:
    case EDIT_OX:
    case EDIT_OCTAVE:
    case EDIT_PYTHON:
    case EDIT_JULIA:
    case EDIT_STATA:
    case EDIT_DYNARE:
    case EDIT_LPSOLVE:
    case EDIT_SPEC:
	title = title_from_filename(fname, role, TRUE);
	break;
    case EDIT_NOTES:
	title = g_strdup(_("gretl: session notes")); break;
    case SCRIPT_OUT:
    case FNCALL_OUT:
	title = script_output_title();
	break;
    case VIEW_DATA:
	title = g_strdup(_("gretl: display data")); break;
    case VIEW_BUNDLE:
	title = title_from_data(data);
	break;
    default:
	break;
    }

    return title;
}

static void content_changed (GtkWidget *w, windata_t *vwin)
{
    mark_vwin_content_changed(vwin);
}

static void attach_content_changed_signal (windata_t *vwin)
{
    GtkTextBuffer *tbuf;

    tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
    g_signal_connect(G_OBJECT(tbuf), "changed",
		     G_CALLBACK(content_changed), vwin);
}

#define viewing_source(r) (r == VIEW_PKG_CODE || \
			   r == EDIT_PKG_CODE || \
			   r == VIEW_LOG ||      \
			   r == EDIT_PKG_SAMPLE || \
			   r == VIEW_PKG_SAMPLE)

static void view_buffer_insert_text (windata_t *vwin, PRN *prn)
{
    if (prn != NULL) {
	const char *buf = gretl_print_get_trimmed_buffer(prn);
	gchar *bconv = NULL;

	if (!g_utf8_validate(buf, -1, NULL)) {
	    gsize bytes;

	    bconv = g_locale_to_utf8(buf, -1, NULL, &bytes, NULL);
	    if (bconv != NULL) {
		buf = (const char *) bconv;
	    }
	}

	if (viewing_source(vwin->role)) {
	    sourceview_insert_buffer(vwin, buf);
	} else if (vwin->role == SCRIPT_OUT) {
	    textview_set_text_colorized(vwin->text, buf);
	} else if (vwin->role == BUILD_PKG) {
	    textview_set_text_report(vwin->text, buf);
	} else if (vwin->role == VIEW_DBSEARCH) {
	    textview_set_text_dbsearch(vwin, buf);
	} else {
	    textview_set_text(vwin->text, buf);
	}

	g_free(bconv);
    }
}

/* for use with reuseable script output window */
static windata_t *script_out_viewer;

static gboolean nullify_script_out (GtkWidget *w, windata_t **pvwin)
{
    *pvwin = NULL;
    return FALSE;
}

void set_reuseable_output_window (int policy, windata_t *vwin)
{
    if (policy == OUTPUT_POLICY_NEW_WINDOW) {
	script_out_viewer = NULL;
    } else {
	script_out_viewer = vwin;
	g_signal_connect(G_OBJECT(vwin->main), "destroy",
			 G_CALLBACK(nullify_script_out),
			 &script_out_viewer);
    }
}

static windata_t *reuse_script_out (windata_t *vwin, PRN *prn)
{
    int policy = get_script_output_policy();
    GtkTextBuffer *buf;
    const char *newtext;

    newtext = gretl_print_get_buffer(prn);
    buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));

    if (policy == OUTPUT_POLICY_APPEND) {
	GtkTextMark *mark;
	GtkTextIter iter;

	gtk_text_buffer_get_end_iter(buf, &iter);
	mark = gtk_text_buffer_create_mark(buf, NULL, &iter, TRUE);
	textview_append_text_colorized(vwin->text, newtext, 1);
	gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(vwin->text),
				     mark, 0.0, TRUE, 0, 0.05);
	gtk_text_buffer_delete_mark(buf, mark);
    } else {
	/* replace previous content */
	gtk_text_buffer_set_text(buf, "", -1);
	textview_set_text_colorized(vwin->text, newtext);
	cursor_to_top(vwin);
    }

    gretl_print_destroy(prn);
    gtk_window_present(GTK_WINDOW(vwin->main));

    return vwin;
}

static void vwin_add_closer (windata_t *vwin)
{
    GtkWidget *b;

    b = gtk_button_new_with_label(_("Close"));
    g_signal_connect(G_OBJECT(b), "clicked",
		     G_CALLBACK(delete_widget), vwin->main);
    gtk_widget_show(b);
    gtk_box_pack_end(GTK_BOX(vwin->vbox), b, FALSE, FALSE, 0);
}

void set_model_save_state (windata_t *vwin, gboolean s)
{
    flip(vwin->ui, "/menubar/File/SaveAsIcon", s);
    flip(vwin->ui, "/menubar/File/SaveAndClose", s);
}

windata_t *
view_buffer_with_parent (windata_t *parent, PRN *prn,
			 int hsize, int vsize,
			 const char *title, int role,
			 gpointer data)
{
    int width = 0, nlines = 0;
    windata_t *vwin;

    if (role == SCRIPT_OUT && script_out_viewer != NULL) {
	return reuse_script_out(script_out_viewer, prn);
    }

    if (title != NULL) {
	vwin = gretl_viewer_new_with_parent(parent, role, title,
					    data);
    } else {
	gchar *tmp = make_viewer_title(role, NULL, data);

	vwin = gretl_viewer_new_with_parent(parent, role, tmp,
					    data);
	g_free(tmp);
    }

    if (vwin == NULL) {
	return NULL;
    }

    if (role == VAR || role == VECM || role == SYSTEM) {
	/* special case: use a text-based menu bar */
	vwin_add_ui(vwin, system_items, n_system_items, sys_ui);
	set_model_save_state(vwin, !is_session_model(vwin->data));
	add_system_menu_items(vwin, role);
	vwin_pack_toolbar(vwin);
	if (role == VAR || role == VECM) {
	    g_signal_connect(G_OBJECT(vwin->mbar), "button-press-event",
			     G_CALLBACK(check_VAR_menu), vwin);
	}
	gretl_object_ref(data, (role == SYSTEM)? GRETL_OBJ_SYS : GRETL_OBJ_VAR);
    } else if (role == VIEW_PKG_CODE ||
	       role == VIEW_PKG_SAMPLE ||
	       role == VIEW_LOG ||
	       role == VIEW_DBSEARCH ||
	       role == VIEW_MODELTABLE) {
	vwin_add_viewbar(vwin, 0);
    } else if (role == EDIT_PKG_CODE ||
	       role == EDIT_PKG_SAMPLE ||
	       role == EDIT_PKG_HELP ||
	       role == EDIT_PKG_GHLP) {
	vwin_add_viewbar(vwin, VIEWBAR_EDITABLE);
    } else if (role == IMPORT || role == BUILD_PKG) {
	vwin_add_closer(vwin);
    } else {
	vwin_add_viewbar(vwin, VIEWBAR_HAS_TEXT);
    }

    if (role != VIEW_PKG_CODE &&
	role != EDIT_PKG_CODE &&
	role != VIEW_PKG_SAMPLE &&
	role != VIEW_LOG &&
	role != EDIT_PKG_HELP &&
	role != EDIT_PKG_GHLP &&
	role != SCRIPT_OUT) {
	gretl_print_get_size(prn, &width, &nlines);
	if (width > 0 && width + 2 < hsize) {
	    hsize = width + 2;
	}
    }

    if (role == VIEW_PKG_CODE || role == VIEW_PKG_SAMPLE || role == VIEW_LOG) {
	create_source(vwin, hsize, vsize, FALSE);
    } else if (role == EDIT_PKG_CODE || role == EDIT_PKG_SAMPLE) {
	create_source(vwin, hsize, vsize, TRUE);
    } else if (role == EDIT_PKG_HELP || role == EDIT_PKG_GHLP) {
	/* editable text */
	create_text(vwin, hsize, vsize, nlines, TRUE);
    } else {
	/* non-editable text */
	create_text(vwin, hsize, vsize, nlines, FALSE);
    }

    text_table_setup(vwin->vbox, vwin->text);

    if (role == SCRIPT_OUT) {
	if (data != NULL) {
	    /* partial output window for script */
	    vwin_add_child((windata_t *) data, vwin);
	    /* define "top-hbox" here? */
	}
	g_signal_connect(G_OBJECT(vwin->main), "destroy",
			 G_CALLBACK(nullify_script_out),
			 &script_out_viewer);
	script_out_viewer = vwin;
    }

    /* insert and then free the text buffer */
    view_buffer_insert_text(vwin, prn);
    gretl_print_destroy(prn);

    g_signal_connect(G_OBJECT(vwin->main), "key-press-event",
		     G_CALLBACK(catch_viewer_key), vwin);

    gtk_widget_show(vwin->vbox);
    gtk_widget_show(vwin->main);

    if (role == EDIT_PKG_CODE || role == EDIT_PKG_SAMPLE ||
	role == EDIT_PKG_HELP || role == EDIT_PKG_GHLP) {
	attach_content_changed_signal(vwin);
	g_signal_connect(G_OBJECT(vwin->main), "delete-event",
			 G_CALLBACK(query_save_text), vwin);
    } else if (role == SUMMARY) {
	widget_set_int(vwin->text, "digits", get_gretl_digits());
    }

    if (role == BUILD_PKG) {
	scroll_to_foot(vwin);
    } else {
	g_signal_connect(G_OBJECT(vwin->text), "button-press-event",
			 G_CALLBACK(text_popup_handler), vwin);
	cursor_to_top(vwin);
    }

    gtk_widget_grab_focus(vwin->text);

    return vwin;
}

windata_t *view_buffer (PRN *prn, int hsize, int vsize,
			const char *title, int role,
			gpointer data)
{
    return view_buffer_with_parent(NULL, prn, hsize,
				   vsize, title,
				   role, data);
}

/* hansl_output_viewer_new: here we're creating a window
   that will display script output, allowing for the
   possibility that it may take a while for the (full)
   output to appear, and the script "flush" mechanism
   may be operating to produce incremental display.
*/

windata_t *hansl_output_viewer_new (PRN *prn, int role,
				    const char *title)
{
    windata_t *vwin;
    const char *buf;

    if (title != NULL) {
	vwin = gretl_viewer_new_with_parent(NULL, role,
					    title, NULL);
    } else {
	gchar *tmp = make_viewer_title(role, NULL, NULL);

	vwin = gretl_viewer_new_with_parent(NULL, role,
					    tmp, NULL);
	g_free(tmp);
    }

    if (vwin == NULL) {
	return NULL;
    }

    vwin_add_tmpbar(vwin);
    create_text(vwin, SCRIPT_WIDTH, 450, 0, FALSE);
    text_table_setup(vwin->vbox, vwin->text);

    /* insert the text buffer from @prn */
    buf = gretl_print_get_buffer(prn);
    if (buf != NULL && *buf != '\0') {
	textview_set_text_colorized(vwin->text, buf);
    }

    g_signal_connect(G_OBJECT(vwin->main), "key-press-event",
		     G_CALLBACK(catch_viewer_key), vwin);
    g_signal_connect(G_OBJECT(vwin->text), "button-press-event",
		     G_CALLBACK(text_popup_handler), vwin);

    gtk_widget_show(vwin->vbox);
    gtk_widget_show(vwin->main);
    gtk_widget_grab_focus(vwin->text);

    return vwin;
}

#define text_out_ok(r) (r == VIEW_DATA || r == VIEW_FILE)

windata_t *
view_file_with_title (const char *filename, int editable, int del_file,
		      int hsize, int vsize, int role,
		      const char *given_title)
{
    windata_t *vwin;
    FILE *fp;
    int ins = 0;

    /* first check that we can open the specified file */
    fp = gretl_fopen(filename, "r");
    if (fp == NULL) {
	errbox_printf(_("Can't open %s for reading"), filename);
	return NULL;
    } else {
	fclose(fp);
    }

#if 0
    /* experimental, not yet */
    if (swallow && role == EDIT_HANSL) {
	ins = mainwin_get_vwin_insertion();
	fprintf(stderr, "HERE ins=%d, title '%s'\n", ins, given_title);
	if (ins) {
	    preset_viewer_flag(VWIN_SWALLOW);
	}
    }
#endif

    if (!ins && role == EDIT_HANSL && use_tabbed_editor()) {
	vwin = viewer_tab_new(role, filename, NULL);
    } else if (editing_alt_script(role) && use_tabbed_editor()) {
	vwin = viewer_tab_new(role, filename, NULL);
    } else if (given_title != NULL) {
	vwin = gretl_viewer_new(role, given_title, NULL);
    } else {
	gchar *title = make_viewer_title(role, filename, NULL);

	vwin = gretl_viewer_new(role, (title != NULL)? title : filename,
				NULL);
	g_free(title);
    }

    if (vwin == NULL) {
	return NULL;
    }

    strcpy(vwin->fname, filename);

    if (role != VIEW_DOC) {
	ViewbarFlags vflags = 0;

	if (editable) {
	    vflags = VIEWBAR_EDITABLE;
	}
	if (text_out_ok(role)) {
	    vflags |= VIEWBAR_HAS_TEXT;
	}
	vwin_add_viewbar(vwin, vflags);
    }

    if (textview_use_highlighting(role) || editable) {
	create_source(vwin, hsize, vsize, editable);
    } else {
	create_text(vwin, hsize, vsize, 0, editable);
    }

    text_table_setup(vwin->vbox, vwin->text);

    if (textview_use_highlighting(role) || editable) {
	sourceview_insert_file(vwin, filename);
    } else {
	textview_insert_file(vwin, filename);
    }

    /* editing script or graph commands: grab the "changed" signal
       and set up alert for unsaved changes on exit */
    if (vwin_editing_script(role)) {
	attach_content_changed_signal(vwin);
	if (!window_is_tab(vwin)) {
	    g_signal_connect(G_OBJECT(vwin->main), "delete-event",
			     G_CALLBACK(query_save_text), vwin);
	}
	/* since 2021-05-18 */
	vwin->flags |= VWIN_USE_FOOTER;
    }

    /* clean up when dialog is destroyed */
    if (del_file) {
	gchar *fname = g_strdup(filename);

	g_signal_connect(G_OBJECT(vwin->main), "destroy",
			 G_CALLBACK(delete_file), (gpointer) fname);
    }

    if (window_is_tab(vwin)) {
	show_tabbed_viewer(vwin);
    } else {
	g_signal_connect(G_OBJECT(vwin->main), "key-press-event",
			 G_CALLBACK(catch_viewer_key), vwin);
	gtk_widget_show_all(vwin->main);
    }

    g_signal_connect(G_OBJECT(vwin->text), "button-press-event",
		     G_CALLBACK(text_popup_handler), vwin);

    cursor_to_top(vwin);
    gtk_widget_grab_focus(vwin->text);

#if 0 /* not yet */
    if (vwin->flags & VWIN_SWALLOW) {
	mainwin_insert_vwin(vwin);
    }
#endif

    return vwin;
}

windata_t *view_file (const char *filename, int editable, int del_file,
		      int hsize, int vsize, int role)
{
    return view_file_with_title(filename, editable, del_file,
				hsize, vsize, role, NULL);
}

windata_t *view_script (const char *filename, int editable,
			int role)
{
    if (editable) {
	windata_t *vwin = get_editor_for_file(filename);

	if (vwin != NULL) {
	    gretl_viewer_present(vwin);
	    return vwin;
	}
    }

    return view_file_with_title(filename, editable, 0,
				SCRIPT_WIDTH, SCRIPT_HEIGHT,
				role, NULL);
}

windata_t *console_window (int hsize, int vsize)
{
    windata_t *vwin;

    if (swallow) {
	vwin = gretl_viewer_new(CONSOLE, NULL, NULL);
    } else {
	gchar *title = NULL;
#ifdef GRETL_PID_FILE
	int seqno = gretl_sequence_number();

	if (seqno > 1) {
	    title = g_strdup_printf("%s (%d)", _("gretl console"), seqno);
	}
#endif
	if (title != NULL) {
	    vwin = gretl_viewer_new(CONSOLE, title, NULL);
	    g_free(title);
	} else {
	    vwin = gretl_viewer_new(CONSOLE, _("gretl console"), NULL);
	}
    }

    if (vwin == NULL) {
	return NULL;
    }

    vwin->flags |= VWIN_USE_FOOTER;
    vwin_add_viewbar(vwin, VIEWBAR_EDITABLE);
    create_console(vwin, hsize, vsize);
    text_table_setup(vwin->vbox, vwin->text);

    /* catch some special keystrokes */
    g_signal_connect(G_OBJECT(vwin->text), "key-press-event",
		     G_CALLBACK(catch_viewer_key), vwin);

    gtk_widget_show(vwin->vbox);
    if (vwin->main != vwin->vbox) {
	gtk_widget_show(vwin->main);
    }

    g_signal_connect(G_OBJECT(vwin->text), "button-press-event",
		     G_CALLBACK(text_popup_handler), vwin);

    return vwin;
}

void help_panes_setup (windata_t *vwin, GtkWidget *text)
{
    GtkWidget *hp = gtk_hpaned_new();
    GtkWidget *sw;

    gtk_container_add(GTK_CONTAINER(vwin->vbox), hp);

    add_help_navigator(vwin, hp);

    sw = gtk_scrolled_window_new(NULL, NULL);
    gtk_paned_pack2(GTK_PANED(hp), sw, TRUE, TRUE);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
				   GTK_POLICY_AUTOMATIC,
				   GTK_POLICY_AUTOMATIC);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
					GTK_SHADOW_IN);
    gtk_container_add(GTK_CONTAINER(sw), text);

    gtk_widget_show_all(hp);
}

windata_t *view_help_file (const char *filename, int role)
{
    windata_t *vwin;
    gchar *fbuf = NULL;
    gchar *title = NULL;
    int hsize = 82, vsize = 450;

    /* grab content of the appropriate help file into a buffer */
    gretl_file_get_contents(filename, &fbuf, NULL);
    if (fbuf == NULL) {
	return NULL;
    }

    title = make_viewer_title(role, NULL, NULL);
    vwin = gretl_viewer_new(role, title, NULL);
    g_free(title);

    if (vwin == NULL) return NULL;

    strcpy(vwin->fname, filename);
    vwin->data = fbuf;

    if (role != GUI_HELP && role != GUI_HELP_EN) {
	set_up_helpview_menu(vwin);
	hsize += 4;
    }

    if (role == FUNC_HELP || role == FUNC_HELP_EN) {
	vsize = 500;
    }

    create_text(vwin, hsize, vsize, 0, FALSE);

    if (role == GUI_HELP || role == GUI_HELP_EN) {
	text_table_setup(vwin->vbox, vwin->text);
    } else {
	help_panes_setup(vwin, vwin->text);
    }

    g_signal_connect(G_OBJECT(vwin->text), "key-press-event",
		     G_CALLBACK(catch_viewer_key), vwin);

    if (vwin->role == CMD_HELP || vwin->role == CMD_HELP_EN ||
	vwin->role == FUNC_HELP || vwin->role == FUNC_HELP_EN) {
	g_signal_connect(G_OBJECT(vwin->text), "button-press-event",
			 G_CALLBACK(help_popup_handler),
			 vwin);
    } else {
	g_signal_connect(G_OBJECT(vwin->text), "button-press-event",
			 G_CALLBACK(text_popup_handler), vwin);
    }

    gtk_widget_show(vwin->vbox);
    gtk_widget_show(vwin->main);

    /* make the helpfile variant discoverable via vwin->text */
    g_object_set_data(G_OBJECT(vwin->text), "role",
		      GINT_TO_POINTER(vwin->role));

    gtk_widget_grab_focus(vwin->text);

    return vwin;
}

static gboolean enter_close_button (GtkWidget *button,
				    GdkEventCrossing *event,
				    gpointer p)
{
    /* remove text cursor: looks broken over a button */
    gdk_window_set_cursor(gtk_widget_get_window(button), NULL);
    return FALSE;
}

static gboolean leave_close_button (GtkWidget *button,
				    GdkEventCrossing *event,
				    gpointer p)
{
    GdkCursor *cursor = gdk_cursor_new(GDK_XTERM);

    if (cursor != NULL) {
	/* revert to text cursor */
	gdk_window_set_cursor(gtk_widget_get_window(button), cursor);
	gdk_cursor_unref(cursor);
    }
    return FALSE;
}

static GtkWidget *small_close_button (GtkWidget *targ)
{
    GtkWidget *img = gtk_image_new_from_stock(GRETL_STOCK_CLOSE,
					      GTK_ICON_SIZE_MENU);
    GtkWidget *button = gtk_button_new();

    gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
    gtk_container_add(GTK_CONTAINER(button), img);

    gtk_widget_add_events(button, GDK_ENTER_NOTIFY_MASK |
			  GDK_LEAVE_NOTIFY_MASK);
    g_signal_connect(button, "enter-notify-event",
		     G_CALLBACK(enter_close_button), NULL);
    g_signal_connect(button, "leave-notify-event",
		     G_CALLBACK(leave_close_button), NULL);
    g_signal_connect_swapped(button, "clicked",
			     G_CALLBACK(gtk_widget_destroy),
			     targ);
    gtk_widget_show_all(button);

    return button;
}

/* Stick a little "close" button into a GtkTextBuffer */

static void add_text_closer (windata_t *vwin)
{
    GtkTextBuffer *tbuf;
    GtkTextIter iter, iend;
    GtkTextTag *tag;
    GtkTextChildAnchor *anchor;
    GtkWidget *button;

    tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
    gtk_text_buffer_get_start_iter(tbuf, &iter);
    tag = gtk_text_buffer_create_tag(tbuf, NULL, "justification",
				     GTK_JUSTIFY_RIGHT, NULL);
    anchor = gtk_text_buffer_create_child_anchor(tbuf, &iter);
    button = small_close_button(vwin->main);
    gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(vwin->text),
				      button, anchor);
    gtk_text_buffer_get_iter_at_child_anchor(tbuf, &iend, anchor);
    gtk_text_iter_forward_char(&iend);
    gtk_text_buffer_insert(tbuf, &iend, "\n", -1);
    gtk_text_buffer_get_start_iter(tbuf, &iter);
    gtk_text_buffer_apply_tag(tbuf, tag, &iter, &iend);
}

/* For use when we want to display a piece of formatted text -- such
   as help for a gretl function package or a help bibliography entry
   -- in a window of its own, without any menu apparatus on the
   window. In the case of VIEW_BIBITEM, this should be a minimal window
   with no decorations and a simple "closer" button embedded in the
   GtkTextView (bibliographical popup).
*/

windata_t *view_formatted_text_buffer (const gchar *title,
				       const char *buf,
				       int hsize, int vsize,
				       int role)
{
    windata_t *vwin;

    vwin = gretl_viewer_new_with_parent(NULL, role, title, NULL);
    if (vwin == NULL) return NULL;

    /* non-editable text */
    create_text(vwin, hsize, vsize, 0, FALSE);

    if (role == VIEW_BIBITEM) {
	/* no scrolling apparatus */
	gtk_container_add(GTK_CONTAINER(vwin->vbox), vwin->text);
	gtk_widget_show(vwin->text);
	gtk_window_set_decorated(GTK_WINDOW(vwin->main), FALSE);
    } else {
	text_table_setup(vwin->vbox, vwin->text);
    }

    gretl_viewer_set_formatted_buffer(vwin, buf);

    if (role == VIEW_BIBITEM) {
	add_text_closer(vwin);
    }

    gtk_widget_show(vwin->vbox);

    if (role != VIEW_BIBITEM) {
	gtk_widget_show(vwin->main);
	gtk_widget_grab_focus(vwin->text);
    }

    return vwin;
}

/* Called on destroying an editing window: give the user a chance
   to save if the content is changed, or to cancel the close.
*/

gint query_save_text (GtkWidget *w, GdkEvent *event, windata_t *vwin)
{
    if (vwin_content_changed(vwin)) {
	int resp = yes_no_cancel_dialog("gretl",
					_("Save changes?"),
					vwin_toplevel(vwin));

	if (resp == GRETL_CANCEL) {
	    /* cancel -> don't save, but also don't close */
	    return TRUE;
	} else if (resp == GRETL_YES) {
	    vwin_save_callback(NULL, vwin);
	}
    }

    return FALSE;
}

windata_t *edit_buffer (char **pbuf, int hsize, int vsize,
			char *title, int role)
{
    windata_t *vwin;

    vwin = gretl_viewer_new(role, title, pbuf);
    if (vwin == NULL) {
	return NULL;
    }

    /* add a tool bar */
    vwin_add_viewbar(vwin, VIEWBAR_EDITABLE);

    create_source(vwin, hsize, vsize, TRUE);
    text_table_setup(vwin->vbox, vwin->text);

    /* insert the buffer text */
    if (pbuf != NULL && *pbuf != NULL) {
	sourceview_insert_buffer(vwin, *pbuf);
    }

    g_signal_connect(G_OBJECT(vwin->text), "button-press-event",
		     G_CALLBACK(text_popup_handler), vwin);
    g_signal_connect(G_OBJECT(vwin->main), "key-press-event",
		     G_CALLBACK(catch_viewer_key), vwin);

    attach_content_changed_signal(vwin);

    /* alert for unsaved changes on exit */
    g_signal_connect(G_OBJECT(vwin->main), "delete-event",
		     G_CALLBACK(query_save_text), vwin);

    gtk_widget_show(vwin->vbox);
    gtk_widget_show(vwin->main);

    cursor_to_top(vwin);

    return vwin;
}

windata_t *view_model (PRN *prn, MODEL *pmod, char *title)
{
    int hsize = MODEL_WIDTH;
    int vsize = MODEL_HEIGHT;
    windata_t *vwin;
    const char *buf;
    gchar *tmp;
    int width, nlines, tabbed;

#if 0
    fprintf(stderr, "view_model: pmod at %p, model %d\n",
	    (void *) pmod, pmod->ID);
#endif

    tabbed = use_tabbed_model_viewer();

    if (tabbed) {
	tmp = g_strdup_printf(_("model %d"), pmod->ID);
	vwin = viewer_tab_new(VIEW_MODEL, tmp, pmod);
	g_free(tmp);
    } else if (title != NULL) {
	vwin = gretl_viewer_new(VIEW_MODEL, title, pmod);
    } else {
	tmp = g_strdup_printf(_("gretl: model %d"), pmod->ID);
	vwin = gretl_viewer_new(VIEW_MODEL, tmp, pmod);
	g_free(tmp);
    }

    if (vwin == NULL) {
	return NULL;
    }

    /* Take responsibility for one reference to this model */
    gretl_object_ref(pmod, GRETL_OBJ_EQN);

    set_up_model_view_menu(vwin);

    gretl_print_get_size(prn, &width, &nlines);
    if (!tabbed && width > 0 && width + 2 < hsize) {
	hsize = width + 2;
    }

    create_text(vwin, hsize, vsize, nlines, FALSE);
    text_table_setup(vwin->vbox, vwin->text);

    /* insert and then free the model results buffer */
    buf = gretl_print_get_trimmed_buffer(prn);
    textview_set_text(vwin->text, buf);
    gretl_print_destroy(prn);

    /* record digits in force */
    widget_set_int(vwin->text, "digits", get_gretl_digits());

    /* sync number of model tests */
    vwin->n_model_tests = pmod->ntests;

    /* attach popup */
    g_signal_connect(G_OBJECT(vwin->text), "button-press-event",
		     G_CALLBACK(text_popup_handler), vwin);

    if (window_is_tab(vwin)) {
	show_tabbed_viewer(vwin);
    } else {
	g_signal_connect(G_OBJECT(vwin->main), "key-press-event",
			 G_CALLBACK(catch_viewer_key), vwin);
	gtk_widget_show_all(vwin->main);
    }

    cursor_to_top(vwin);
    gtk_widget_grab_focus(vwin->text);

    return vwin;
}

static void mnl_probs_callback (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    MODEL *pmod = vwin->data;
    gretl_matrix *P = NULL;
    int err = 0;

    if (pmod == NULL) return;

    P = mn_logit_probabilities(pmod, dataset, &err);

    if (err) {
	gui_errmsg(err);
    } else {
	PRN *prn = NULL;

	if (bufopen(&prn)) {
	    gretl_matrix_free(P);
	} else {
	    gretl_matrix *yvals = gretl_model_get_data(pmod, "yvals");
	    int obslen = max_obs_marker_length(dataset);
	    int i, j, t = gretl_matrix_get_t1(P);
	    int n = gretl_vector_get_length(yvals);
	    double x;

	    pprintf(prn, "\nEstimated outcome probabilities for %s\n\n",
		    gretl_model_get_depvar_name(pmod, dataset));

	    /* case values */
	    bufspace(obslen, prn);
	    for (j=0; j<n; j++) {
		pprintf(prn, "%9g", yvals->val[j]);
	    }
	    pputc(prn, '\n');

	    /* format the matrix content nicely, prepending
	       observation strings */

	    for (i=0; i<P->rows; i++) {
		print_obs_marker(t, dataset, obslen, prn);
		for (j=0; j<P->cols; j++) {
		    x = gretl_matrix_get(P, i, j);
		    if (na(x)) {
			pprintf(prn, "%9s", " ");
		    } else {
			pprintf(prn, "%9.4f", x);
		    }
		}
		pputc(prn, '\n');
		t++;
	    }

	    view_buffer(prn, 78, 400, _("Outcome probabilities"), PRINT, NULL);
	    gretl_matrix_free(P);
	}
    }
}

static void add_multinomial_probs_item (windata_t *vwin)
{
    const gchar *mpath = "/menubar/Analysis";
    GtkActionEntry entry;

    action_entry_init(&entry);
    entry.name = "mnlprobs";
    entry.label = _("Outcome probabilities");
    entry.callback = G_CALLBACK(mnl_probs_callback);
    vwin_menu_add_item(vwin, mpath, &entry);
}

static int dw_pval_ok (const MODEL *pmod)
{
    if (na(pmod->dw)) {
	return 0;
    } else if (pmod->ci == OLS) {
	return 1;
    } else if (pmod->ci == PANEL) {
	return panel_DW_pval_ok(pmod);
    } else {
	return 0;
    }
}

static void get_ci_and_opt (const gchar *s, int *ci, gretlopt *opt)
{
    char c, word[9];

    sscanf(s, "%8[^:]:%c", word, &c);
    *ci = gretl_command_number(word);
    *opt = opt_from_flag((unsigned char) c);
}

static void set_tests_menu_state (GtkUIManager *ui, const MODEL *pmod)
{
    gretlopt opt;
    char path[128];
    const gchar *s;
    int i, n, ci;

    if (pmod->ci == MPOLS) {
	/* can we relax this? */
	flip(ui, "/menubar/Tests", FALSE);
	return;
    }

    n = G_N_ELEMENTS(model_test_items);

    for (i=0; i<n; i++) {
	int skip = 0;

	opt = OPT_NONE;
	s = model_test_items[i].name;
	if (strchr(s, ':')) {
	    get_ci_and_opt(s, &ci, &opt);
	} else if (!strcmp(s, "dwpval")) {
	    sprintf(path, "/menubar/Tests/%s", s);
	    flip(ui, path, dw_pval_ok(pmod));
	    continue;
	} else if (!strcmp(s, "Hsk")) {
	    ci = MODTEST;
	    if (pmod->ci == PANEL && (pmod->opt & OPT_U)) {
		/* random effects: not supported (FIXME?) */
		skip = 1;
	    } else {
		opt = (dataset_is_panel(dataset))? OPT_P : OPT_W;
	    }
	} else {
	    ci = gretl_command_number(s);
	}
	sprintf(path, "/menubar/Tests/%s", s);
	if (skip) {
	    flip(ui, path, FALSE);
	} else {
	    flip(ui, path, model_test_ok(ci, opt, pmod, dataset));
	}
    }

    if (pmod->ci == GARCH) {
	flip(ui, "/menubar/Tests/Hsk", FALSE);
    } else if (pmod->ci == PROBIT && (pmod->opt & OPT_E)) {
	/* random effects probit */
	flip(ui, "/menubar/Tests/modtest:n", FALSE);
    } else if (gretl_is_between_model(pmod)) {
	flip(ui, "/menubar/Tests/coeffsum", FALSE);
	flip(ui, "/menubar/Tests/Hsk", FALSE);
    }
}

static void set_analysis_menu_state (windata_t *vwin, const MODEL *pmod)
{
    GtkUIManager *ui = vwin->ui;
    gboolean s;

    if (pmod->ci == MLE || pmod->ci == GMM || pmod->ci == BIPROBIT) {
	/* can we relax some of this later? */
	flip(ui, "/menubar/Analysis/DisplayAFR", FALSE);
	flip(ui, "/menubar/Analysis/Forecasts", FALSE);
    } else if (pmod->ci == LOGIT && gretl_model_get_int(pmod, "multinom")) {
	/* relax this? */
	flip(ui, "/menubar/Analysis/Forecasts", FALSE);
	add_multinomial_probs_item(vwin);
    } else if (pmod->ci == PROBIT && (pmod->opt & OPT_E)) {
	/* random effects probit */
	flip(ui, "/menubar/Analysis/Forecasts", FALSE);
    }

    if (pmod->ncoeff == 1) {
	flip(ui, "/menubar/Analysis/ConfEllipse", FALSE);
    }

    if (pmod->ci == DPANEL || (pmod->ci == PANEL && !(pmod->opt & OPT_P))) {
	flip(ui, "/menubar/Analysis/Forecasts", FALSE);
    }

    if (pmod->ci != OLS || !pmod->ifc || na(pmod->ess) || na(pmod->tss)) {
	flip(ui, "/menubar/Analysis/ANOVA", FALSE);
    }

    if (!bootstrap_ok(pmod->ci)) {
	flip(ui, "/menubar/Analysis/Bootstrap", FALSE);
    }

    if (gretl_model_get_int(pmod, "null-model")) {
	flip(ui, "/menubar/Analysis/ConfIntervals", FALSE);
	flip(ui, "/menubar/Analysis/Covariance", FALSE);
    }

    s = model_test_ok(VIF, OPT_NONE, pmod, dataset) ||
	pmod->vcv != NULL;
    flip(ui, "/menubar/Analysis/Collinearity", s);

    if (!model_test_ok(LEVERAGE, OPT_NONE, pmod, dataset)) {
	flip(ui, "/menubar/Analysis/Leverage", FALSE);
    }
}

static void arma_x12_menu_mod (windata_t *vwin)
{
    flip(vwin->ui, "/menubar/Analysis/Covariance", FALSE);
    add_x12_output_menu_item(vwin);
}

static void rq_coeff_intervals_mod (windata_t *vwin)
{
    flip(vwin->ui, "/menubar/Analysis/ConfIntervals", FALSE);
}

static void midas_plot_callback (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    MODEL *pmod = vwin->data;
    gretl_matrix *C = NULL;
    int err;

    if (pmod == NULL) return;

    C = gretl_model_get_data(pmod, "midas_coeffs");

    if (C != NULL) {
	const char *literal =
	    "{set title 'MIDAS coefficients';"
	    " set xlabel 'high-frequency lag';"
	    " set ylabel '';}";

	err = matrix_plot(C, NULL, literal,  OPT_P | OPT_S | OPT_G);
	gui_graph_handler(err);
    }
}

static void arma_spectrum_callback (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    MODEL *pmod = (MODEL *) vwin->data;
    int err;

    err = arma_spectrum_plot(pmod, dataset, OPT_NONE);

    gui_graph_handler(err);
}

#define intervals_model(m) (m->ci == LAD && \
			    gretl_model_get_data(m, "coeff_intervals"))

static void adjust_model_menu_state (windata_t *vwin, const MODEL *pmod)
{
    /* disallow saving an already-saved model */
    if (is_session_model((void *) pmod)) {
	set_model_save_state(vwin, FALSE);
    }

    if (RQ_SPECIAL_MODEL(pmod)) {
	/* can we relax this later? */
	flip(vwin->ui, "/menubar/Tests", FALSE);
	flip(vwin->ui, "/menubar/Save", FALSE);
	flip(vwin->ui, "/menubar/Analysis", FALSE);
	return;
    }

    set_tests_menu_state(vwin->ui, pmod);
    set_analysis_menu_state(vwin, pmod);

    if (intervals_model(pmod)) {
	rq_coeff_intervals_mod(vwin);
    }

    if (pmod->ci == ARMA && arma_by_x12a(pmod)) {
	arma_x12_menu_mod(vwin);
    }

    if (pmod->ci == MLE || pmod->ci == GMM || pmod->ci == BIPROBIT) {
	/* can we relax this later? */
	flip(vwin->ui, "/menubar/Graphs", FALSE);
    } else if (gretl_is_between_model(pmod) &&
	       pmod->dataset == NULL) {
	flip(vwin->ui, "/menubar/Graphs", FALSE);
    }

    if (pmod->ci == GMM || pmod->ci == BIPROBIT) {
	/* FIXME? */
	flip(vwin->ui, "/menubar/Save", FALSE);
    }
}

static GtkActionEntry model_data_base_items[] = {
    { "bundle", NULL, N_("_Model as bundle"), NULL, NULL,
      G_CALLBACK(model_stat_callback) },
    { "yhat", NULL, N_("_Fitted values"), NULL, NULL,
      G_CALLBACK(fit_resid_callback) },
    { "uhat", NULL, N_("_Residuals"), NULL, NULL,
      G_CALLBACK(fit_resid_callback) },
    { "uhat2", NULL, N_("_Squared residuals"), NULL, NULL,
      G_CALLBACK(fit_resid_callback) }
};

static GtkActionEntry ess_items[] = {
    { "ess", NULL, N_("_Error sum of squares"), NULL, NULL,
      G_CALLBACK(model_stat_callback) },
    { "se", NULL, N_("_Standard error of the regression"), NULL, NULL,
      G_CALLBACK(model_stat_callback) }
};

static GtkActionEntry r_squared_items[] = {
    { "rsq", NULL, N_("_R-squared"), NULL, NULL, G_CALLBACK(model_stat_callback) },
    { "trsq", NULL, N_("_T*R-squared"), NULL, NULL, G_CALLBACK(model_stat_callback) }
};

static GtkActionEntry lnl_data_items[] = {
    { "lnL", NULL, N_("_Log likelihood"), NULL, NULL,
      G_CALLBACK(model_stat_callback) }
};

static GtkActionEntry criteria_items[] = {
    { "AIC", NULL, N_("_Akaike Information Criterion"), NULL, NULL,
      G_CALLBACK(model_stat_callback) },
    { "BIC", NULL, N_("_Bayesian Information Criterion"), NULL, NULL,
      G_CALLBACK(model_stat_callback) },
    { "HQC", NULL, N_("_Hannan-Quinn Information Criterion"), NULL, NULL,
      G_CALLBACK(model_stat_callback) }
};

static GtkActionEntry garch_data_items[] = {
    { "h", NULL, N_("_Predicted error variance"), NULL, NULL,
      G_CALLBACK(fit_resid_callback)
    }
};

static GtkActionEntry fixed_effects_data_items[] = {
    { "ahat", NULL, N_("Per-unit _constants"), NULL, NULL,
      G_CALLBACK(fit_resid_callback)
    }
};

static GtkActionEntry random_effects_data_items[] = {
    { "ahat", NULL, N_("Individual effects"), NULL, NULL,
      G_CALLBACK(fit_resid_callback)
    }
};

static GtkActionEntry define_var_items[] = {
    /* Under Save; Sep wanted */
    { "NewVar", NULL, N_("Define _new variable..."), NULL, NULL,
      G_CALLBACK(model_genr_callback) }
};

static int criteria_available (const MODEL *pmod)
{
    int i;

    for (i=0; i<C_MAX; i++) {
	if (na(pmod->criterion[i])) {
	    return 0;
	}
    }

    return 1;
}

static void add_model_dataset_items (windata_t *vwin)
{
    const gchar *path = "/menubar/Save";
    MODEL *pmod = vwin->data;

    vwin_menu_add_items(vwin, path, model_data_base_items,
			G_N_ELEMENTS(model_data_base_items));

    if (gretl_model_get_data(pmod, "ahat") != NULL) {
	if (pmod->opt & OPT_U) {
	    vwin_menu_add_items(vwin, path, random_effects_data_items,
				G_N_ELEMENTS(random_effects_data_items));
	} else {
	    vwin_menu_add_items(vwin, path, fixed_effects_data_items,
				G_N_ELEMENTS(fixed_effects_data_items));
	}
    }

    if (pmod->ci != GARCH && !(pmod->ci == LOGIT && (pmod->opt & OPT_M))) {
	vwin_menu_add_items(vwin, path, ess_items,
			    G_N_ELEMENTS(ess_items));
    }

    if (!ML_ESTIMATOR(pmod->ci) && pmod->ci != LAD && !na(pmod->rsq)) {
	vwin_menu_add_items(vwin, path, r_squared_items,
			    G_N_ELEMENTS(r_squared_items));
    }

    if (!na(pmod->lnL)) {
	vwin_menu_add_items(vwin, path, lnl_data_items,
			    G_N_ELEMENTS(lnl_data_items));
    }

    if (criteria_available(pmod)) {
	vwin_menu_add_items(vwin, path, criteria_items,
			    G_N_ELEMENTS(criteria_items));
    }

    if (pmod->ci == GARCH) {
	vwin_menu_add_items(vwin, path, garch_data_items,
			    G_N_ELEMENTS(garch_data_items));
    }

    vwin_menu_add_separator(vwin, path);

    vwin_menu_add_items(vwin, path, define_var_items,
			G_N_ELEMENTS(define_var_items));
}

static void add_model_tex_items (windata_t *vwin)
{
    MODEL *pmod = (MODEL *) vwin->data;
    int eqn_ok = command_ok_for_model(EQNPRINT, 0, pmod);
    GtkActionGroup *actions;
    GError *err = NULL;
    int imod = 0;

    gtk_ui_manager_add_ui_from_string(vwin->ui, model_tex_ui, -1, &err);

    if (err != NULL) {
	g_message("building LaTeX menu failed: %s", err->message);
	g_error_free(err);
	return;
    }

    actions = gtk_action_group_new("ModelTeX");
    gtk_action_group_set_translation_domain(actions, "gretl");
    gtk_action_group_add_actions(actions, model_tex_items,
				 G_N_ELEMENTS(model_tex_items),
				 vwin);
    gtk_action_group_add_radio_actions(actions, tex_eqn_items,
				       G_N_ELEMENTS(tex_eqn_items),
				       (get_tex_eqn_opt() == OPT_T),
				       G_CALLBACK(set_tex_eqn_opt),
				       vwin);
    gtk_ui_manager_insert_action_group(vwin->ui, actions, 0);
    g_object_unref(actions);

    if (intervals_model(pmod)) {
	eqn_ok = 0;
	imod = 1;
    }

    if (!eqn_ok || pmod->errcode) {
	flip(vwin->ui, "/menubar/LaTeX/TeXView/EqnView", FALSE);
	flip(vwin->ui, "/menubar/LaTeX/TeXCopy/EqnCopy", FALSE);
	flip(vwin->ui, "/menubar/LaTeX/TeXSave/EqnSave", FALSE);
	flip(vwin->ui, "/menubar/LaTeX/EqnOpts", FALSE);
    }

    if (imod) {
	flip(vwin->ui, "/menubar/LaTeX/TabOpts", FALSE);
    }
}

/* dummy placeholder, for when TeX is not supported */

static void add_missing_tex_items (windata_t *vwin)
{
    GtkActionGroup *actions;
    GError *err = NULL;

    gtk_ui_manager_add_ui_from_string(vwin->ui, missing_tex_ui, -1, &err);
    if (err != NULL) {
	g_message("building menus failed: %s", err->message);
	g_error_free(err);
	return;
    }

    actions = gtk_action_group_new("MissingTeX");
    gtk_action_group_add_actions(actions, missing_tex_items,
				 G_N_ELEMENTS(missing_tex_items),
				 vwin);
    gtk_ui_manager_insert_action_group(vwin->ui, actions, 0);
    g_object_unref(actions);

    flip(vwin->ui, "/menubar/LaTeX", FALSE);
}

#define VNAMELEN2 (2*VNAMELEN)

static void add_vars_to_plot_menu (windata_t *vwin)
{
    GtkActionEntry entry;
    const gchar *mpath[] = {
	"/menubar/Graphs/ResidPlot",
	"/menubar/Graphs/FittedActualPlot"
    };
    MODEL *pmod = vwin->data;
    char tmp[VNAMELEN2], aname[VNAMELEN];
    gchar *alabel;
    int *xlist;
    int v1, v2;
    int i, j;

    action_entry_init(&entry);

    /* non-time series: residual boxplot */
    if (!dataset_is_time_series(dataset)) {
	entry.label = _("_Boxplot");
	entry.name = "r:box";
	entry.callback = G_CALLBACK(resid_plot);
	vwin_menu_add_item(vwin, mpath[0], &entry);
    }

    xlist = gretl_model_get_x_list(pmod);

    for (i=0; i<2; i++) {
	/* plot against time/obs number */
	if (dataset_is_time_series(dataset)) {
	    entry.label = _("_Against time");
	} else {
	    entry.label = _("By _observation number");
	}
	entry.name = (i == 0)? "r:byobs" : "f:byobs";
	entry.callback = (i == 0)? G_CALLBACK(resid_plot) :
	    G_CALLBACK(fit_actual_plot);
	vwin_menu_add_item(vwin, mpath[i], &entry);

	if (pmod->ci == NLS ||
	    pmod->ci == MLE ||
	    pmod->ci == GMM ||
	    pmod->ci == PANEL) {
	    continue;
	}

	/* if doing resid plot, put dependent var in menu */
	if (i == 0) {
	    v1 = gretl_model_get_depvar(pmod);
	    if (v1 > 0) {
		sprintf(aname, "r:xvar %d", v1); /* FIXME */
		double_underscores(tmp, dataset->varname[v1]);
		alabel = g_strdup_printf(_("_Against %s"), tmp);
		entry.name = aname;
		entry.label = alabel;
		entry.callback = G_CALLBACK(resid_plot);
		vwin_menu_add_item(vwin, mpath[0], &entry);
		g_free(alabel);
	    }
	}

	if (xlist != NULL) {
	    /* put the independent vars on the menu list */
	    for (j=1; j<=xlist[0]; j++) {
		v1 = xlist[j];
		if (v1 == 0) {
		    continue;
		}
		if (!strcmp(dataset->varname[v1], "time")) {
		    continue;
		}
		sprintf(aname, "%c:xvar %d", (i == 0)? 'r' : 'f', v1);
		double_underscores(tmp, dataset->varname[v1]);
		alabel = g_strdup_printf(_("_Against %s"), tmp);
		entry.name = aname;
		entry.label = alabel;
		entry.callback = (i == 0)? G_CALLBACK(resid_plot) :
		    G_CALLBACK(fit_actual_plot);
		vwin_menu_add_item(vwin, mpath[i], &entry);
		g_free(alabel);
	    }
	}

	if (i == 1) {
	    /* fitted values: offer Theil-type scatterplot */
	    entry.name = "f:theil";
	    entry.label = _("Actual vs. Fitted");
	    entry.callback = G_CALLBACK(fit_actual_plot);
	    vwin_menu_add_item(vwin, mpath[i], &entry);
	}
    }

    /* time series models: residual correlogram, spectrum */
    if (dataset_is_time_series(dataset)) {
	vwin_menu_add_separator(vwin, "/menubar/Graphs");
	entry.name = "Correlogram";
	entry.label = _("Residual _correlogram");
	entry.callback = G_CALLBACK(residual_correlogram_callback);
	vwin_menu_add_item(vwin, "/menubar/Graphs", &entry);
	entry.name = "Spectrum";
	entry.label = _("Residual _periodogram");
	entry.callback = G_CALLBACK(residual_periodogram_callback);
	vwin_menu_add_item(vwin, "/menubar/Graphs", &entry);
	if (pmod->ci == ARMA && gretl_model_get_data(pmod, "ainfo") != NULL) {
	    entry.name = "ARMAspectrum";
	    entry.label = _("_Spectrum vs sample periodogram");
	    entry.callback = G_CALLBACK(arma_spectrum_callback);
	    vwin_menu_add_item(vwin, "/menubar/Graphs", &entry);
	} else if (gretl_model_get_data(pmod, "midas_coeffs") != NULL) {
	    entry.name = "MIDAScoeffs";
	    entry.label = _("_MIDAS coefficients");
	    entry.callback = G_CALLBACK(midas_plot_callback);
	    vwin_menu_add_item(vwin, "/menubar/Graphs", &entry);
	}
    } else {
	vwin_menu_add_separator(vwin, "/menubar/Graphs");
    }

    /* residual Q-Q plot */
    entry.name = "QQPlot";
    entry.label = _("Residual _Q-Q plot");
    entry.callback = G_CALLBACK(residual_qq_plot);
    vwin_menu_add_item(vwin, "/menubar/Graphs", &entry);

    /* 3-D fitted versus actual plot? */
    if (xlist != NULL) {
	int parnames = pmod->dataset != NULL;

	v1 = v2 = -1;
	if (pmod->ifc && xlist[0] == 3) {
	    v1 = parnames ? 1 : xlist[2];
	    v2 = parnames ? 2 : xlist[3];
	} else if (!pmod->ifc && xlist[0] == 2) {
	    v1 = parnames ? 0 : xlist[1];
	    v2 = parnames ? 1 : xlist[2];
	}
	if (v1 >= 0 && v2 >= 0) {
	    char tmp2[VNAMELEN2];

	    if (parnames) {
		char pname[VNAMELEN];

		gretl_model_get_param_name(pmod, pmod->dataset,
					   v1, pname);
		double_underscores(tmp, pname);
		gretl_model_get_param_name(pmod, pmod->dataset,
					   v2, pname);
		double_underscores(tmp2, pname);
	    } else {
		double_underscores(tmp, dataset->varname[v1]);
		double_underscores(tmp2, dataset->varname[v2]);
	    }
	    vwin_menu_add_separator(vwin, mpath[1]);
	    alabel = g_strdup_printf(_("_Against %s and %s"), tmp, tmp2);
	    entry.name = "splot";
	    entry.label = alabel;
	    entry.callback = G_CALLBACK(fit_actual_splot);
	    vwin_menu_add_item(vwin, mpath[1], &entry);
	    g_free(alabel);
	}
    }

    free(xlist);
}

static void plot_dummy_call (GtkRadioAction *action,
			     GtkRadioAction *current,
			     windata_t *vwin)
{
    vwin->active_var = gtk_radio_action_get_current_value(action);
}

static void radio_action_init (GtkRadioActionEntry *a)
{
    a->stock_id = NULL;
    a->accelerator = NULL;
    a->tooltip = NULL;
}

static void add_dummies_to_plot_menu (windata_t *vwin)
{
    GtkActionEntry item;
    GtkRadioActionEntry *items;
    MODEL *pmod = vwin->data;
    const gchar *gpath = "/menubar/Graphs/ResidPlot";
    const gchar *spath = "/menubar/Graphs/ResidPlot/Separation";
    char tmp[VNAMELEN2];
    int *dlist = NULL;
    int i, vi, ndums;

    /* make a list of dummy independent variables */
    for (i=2; i<=pmod->list[0]; i++) {
	vi = pmod->list[i];
	if (vi == LISTSEP) {
	    break;
	} else if (vi > 0 && vi < dataset->v &&
	    gretl_isdummy(dataset->t1, dataset->t2, dataset->Z[vi])) {
	    gretl_list_append_term(&dlist, vi);
	}
    }

    if (dlist == NULL) {
	return;
    }

    ndums = dlist[0];
    items = malloc((ndums + 1) * sizeof *items);
    if (items == NULL) {
	free(dlist);
	return;
    }

    /* add separator */
    vwin_menu_add_separator(vwin, gpath);

    /* add menu branch */
    action_entry_init(&item);
    item.name = "Separation";
    item.label = _("Separation");
    vwin_menu_add_menu(vwin, gpath, &item);

    /* configure "none" radio option */
    radio_action_init(&items[0]);
    items[0].name = "none";
    items[0].label = _("none");
    items[0].value = 0;

    /* put the dummy independent vars on the menu list */
    for (i=1; i<=dlist[0]; i++) {
	vi = dlist[i];
	radio_action_init(&items[i]);
	double_underscores(tmp, dataset->varname[vi]);
	items[i].name = g_strdup_printf("dum %d", vi);
	items[i].label = g_strdup_printf(_("By %s"), tmp);
	items[i].value = vi;
    }

    vwin_menu_add_radios(vwin, spath, items, ndums + 1, 0,
			 G_CALLBACK(plot_dummy_call));

    for (i=1; i<=dlist[0]; i++) {
	g_free((gchar *) items[i].name);
	g_free((gchar *) items[i].label);
    }

    free(items);
    free(dlist);
}

static void varnum_from_action (GtkAction *action, int *i)
{
    const gchar *s = gtk_action_get_name(action);

    sscanf(s, "%*s %d", i);
}

static void tau_plot_call (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    MODEL *pmod = (MODEL *) vwin->data;
    int v, err;

    varnum_from_action(action, &v);
    err = plot_tau_sequence(pmod, dataset, v);
    gui_graph_handler(err);
}

static void add_tau_plot_menu (windata_t *vwin)
{
    GtkActionEntry item;
    MODEL *pmod = vwin->data;
    char tmp[VNAMELEN2], aname[VNAMELEN];
    int i;

    action_entry_init(&item);
    item.name = "TauMenu";
    item.label = _("tau sequence");
    vwin_menu_add_menu(vwin, "/menubar/Graphs", &item);

    item.callback = G_CALLBACK(tau_plot_call);

    /* put the independent vars on the menu list */
    for (i=2; i<=pmod->list[0]; i++) {
	sprintf(aname, "tauseq %d", i - 2);
	double_underscores(tmp, dataset->varname[pmod->list[i]]);
	item.name = aname;
	item.label = tmp;
	vwin_menu_add_item(vwin, "/menubar/Graphs/TauMenu", &item);
    }

    /* and disable what we can't (yet) show */
    flip(vwin->ui, "/menubar/Graphs/ResidPlot", FALSE);
    flip(vwin->ui, "/menubar/Graphs/FittedActualPlot", FALSE);
}

static void x12_output_callback (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    MODEL *pmod = vwin->data;
    char *fname;

    if (pmod == NULL) return;

    fname = gretl_model_get_data(pmod, "x12a_output");

    if (fname != NULL) {
	char *p = strrchr(fname, '.');

	if (p != NULL && strlen(p) == 7) {
	    gchar *tmp = g_strdup(fname);

	    sprintf(p, ".%d", pmod->ID);
	    gretl_rename(tmp, fname);
	    g_free(tmp);
	}
	view_file(fname, 0, 0, 78, 350, VIEW_FILE);
    }
}

static gchar *get_model_ui (void)
{
    gchar *ui = NULL;
    gchar *fname;
    int err;

    fname = g_strdup_printf("%sui%cgretlmodel.xml", gretl_home(), SLASH);
    err = gretl_file_get_contents(fname, &ui, NULL);
    g_free(fname);

    return err ? NULL : ui;
}

static void set_up_model_view_menu (windata_t *vwin)
{
    static gchar *model_ui;
    MODEL *pmod = (MODEL *) vwin->data;
    GtkActionGroup *actions;
    GtkWidget *toplevel;
    GError *err = NULL;

    if (model_ui == NULL) {
	model_ui = get_model_ui();
	if (model_ui == NULL) {
	    errbox("building menus failed");
	    return;
	}
    }

    actions = gtk_action_group_new("ModelActions");
    gtk_action_group_set_translation_domain(actions, "gretl");

    gtk_action_group_add_actions(actions, model_items,
				 G_N_ELEMENTS(model_items),
				 vwin);
    gtk_action_group_add_actions(actions, model_test_items,
				 G_N_ELEMENTS(model_test_items),
				 vwin);

    vwin->ui = gtk_ui_manager_new();
    gtk_ui_manager_insert_action_group(vwin->ui, actions, 0);
    g_object_unref(actions);

    gtk_ui_manager_add_ui_from_string(vwin->ui, model_ui, -1, &err);
    if (err != NULL) {
	g_message("building menus failed: %s", err->message);
	g_error_free(err);
    }

    if (pmod->ci != MLE && pmod->ci != GMM && pmod->ci != BIPROBIT) {
	if (RQ_SPECIAL_MODEL(pmod)) {
	    add_tau_plot_menu(vwin);
	} else {
	    add_vars_to_plot_menu(vwin);
	}
	add_model_dataset_items(vwin);
    }

    /* heteroskedasticity tests: the permissible options vary
       depending on the nature of the model
    */

    if (dataset_is_panel(dataset)) {
	if (pmod->ci == OLS) {
	    vwin_menu_add_items(vwin, "/menubar/Tests/Hsk",
				panel_hsk_items,
				G_N_ELEMENTS(panel_hsk_items));
	} else if (pmod->ci == PANEL && (pmod->opt & OPT_F)) {
	    /* fixed effects */
	    vwin_menu_add_items(vwin, "/menubar/Tests/Hsk",
				panel_hsk_items + 1, 1);
	} else if (0 && pmod->ci == PANEL && (pmod->opt & OPT_U)) {
	    /* random effects: is this OK?? */
	    vwin_menu_add_items(vwin, "/menubar/Tests/Hsk",
				panel_hsk_items + 1, 1);
	}
    } else if (pmod->ci == IVREG) {
	vwin_menu_add_items(vwin, "/menubar/Tests/Hsk",
			    ivreg_hsk_items,
			    G_N_ELEMENTS(ivreg_hsk_items));
    } else if (model_test_ok(MODTEST, OPT_W, pmod, dataset)) {
	vwin_menu_add_items(vwin, "/menubar/Tests/Hsk",
			    base_hsk_items,
			    G_N_ELEMENTS(base_hsk_items));
	if (pmod->ncoeff == 1 || (pmod->ifc && pmod->ncoeff == 2)) {
	    flip(vwin->ui, "/menubar/Tests/Hsk/WhiteSquares", FALSE);
	}
    }

    maybe_add_packages_to_model_menus(vwin);

    if (latex_is_ok() && !pmod->errcode && !RQ_SPECIAL_MODEL(pmod)) {
	add_model_tex_items(vwin);
    } else {
	add_missing_tex_items(vwin);
    }

    if (!text_equation_ok(pmod)) {
	flip(vwin->ui, "/menubar/File/TextEqn", FALSE);
    }

    if (pmod->ci != ARMA && pmod->ci != GARCH &&
	pmod->ci != NLS && pmod->ci != MLE && pmod->ci != GMM &&
	pmod->ci != PANEL && pmod->ci != DPANEL &&
	pmod->ci != BIPROBIT) {
	add_dummies_to_plot_menu(vwin);
    }

    toplevel = vwin_toplevel(vwin);
    if (toplevel != NULL) {
	/* FIXME tabbed case? */
	gtk_window_add_accel_group(GTK_WINDOW(toplevel),
				   gtk_ui_manager_get_accel_group(vwin->ui));
    }

    vwin->mbar = gtk_ui_manager_get_widget(vwin->ui, "/menubar");

    /* disable some menu items if need be */
    adjust_model_menu_state(vwin, pmod);

    g_signal_connect(G_OBJECT(vwin->mbar), "button-press-event",
		     G_CALLBACK(check_model_menu), vwin);

    vwin_pack_toolbar(vwin);
}

enum {
    SYS_DATA_RESIDS,
    SYS_DATA_FITTED,
    SYS_DATA_SIGMA
};

static int sys_data_code (GtkAction *action)
{
    const gchar *s = gtk_action_get_name(action);

    if (!strcmp(s, "uhat")) {
	return SYS_DATA_RESIDS;
    } else if (!strcmp(s, "yhat")) {
	return SYS_DATA_FITTED;
    } else if (!strcmp(s, "sigma")) {
	return SYS_DATA_SIGMA;
    } else {
	return SYS_DATA_RESIDS;
    }
}

static void system_data_callback (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    GRETL_VAR *var = NULL;
    equation_system *sys = NULL;
    const gretl_matrix *M = NULL;
    gchar *wtitle = NULL;
    PRN *prn;
    int code, k = 0;
    int err = 0;

    if (vwin->role == SYSTEM) {
	sys = (equation_system *) vwin->data;
    } else {
	var = (GRETL_VAR *) vwin->data;
    }

    if ((var == NULL && sys == NULL) || bufopen(&prn)) {
	return;
    }

    code = sys_data_code(action);

    if (code == SYS_DATA_SIGMA) {
	if (var != NULL) {
	    wtitle = g_strdup(_("gretl: VAR covariance matrix"));
	    err = gretl_VAR_print_sigma(var, prn);
	} else {
	    wtitle = g_strdup(_("gretl: system covariance matrix"));
	    err = system_print_sigma(sys, prn);
	}
    } else if (code == SYS_DATA_RESIDS || code == SYS_DATA_FITTED) {
	const char *titles[] = {
	    N_("System residuals"),
	    N_("System fitted values")
	};
	const char *title;
	const char **heads = NULL;

	if (var != NULL) {
	    /* fitted values matrix not currently available */
	    M = (code == SYS_DATA_RESIDS)? gretl_VAR_get_residual_matrix(var) :
		NULL;
	} else {
	    M = (code == SYS_DATA_RESIDS)? sys->E : sys->yhat;
	}

	if (M == NULL) {
	    err = E_DATA;
	} else {
	    k = gretl_matrix_cols(M);
	    heads = malloc(k * sizeof *heads);
	    if (heads == NULL) {
		err = E_ALLOC;
	    }
	}

	if (!err) {
	    int i, v;

	    for (i=0; i<k && !err; i++) {
		v = (var != NULL)? gretl_VAR_get_variable_number(var, i) :
		    sys->lists[i][1];
		if (v < 0 || v >= dataset->v) {
		    err = E_DATA;
		} else {
		    heads[i] = dataset->varname[v];
		}
	    }
	}

	if (!err) {
	    title = (code == SYS_DATA_RESIDS)? titles[0] : titles[1];
	    wtitle = g_strdup_printf("gretl: %s", _(title));
	    gretl_matrix_print_with_col_heads(M, _(title), heads,
					      dataset, prn);
	}

	free(heads);
    }

    if (err) {
	gui_errmsg(err);
	gretl_print_destroy(prn);
    } else {
	/* FIXME: add matrix as saveable data */
	view_buffer(prn, 80, 400, wtitle, PRINT, NULL);
    }

    g_free(wtitle);
}

static void add_x12_output_menu_item (windata_t *vwin)
{
    const gchar *mpath = "/menubar/Analysis";
    GtkActionEntry entry;

    vwin_menu_add_separator(vwin, mpath);

    action_entry_init(&entry);
    entry.name = "x12aout";
    entry.label = _("View X-12-ARIMA output");
    entry.callback = G_CALLBACK(x12_output_callback);
    vwin_menu_add_item(vwin, mpath, &entry);
}

#include "up_down.h" /* arrows for buttons below */

static GtkWidget *up_down_button (int up)
{
    GtkWidget *img, *w = gtk_button_new();
    GdkPixbuf *pbuf;

    if (up) {
	pbuf = gdk_pixbuf_new_from_inline(-1, up_pixbuf, FALSE, NULL);
    } else {
	pbuf = gdk_pixbuf_new_from_inline(-1, down_pixbuf, FALSE, NULL);
    }

    img = gtk_image_new_from_pixbuf(pbuf);
    gtk_container_add(GTK_CONTAINER(w), img);
    g_object_unref(pbuf);

    return w;
}

static void set_order_vec (GtkWidget *view)
{
    GtkTreeModel *model;
    GtkTreeIter iter;
    gretl_matrix *m;
    int v, i = 0;

    m = g_object_get_data(G_OBJECT(view), "ordvec");
    model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));

    gtk_tree_model_get_iter_first(model, &iter);
    gtk_tree_model_get(model, &iter, 1, &v, -1);
    m->val[0] = v;

    while (gtk_tree_model_iter_next(model, &iter)) {
	gtk_tree_model_get(model, &iter, 1, &v, -1);
	m->val[++i] = v;
    }
}

static void sensitize_up_down (GtkTreeSelection *selection,
			       GtkWidget *view)
{
    gretl_matrix *v;
    GtkTreeModel *model;
    GtkTreeIter iter;
    GtkTreePath *path;
    GtkWidget *up, *down;
    gboolean s;

    model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
    v = g_object_get_data(G_OBJECT(view), "ordvec");
    up = g_object_get_data(G_OBJECT(view), "up-button");
    down = g_object_get_data(G_OBJECT(view), "down-button");

    gtk_tree_model_get_iter_first(model, &iter);
    s = gtk_tree_selection_iter_is_selected(selection, &iter);
    gtk_widget_set_sensitive(up, !s);

    path = gtk_tree_path_new_from_indices(gretl_vector_get_length(v) - 1,
					  -1);
    s = gtk_tree_selection_path_is_selected(selection, path);
    gtk_widget_set_sensitive(down, !s);
    gtk_tree_path_free(path);
}

static void shift_var_up (GtkButton *b, GtkWidget *view)
{
    GtkTreeSelection *selection;
    GtkTreeModel *model;
    GtkTreeIter seliter, previter;
    GtkTreePath *path;

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
    gtk_tree_selection_get_selected(selection, &model, &seliter);
    path = gtk_tree_model_get_path(model, &seliter);
    if (gtk_tree_path_prev(path)) {
	gtk_tree_model_get_iter(model, &previter, path);
	gtk_list_store_swap(GTK_LIST_STORE(model), &seliter, &previter);
	set_order_vec(view);
	sensitize_up_down(selection, view);
    }
    gtk_tree_path_free(path);
}

static void shift_var_down (GtkButton *b, GtkWidget *view)
{
    GtkTreeSelection *selection;
    GtkTreeModel *model;
    GtkTreeIter seliter, nextiter;

    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
    gtk_tree_selection_get_selected(selection, &model, &seliter);
    nextiter = seliter;
    if (gtk_tree_model_iter_next(model, &nextiter)) {
	gtk_list_store_swap(GTK_LIST_STORE(model), &seliter, &nextiter);
	set_order_vec(view);
	sensitize_up_down(selection, view);
    }
}

static void dialog_add_order_selector (GtkWidget *dlg, GRETL_VAR *var,
				       gretl_matrix *ordvec)
{
    GtkWidget *b1, *b2;
    GtkWidget *vbox, *hbox;
    GtkWidget *bbox, *scroller;
    GtkTreeViewColumn *column;
    GtkCellRenderer *renderer;
    GtkTreeSelection *select;
    GtkListStore *store;
    GtkWidget *view, *lbl;
    GtkTreeIter iter;
    const char *vname;
    int i, j, v;

    vbox = gtk_dialog_get_content_area(GTK_DIALOG(dlg));

    hbox = gtk_hbox_new(FALSE, 5);
    lbl = gtk_label_new(_("Cholesky ordering:"));
    gtk_box_pack_start(GTK_BOX(hbox), lbl, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);

    store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
    view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
    g_object_set_data(G_OBJECT(view), "ordvec", ordvec);

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

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

    gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);

    for (i=0; i<var->neqns; i++) {
	j = gretl_vector_get(ordvec, i);
	v = var->ylist[j+1];
	vname = dataset->varname[v];
	gtk_list_store_append(store, &iter);
	gtk_list_store_set(store, &iter, 0, vname,
			   1, j, -1);
    }

    select = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
    gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
    gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
    gtk_tree_selection_select_iter(select, &iter);

    gtk_widget_set_size_request(view, 140 * gui_scale, -1);

    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);

#if GTK_MAJOR_VERSION >= 3
    gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(scroller),
					      140 * gui_scale);
#endif

    bbox = gtk_vbox_new(FALSE, 5);
    b1 = up_down_button(1);
    b2 = up_down_button(0);
    gtk_box_pack_start(GTK_BOX(bbox), b1, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(bbox), b2, FALSE, FALSE, 5);
    g_signal_connect(G_OBJECT(b1), "clicked",
		     G_CALLBACK(shift_var_up), view);
    g_signal_connect(G_OBJECT(b2), "clicked",
		     G_CALLBACK(shift_var_down), view);

    /* FIXME this is ignored in GTK3 ? */
    gtk_widget_set_sensitive(b1, FALSE);
    g_object_set_data(G_OBJECT(view), "up-button", b1);
    g_object_set_data(G_OBJECT(view), "down-button", b2);
    g_signal_connect(G_OBJECT(select), "changed",
		     G_CALLBACK(sensitize_up_down), view);

    hbox = gtk_hbox_new(FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), scroller, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), bbox, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);

    g_object_unref(store);
}

static int
impulse_response_setup (GRETL_VAR *var, gretl_matrix *ordvec, int *horizon,
			int *bootstrap, double *alpha, int *piters,
			gretlopt *gopt, GtkWidget *parent)
{
    gchar *title;
    int h = default_VAR_horizon(dataset);
    const char *impulse_opts[] = {
	N_("include bootstrap confidence interval")
    };
    static int active[] = {0};
    GtkWidget *dlg;
    double conf = 1 - *alpha;
    int iters = *piters;
    int resp = -1;

    title = g_strdup_printf("gretl: %s", _("impulse responses"));
    dlg = build_checks_dialog(title, NULL,
			      impulse_opts, 1, active, 0, 0, /* check */
			      0, NULL, /* no radios */
			      &h, _("forecast horizon (periods):"),
			      2, dataset->n / 2, IRF_BOOT,
			      parent, &resp);
    g_free(title);

    if (dlg == NULL) {
	return -1;
    }

    dialog_add_confidence_selector(dlg, &conf, gopt);

    if (iters == 0) {
	iters = libset_get_int(BOOT_ITERS);
    }
    dialog_add_iters_spinner(dlg, &iters);

    if (ordvec != NULL) {
	dialog_add_order_selector(dlg, var, ordvec);
    }
    gtk_widget_show_all(dlg);

    if (resp < 0) {
	/* canceled */
	*horizon = 0;
    } else {
	*horizon = h;
	*bootstrap = (active[0] > 0);
	if (*bootstrap) {
	    *alpha = 1 - conf;
	    *piters = iters;
	}
    }

    return resp;
}

static int FEVD_setup (GRETL_VAR *var, gretl_matrix *ordvec,
		       int *horizon, gretlopt *opt,
		       GtkWidget *parent)
{
    const char *opts[] = {
	N_("stacked bar graph"),
	N_("line graph"),
    };
    gchar *title;
    int h = default_VAR_horizon(dataset);
    GtkWidget *dlg;
    int lines = 0;
    int resp = -1;

    title = g_strdup_printf("gretl: %s", _("Forecast variance decomposition"));

    dlg = build_checks_dialog(title, NULL, opts,
			      0, NULL, 0, 0, /* no checks */
			      2, &lines, /* two radio buttons */
			      &h, _("forecast horizon (periods):"),
			      2, dataset->n / 2, 0,
			      parent, &resp);

    g_free(title);

    if (dlg == NULL) {
	return -1;
    }

    if (ordvec != NULL) {
	dialog_add_order_selector(dlg, var, ordvec);
    }

    gtk_widget_show_all(dlg);

    if (resp < 0) {
	/* cancelled */
	*horizon = 0;
    } else {
	*horizon = h;
	if (!lines) {
	    *opt = OPT_H;
	}
    }

    return resp;
}

static void impulse_params_from_action (GtkAction *action,
					int *targ,
					int *shock)
{
    const gchar *s = gtk_action_get_name(action);

    sscanf(s, "Imp:%d:%d", targ, shock);
}

static void FEVD_param_from_action (GtkAction *action,
				    int *targ)
{
    const gchar *s = gtk_action_get_name(action);

    sscanf(s, "FEVD:%d", targ);
}

static int ordvec_default (gretl_matrix *v)
{
    int i, n = gretl_vector_get_length(v);

    for (i=0; i<n; i++) {
	if (v->val[i] != i) {
	    return 0;
	}
    }

    return 1;
}

static gretl_matrix *cholesky_order_vector (GRETL_VAR *var)
{
    gretl_matrix *v = NULL;

    if (var->ord != NULL) {
	v = gretl_matrix_copy(var->ord);
    } else if (var->neqns > 1) {
	int i;

	v = gretl_vector_alloc(var->neqns);
	if (v != NULL) {
	    for (i=0; i<var->neqns; i++) {
		v->val[i] = i;
	    }
	}
    }

    return v;
}

static void FEVD_plot_call (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    GRETL_VAR *var = (GRETL_VAR *) vwin->data;
    int targ, horizon;
    gretl_matrix *ordvec = NULL;
    gretlopt opt = OPT_NONE;
    int resp, err;

    FEVD_param_from_action(action, &targ);
    ordvec = cholesky_order_vector(var);

    resp = FEVD_setup(var, ordvec, &horizon, &opt, vwin->main);

    if (resp < 0) {
	/* canceled */
	gretl_matrix_free(ordvec);
	return;
    }

    if (ordvec != NULL) {
	if (ordvec_default(ordvec)) {
	    gretl_matrix_free(ordvec);
	    gretl_VAR_set_ordering(var, NULL);
	} else {
	    gretl_VAR_set_ordering(var, ordvec);
	}
    }

    err = gretl_VAR_plot_FEVD(var, targ, horizon, dataset, opt);
    gui_graph_handler(err);
}

static void impulse_plot_call (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    GRETL_VAR *var = (GRETL_VAR *) vwin->data;
    int horizon, bootstrap;
    gint shock, targ;
    static double alpha = 0.10;
    static gretlopt gopt = OPT_NONE;
    static int iters = 0;
    gretl_matrix *ordvec = NULL;
    double this_alpha = 0;
    int save_iters;
    int resp, err;

    impulse_params_from_action(action, &targ, &shock);
    ordvec = cholesky_order_vector(var);
    save_iters = libset_get_int(BOOT_ITERS);

    resp = impulse_response_setup(var, ordvec, &horizon, &bootstrap,
				  &alpha, &iters, &gopt, vwin->main);

    if (resp < 0) {
	/* canceled */
	gretl_matrix_free(ordvec);
	return;
    }

    if (bootstrap) {
	this_alpha = alpha;
    }

    if (ordvec != NULL) {
	if (ordvec_default(ordvec)) {
	    gretl_matrix_free(ordvec);
	    gretl_VAR_set_ordering(var, NULL);
	} else {
	    gretl_VAR_set_ordering(var, ordvec);
	}
    }

    if (iters != save_iters) {
	libset_set_int(BOOT_ITERS, iters);
    }
    err = gretl_VAR_plot_impulse_response(var, targ, shock,
					  horizon, this_alpha,
					  dataset, gopt);
    if (iters != save_iters) {
	libset_set_int(BOOT_ITERS, save_iters);
    }

    gui_graph_handler(err);
}

static void multiple_irf_plot_call (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    GRETL_VAR *var = (GRETL_VAR *) vwin->data;
    int horizon, bootstrap;
    static double alpha = 0.10;
    static gretlopt gopt = OPT_NONE;
    static int iters = 0;
    gretl_matrix *ordvec = NULL;
    double this_alpha = 0;
    int save_iters;
    int resp, err;

    ordvec = cholesky_order_vector(var);
    save_iters = libset_get_int(BOOT_ITERS);

    resp = impulse_response_setup(var, ordvec, &horizon, &bootstrap,
				  &alpha, &iters, &gopt, vwin->main);

    if (resp < 0) {
	/* canceled */
	gretl_matrix_free(ordvec);
	return;
    }

    if (bootstrap) {
	this_alpha = alpha;
    }

    if (ordvec != NULL) {
	if (ordvec_default(ordvec)) {
	    gretl_matrix_free(ordvec);
	    gretl_VAR_set_ordering(var, NULL);
	} else {
	    gretl_VAR_set_ordering(var, ordvec);
	}
    }

    if (iters != save_iters) {
	libset_set_int(BOOT_ITERS, iters);
    }
    err = gretl_VAR_plot_multiple_irf(var, horizon, this_alpha,
				      dataset, gopt);
    if (iters != save_iters) {
	libset_set_int(BOOT_ITERS, save_iters);
    }

    gui_graph_handler(err);
}

static int VAR_model_data_code (GtkAction *action)
{
    const gchar *s = gtk_action_get_name(action);

    if (!strcmp(s, "VarIrf")) {
	return VAR_IRF;
    } else if (!strcmp(s, "VarDecomp")) {
	return VAR_DECOMP;
    } else {
	return VAR_IRF;
    }
}

static void VAR_model_data_callback (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    GRETL_VAR *var = vwin->data;
    gretl_matrix *ordvec;
    GtkWidget *dlg;
    gchar *title;
    PRN *prn = NULL;
    int code, h = 0;
    int resp = -1;
    int err;

    if (var == NULL) {
	return;
    }

    code = VAR_model_data_code(action);
    h = default_VAR_horizon(dataset);
    ordvec = cholesky_order_vector(var);

    title = g_strdup_printf("gretl: %s",
			    (code == VAR_IRF)? _("impulse responses") :
			    _("variance decompositions"));

    dlg = build_checks_dialog(title, NULL,
			      NULL, 0, NULL, 0, 0, /* no check-buttons */
			      0, NULL,             /* no radios */
			      &h, _("forecast horizon (periods):"),
			      2, dataset->n / 2,
			      0, vwin->main, &resp);

    if (dlg == NULL) {
	goto bailout;
    }

    if (ordvec != NULL) {
	dialog_add_order_selector(dlg, var, ordvec);
    }

    /* blocks till response is selected */
    gtk_widget_show_all(dlg);

    if (resp < 0 || bufopen(&prn)) {
	/* cancel or fail */
	goto bailout;
    }

    if (ordvec != NULL) {
	if (ordvec_default(ordvec)) {
	    gretl_matrix_free(ordvec);
	    ordvec = NULL;
	}
	gretl_VAR_set_ordering(var, ordvec);
	ordvec = NULL;
    }

    if (code == VAR_IRF) {
	err = gretl_VAR_print_all_impulse_responses(var, dataset, h, prn);
    } else if (code == VAR_DECOMP) {
	err = gretl_VAR_print_all_fcast_decomps(var, dataset, h, prn);
    } else {
	err = 1;
    }

    if (err) {
	gui_errmsg(err);
	gretl_print_destroy(prn);
    } else {
	windata_t *viewer;

	viewer = view_buffer_with_parent(vwin, prn, 80, 400, title,
					 code, NULL);
	/* for use when printing in other formats */
	viewer->active_var = h;
    }

 bailout:

    g_free(title);
    gretl_matrix_free(ordvec);
}

static void system_forecast_callback (GtkAction *action, gpointer p)
{
    static gretlopt gopt = OPT_P;
    windata_t *vwin = (windata_t *) p;
    int ci = vwin->role;
    GRETL_VAR *var = NULL;
    equation_system *sys = NULL;
    FITRESID *fr;
    int t1, t2, t2est, yno, resp;
    int premax, pre_n, dyn_ok;
    int static_model = 0;
    gretlopt opt = OPT_NONE;
    double conf = 0.95;
    int i, err = 0;

    varnum_from_action(action, &i);

    if (ci == VAR || ci == VECM) {
	var = (GRETL_VAR *) vwin->data;
	t2est = gretl_VAR_get_t2(var);
	yno = var->ylist[i+1];
    } else if (ci == SYSTEM) {
	sys = (equation_system *) vwin->data;
	t2est = sys->t2;
	yno = sys->ylist[i+1];
	static_model = (sys->order == 0);
    } else {
	return;
    }

    t2 = dataset->n - 1;

    /* if no out-of-sample obs are available, alert the user */
    if (t2 == t2est) {
	err = out_of_sample_info(1, &t2);
	if (err) {
	    return;
	}
	t2 = dataset->n - 1;
    }

    /* max number of pre-forecast obs in "best case" */
    premax = dataset->n - 1;

    /* if there are spare obs available, default to an
       out-of-sample forecast */
    if (t2 > t2est) {
	t1 = t2est + 1;
	pre_n = t2est / 2;
	if (pre_n > 100) {
	    pre_n = 100;
	}
	dyn_ok = !static_model;
    } else {
	if (var != NULL) {
	    t1 = effective_order(var);
	} else {
	    t1 = sys->order;
	}
	pre_n = 0;
	dyn_ok = 0;
    }

    resp = forecast_dialog(t1, t1, &t1,
			   t1, t2, &t2, NULL,
			   0, premax, &pre_n,
			   dyn_ok, &gopt, &conf,
			   NULL, vwin->main);
    if (resp < 0) {
	return;
    }

    if (resp == 1) {
	opt = OPT_D;
    } else if (resp == 2) {
	opt = OPT_S;
    }

    fr = get_system_forecast(vwin->data, ci, i, t1, t2, pre_n,
			     dataset, opt, &err);

    if (err) {
	gui_errmsg(err);
    } else {
	char obs1[OBSLEN];
	char obs2[OBSLEN];
	PRN *prn;

	ntolabel(obs1, t1, dataset);
	ntolabel(obs2, t2, dataset);
	lib_command_sprintf("fcast %s %s %s%s", obs1, obs2, dataset->varname[yno],
			    print_flags(opt, FCAST));
	record_command_verbatim();

	if (bufopen(&prn)) {
	    return;
	}

	fr->alpha = 1 - conf;
	err = text_print_forecast(fr, dataset, gopt, prn);
	gui_graph_handler(err);

	view_buffer(prn, (fr->sderr == NULL)? 50 : 78, 400,
		    _("gretl: forecasts"), FCAST, fr);
    }
}

enum {
    SYS_AUTOCORR_TEST,
    SYS_ARCH_TEST,
    SYS_NORMALITY_TEST,
    SYS_RESTRICT
};

static int sys_test_code (GtkAction *action)
{
    const gchar *s = gtk_action_get_name(action);

    if (!strcmp(s, "autocorr")) {
	return SYS_AUTOCORR_TEST;
    } else if (!strcmp(s, "ARCH")) {
	return SYS_ARCH_TEST;
    } else if (!strcmp(s, "normtest")) {
	return SYS_NORMALITY_TEST;
    } else if (!strcmp(s, "restrict")) {
	return SYS_RESTRICT;
    } else {
	return SYS_NORMALITY_TEST;
    }
}

static void system_test_call (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    GRETL_VAR *var = NULL;
    equation_system *sys = NULL;
    gchar *title = NULL;
    gchar *cstr = NULL;
    PRN *prn;
    int code, order = 0;
    int err = 0;

    if (bufopen(&prn)) {
	return;
    }

    code = sys_test_code(action);

    if (vwin->role == SYSTEM) {
	sys = (equation_system *) vwin->data;
    } else {
	var = (GRETL_VAR *) vwin->data;
    }

    if (code == SYS_AUTOCORR_TEST || code == SYS_ARCH_TEST) {
	int resp;

	order = default_lag_order(dataset);
	resp = spin_dialog((code == SYS_AUTOCORR_TEST)?
			   _("gretl: autocorrelation") :
			   _("gretl: ARCH test"), NULL,
			   &order, _("Lag order for test:"),
			   1, dataset->n / 2, 0, vwin->main);
	if (canceled(resp)) {
	    gretl_print_destroy(prn);
	    return;
	}
    }

    if (code == SYS_AUTOCORR_TEST) {
	title = g_strdup(_("gretl: autocorrelation"));
	cstr = g_strdup_printf("modtest %d --autocorr", order);
	if (var != NULL) {
	    err = gretl_VAR_autocorrelation_test(var, order,
						 dataset,
						 OPT_NONE,
						 prn);
	} else {
	    err = system_autocorrelation_test(sys, order, OPT_NONE, prn);
	}
    } else if (code == SYS_ARCH_TEST) {
	title = g_strdup(_("gretl: ARCH test"));
	cstr = g_strdup_printf("modtest %d --arch", order);
	if (var != NULL) {
	    err = gretl_VAR_arch_test(var, order, dataset, OPT_NONE, prn);
	} else {
	    err = system_arch_test(sys, order, OPT_NONE, prn);
	}
    } else if (code == SYS_NORMALITY_TEST) {
	title = g_strdup_printf("gretl: %s", _("Test for normality of residual"));
	cstr = g_strdup("modtest --normality");
	if (var != NULL) {
	    err = gretl_VAR_normality_test(var, OPT_NONE, prn);
	} else {
	    err = system_normality_test(sys, OPT_NONE, prn);
	}
    } else {
	err = 1;
    }

    if (err) {
	gui_errmsg(err);
	gretl_print_destroy(prn);
    } else {
	add_command_to_stack(cstr, 0);
	view_buffer(prn, 78, 400, title, PRINT, NULL);
    }

    g_free(title);
    g_free(cstr);
}

static void VAR_roots_plot_call (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    GRETL_VAR *var = (GRETL_VAR *) vwin->data;
    int err;

    err = gretl_VAR_roots_plot(var);
    gui_graph_handler(err);
}

static void combined_EC_plot_call (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    GRETL_VAR *var = (GRETL_VAR *) vwin->data;
    int err;

    err = gretl_VECM_combined_EC_plot(var, dataset);
    gui_graph_handler(err);
}

static int sys_ci_from_action (GtkAction *action, int *eqn)
{
    const gchar *s = gtk_action_get_name(action);
    char cmdword[9];

    if (eqn != NULL) {
	sscanf(s, "residplot_%d", eqn);
    }
    sscanf(s, "%*s %8s", cmdword);
    return gretl_command_number(cmdword);
}

static void system_resid_plot_call (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    int ci, eqn = 0;
    int err;

    ci = sys_ci_from_action(action, &eqn);
    err = gretl_system_residual_plot(vwin->data, ci, eqn, dataset);
    gui_graph_handler(err);
}

static void system_resid_mplot_call (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    int ci = sys_ci_from_action(action, NULL);
    int err;

    err = gretl_system_residual_mplot(vwin->data, ci, dataset);
    gui_graph_handler(err);
}

static const char *VECM_matrix_name (int idx)
{
    if (idx == M_JALPHA) {
	return "alpha";
    } else if (idx == M_JBETA) {
	return "beta";
    } else if (idx == M_JVBETA) {
	return "var(beta)";
    } else {
	/* not registered! */
	return NULL;
    }
}

static int get_VECM_matrix_idx (GtkAction *action)
{
    const gchar *aname = gtk_action_get_name(action);
    int idx;

    sscanf(aname, "matrix %d", &idx);
    return idx;
}

static void VECM_add_matrix (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    GRETL_VAR *var = (GRETL_VAR *) vwin->data;
    gretl_matrix *m = NULL;
    char vname[VNAMELEN];
    const char *mname;
    gchar *blurb = NULL;
    int vecid = gretl_VECM_id(var);
    int idx, resp, show = 1;
    int err = 0;

    idx = get_VECM_matrix_idx(action);
    mname = VECM_matrix_name(idx);
    if (mname == NULL) {
	/* internal error! */
	gui_errmsg(E_DATA);
	return;
    }

    if (!strcmp(mname, "var(beta)")) {
	sprintf(vname, "jvbeta_%d", vecid);
    } else {
	sprintf(vname, "j%s_%d", mname, vecid);
    }
    blurb = g_strdup_printf("%s (%s) from VECM %d\n"
			    "Name (max. %d characters):",
			    mname, gretl_type_get_name(GRETL_TYPE_MATRIX),
			    vecid, VNAMELEN - 1);
    resp = object_name_entry_dialog(vname, GRETL_TYPE_MATRIX, blurb,
				    &show, vwin->main);
    g_free(blurb);
    if (resp < 0) {
	/* canceled */
	return;
    }

    m = gretl_VAR_get_matrix(var, idx, &err);
    if (!err) {
	err = user_var_add_or_replace(vname, GRETL_TYPE_MATRIX, m);
    }
    if (err) {
	gui_errmsg(err);
    } else if (show) {
	view_session();
    }
}

static void add_system_menu_items (windata_t *vwin, int ci)
{
    GtkActionEntry item;
    const gchar *top = "/menubar";
    const gchar *tests = "/menubar/Tests";
    const gchar *save = "/menubar/Save";
    const gchar *graphs = "/menubar/Graphs";
    const gchar *analysis = "/menubar/Analysis";
    GRETL_VAR *var = NULL;
    equation_system *sys = NULL;
    int neqns, nfc, vtarg, vshock;
    char tmp[VNAMELEN2], istr[VNAMELEN];
    char maj[128], min[64];
    const char *cmdword;
    int i, j;

    if (ci == SYSTEM) {
	sys = (equation_system *) vwin->data;
	neqns = sys->neqns;
	nfc = sys->neqns + sys->nidents;
    } else {
	var = (GRETL_VAR *) vwin->data;
	nfc = neqns = gretl_VAR_get_n_equations(var);
    }

    cmdword = gretl_command_word(ci);
    action_entry_init(&item);

    /* FIXME: the following two tests should really be multivariate */

    if (dataset_is_time_series(dataset)) {
	/* univariate autocorrelation tests */
	item.name = "autocorr";
	item.label = N_("_Autocorrelation");
	item.callback = G_CALLBACK(system_test_call);
	vwin_menu_add_item(vwin, tests, &item);

	/* univariate ARCH tests */
	item.name = "ARCH";
	item.label = N_("A_RCH");
	vwin_menu_add_item(vwin, tests, &item);
    }

    /* multivariate normality test */
    item.name = "normtest";
    item.label = N_("_Normality of residuals");
    item.callback = G_CALLBACK(system_test_call);
    vwin_menu_add_item(vwin, tests, &item);

    if (ci == VECM || ci == SYSTEM) {
	/* linear restrictions */
	item.name = "restrict";
	item.label = N_("Linear restrictions");
	item.callback = G_CALLBACK(gretl_callback);
	vwin_menu_add_item(vwin, tests, &item);
    } else if (ci == VAR) {
	/* regular VAR: omit exogenous variables test */
	if (gretl_VAR_get_exo_list(var) != NULL) {
	    item.name = "VarOmit";
	    item.label = N_("Omit exogenous variables...");
	    item.callback = G_CALLBACK(selector_callback);
	    vwin_menu_add_item(vwin, tests, &item);
	}
	if (var->detflags & DET_TREND) {
	    item.name = "VarOmitTrend";
	    item.label = N_("Omit time trend");
	    item.callback = G_CALLBACK(VAR_omit_auto);
	    vwin_menu_add_item(vwin, tests, &item);
	}
	if (var->detflags & DET_SEAS) {
	    item.name = "VarOmitSeas";
	    item.label = N_("Omit seasonal dummies");
	    item.callback = G_CALLBACK(VAR_omit_auto);
	    vwin_menu_add_item(vwin, tests, &item);
	}
    }

    /* Save residuals */
    for (i=0; i<neqns; i++) {
	sprintf(istr, "resid %d", i);
	sprintf(maj, "%s %d", _("Residuals from equation"), i + 1);
	item.name = istr;
	item.label = maj;
	item.callback = G_CALLBACK(add_system_resid);
	vwin_menu_add_item(vwin, save, &item);
    }

    /* Display residual matrix */
    item.name = "uhat";
    item.label = N_("Display residuals, all equations");
    item.callback = G_CALLBACK(system_data_callback);
    vwin_menu_add_item(vwin, analysis, &item);

    if (ci == SYSTEM) {
	/* Display fitted values matrix */
	item.name = "yhat";
	item.label = N_("Display fitted values, all equations");
	vwin_menu_add_item(vwin, analysis, &item);
    }

    if (neqns > 1) {
	/* Display VCV matrix */
	item.name = "sigma";
	item.label = N_("Cross-equation covariance matrix");
	vwin_menu_add_item(vwin, analysis, &item);
    }

    if (ci == VAR || ci == VECM) {
	/* impulse response printout */
	item.name = "VarIrf";
	item.label = N_("Impulse responses");
	item.callback = G_CALLBACK(VAR_model_data_callback);
	vwin_menu_add_item(vwin, analysis, &item);

	/* variance decomp printout */
	item.name = "VarDecomp";
	item.label = N_("Forecast variance decomposition");
	vwin_menu_add_item(vwin, analysis, &item);
    }

    /* Residual plots */

    action_entry_init(&item);
    item.name = "ResidsMenu";
    item.label = _("Residuals");
    vwin_menu_add_menu(vwin, graphs, &item);

    if (neqns > 1) {
	/* combined residual plot */
	sprintf(min, "comboresid %s", cmdword);
	item.name = min;
	item.label = N_("Combined plot");
	item.callback = G_CALLBACK(system_resid_plot_call);
	vwin_menu_add_item(vwin, "/menubar/Graphs/ResidsMenu", &item);
    }

    if (neqns > 1 && neqns <= 6) {
	/* multiple residual plots in one frame */
	sprintf(min, "multiresid %s", cmdword);
	item.name = min;
	item.label = N_("Multiple plots");
	item.callback = G_CALLBACK(system_resid_mplot_call);
	vwin_menu_add_item(vwin, "/menubar/Graphs/ResidsMenu", &item);
    }

    item.callback = G_CALLBACK(system_resid_plot_call);

    for (i=0; i<neqns; i++) {
	sprintf(min, "residplot_%d %s", i+1, cmdword);
	if (var != NULL) {
	    vtarg = gretl_VAR_get_variable_number(var, i);
	} else {
	    vtarg = sys->ylist[i+1];
	}
	double_underscores(tmp, dataset->varname[vtarg]);
	strcpy(maj, tmp);
	item.name = min;
	item.label = maj;
	vwin_menu_add_item(vwin, "/menubar/Graphs/ResidsMenu", &item);
    }

    /* end residual plots */

    if (ci == VECM) {
	int r = gretl_VECM_rank(var);

	if (r == 1) {
	    item.name = "ecplot";
	    item.label = N_("EC plot");
	    item.callback = G_CALLBACK(combined_EC_plot_call);
	    vwin_menu_add_item(vwin, graphs, &item);
	} else {
	    item.name = "ecplot";
	    item.label = N_("Combined EC plot");
	    item.callback = G_CALLBACK(combined_EC_plot_call);
	    vwin_menu_add_item(vwin, graphs, &item);
	}
    }

    if (ci != SYSTEM) {
	/* VAR inverse roots */
	item.name = "VarRoots";
	item.label = N_("VAR inverse roots");
	item.callback = G_CALLBACK(VAR_roots_plot_call);
	vwin_menu_add_item(vwin, graphs, &item);
    }

    if (ci != SYSTEM && neqns > 1 && neqns <= 4) {
	/* Multiple IRFs */
	item.name = "MultiIrf";
	item.label = N_("Impulse responses (combined)");
	item.callback = G_CALLBACK(multiple_irf_plot_call);
	vwin_menu_add_item(vwin, graphs, &item);
    }

    for (i=0; i<nfc; i++) {
	char newpath[64];
	int dv;

	/* forecast items */
	if (var != NULL) {
	    dv = gretl_VAR_get_variable_number(var, i);
	} else {
	    dv = sys->ylist[i+1];
	}
	double_underscores(tmp, dataset->varname[dv]);
	sprintf(istr, "fcast %d", i);
	item.name = istr;
	item.label = tmp;
	item.callback = G_CALLBACK(system_forecast_callback);
	vwin_menu_add_item(vwin, "/menubar/Analysis/Forecasts", &item);

	if (var == NULL) {
	    continue;
	}

	/* impulse response plots: make menu for target */
	vtarg = gretl_VAR_get_variable_number(var, i);
	double_underscores(tmp, dataset->varname[vtarg]);
	sprintf(istr, "targ_%d", i);
	sprintf(maj, _("Response of %s"), tmp);
	item.name = istr;
	item.label = maj;
	item.callback = NULL;
	vwin_menu_add_menu(vwin, graphs, &item);

	/* path under which to add shocks */
	sprintf(newpath, "/menubar/Graphs/targ_%d", i);

	for (j=0; j<neqns; j++) {
	    /* impulse responses: subitems for shocks */
	    vshock = gretl_VAR_get_variable_number(var, j);
	    double_underscores(tmp, dataset->varname[vshock]);
	    sprintf(istr, "Imp:%d:%d", i, j);
	    sprintf(min, _("to %s"), tmp);
	    item.name = istr;
	    item.label = min;
	    item.callback = G_CALLBACK(impulse_plot_call);
	    vwin_menu_add_item(vwin, newpath, &item);
	}
    }

    if (var != NULL) {
	item.name = "FEVD";
	item.label = _("Forecast variance decomposition");
	item.callback = NULL;
	vwin_menu_add_menu(vwin, graphs, &item);
	for (j=0; j<neqns; j++) {
	    /* FEVD graphs per equation */
	    vtarg = gretl_VAR_get_variable_number(var, j);
	    double_underscores(tmp, dataset->varname[vtarg]);
	    sprintf(istr, "FEVD:%d", j);
	    item.name = istr;
	    item.label = tmp;
	    item.callback = G_CALLBACK(FEVD_plot_call);
	    vwin_menu_add_item(vwin, "/menubar/Graphs/FEVD", &item);
	}
    }

    if (ci == VECM) {
	/* saving things specific to VECMs */
	int mtypes[] = {M_JALPHA, M_JBETA, M_JVBETA};

	/* save error correction terms as series */
	for (i=0; i<jrank(var); i++) {
	    sprintf(istr, "EC %d", i);
	    sprintf(maj, "%s %d", _("EC term"), i+1);
	    item.name = istr;
	    item.label = maj;
	    item.callback = G_CALLBACK(VECM_add_EC_data);
	    vwin_menu_add_item(vwin, save, &item);
	}
	/* save relevant matrices */
	for (i=0; i<G_N_ELEMENTS(mtypes); i++) {
	    sprintf(istr, "matrix %d", mtypes[i]);
	    item.name = istr;
	    item.label = VECM_matrix_name(mtypes[i]);
	    item.callback = G_CALLBACK(VECM_add_matrix);
	    vwin_menu_add_item(vwin, save, &item);
	}
    }

    maybe_add_packages_to_model_menus(vwin);

    if (latex_is_ok()) {
	int n = G_N_ELEMENTS(sys_tex_items);

	vwin_menu_add_menu(vwin, top, &sys_tex_items[0]);
	vwin_menu_add_items(vwin, "/menubar/LaTeX",
			    sys_tex_items + 1, n - 1);
    }
}

static int vector_suitable_for_series (const gretl_matrix *m)
{
    if (m->cols == 1 && gretl_matrix_is_dated(m)) {
	int t2 = gretl_matrix_get_t2(m);

	/* the column vector can be "cast" to series
	   without data loss */
	return t2 < dataset->n;
    } else {
	return 0;
    }
}

void save_bundled_item_call (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    gretl_bundle *bundle = vwin->data;
    const gchar *key = gtk_action_get_name(action);
    const char *note;
    GretlType type;
    void *val;
    int size = 0;
    int err = 0;

    val = gretl_bundle_get_data(bundle, key, &type, &size, &err);
    if (err) {
	gui_errmsg(err);
	return;
    }

    note = gretl_bundle_get_note(bundle, key);

    if (type == GRETL_TYPE_SERIES && size <= dataset->n) {
	const double *x = (double *) val;

	save_bundled_series(x, 0, size - 1, key, note, vwin);
    } else if (type == GRETL_TYPE_MATRIX &&
	       vector_suitable_for_series((gretl_matrix *) val)) {
	const gretl_matrix *m = val;
	int t1 = gretl_matrix_get_t1(m);
	int t2 = gretl_matrix_get_t2(m);

	save_bundled_series(m->val, t1, t2, key, note, vwin);
    } else {
	char vname[VNAMELEN];
	gchar *blurb;
	int resp, show = 1;

	*vname = '\0';
	strncat(vname, key, VNAMELEN - 1);

	blurb = g_strdup_printf("%s (%s) from bundle\n"
				"Name (max. %d characters):",
				key, gretl_type_get_name(type),
				VNAMELEN - 1);
	resp = object_name_entry_dialog(vname, type, blurb,
					&show, vwin->main);
	g_free(blurb);

	if (resp < 0) {
	    /* canceled */
	    return;
	}

	if (gretl_is_scalar_type(type)) {
	    double *xp = malloc(sizeof *xp);

	    if (type == GRETL_TYPE_INT || type == GRETL_TYPE_BOOL) {
		*xp = *(int *) val;
	    } else if (type == GRETL_TYPE_UNSIGNED) {
		*xp = *(unsigned *) val;
	    } else {
		*xp = *(double *) val;
	    }
	    err = user_var_add_or_replace(vname, GRETL_TYPE_DOUBLE, xp);
	} else if (type == GRETL_TYPE_MATRIX) {
	    gretl_matrix *orig = (gretl_matrix *) val;
	    gretl_matrix *m = gretl_matrix_copy(orig);

	    if (m == NULL) {
		err = E_ALLOC;
	    } else {
		err = user_var_add_or_replace(vname, GRETL_TYPE_MATRIX, m);
	    }
	} else if (type == GRETL_TYPE_SERIES) {
	    double *x = (double *) val;
	    gretl_matrix *m;

	    m = gretl_vector_from_array(x, size, GRETL_MOD_NONE);
	    err = user_var_add_or_replace(vname, GRETL_TYPE_MATRIX, m);
	} else if (type == GRETL_TYPE_STRING) {
	    char *s = gretl_strdup((char *) val);

	    err = user_var_add_or_replace(vname, GRETL_TYPE_STRING, s);
	} else if (type == GRETL_TYPE_BUNDLE) {
	    gretl_bundle *orig = (gretl_bundle *) val;
	    gretl_bundle *b = gretl_bundle_copy(orig, &err);

	    if (!err) {
		err = user_var_add_or_replace(vname, GRETL_TYPE_BUNDLE, b);
	    }
	} else if (type == GRETL_TYPE_ARRAY) {
	    gretl_array *orig = (gretl_array *) val;
	    gretl_array *a = gretl_array_copy(orig, &err);

	    if (!err) {
		err = user_var_add_or_replace(vname, gretl_array_get_type(a), a);
	    }
	}

	if (show && !err) {
	    if (gretl_is_scalar_type(type)) {
		edit_scalars();
	    } else {
		view_session();
	    }
	}
    }

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

static void bundle_plot_call (GtkAction *action, gpointer p)
{
    windata_t *vwin = (windata_t *) p;
    gretl_bundle *bundle = vwin->data;
    const gchar *aname = gtk_action_get_name(action);

    exec_bundle_special_function(bundle, BUNDLE_PLOT,
				 aname, vwin->main);
}

static void add_blist_item_to_menu (gpointer listitem,
				    gpointer p)
{
    bundled_item *bi = listitem;
    gpointer data;
    const char *key;
    GtkAction *action;
    GtkWidget *item;
    GtkWidget *menu = p;
    gchar *keystr, *label = NULL;
    const char *typestr = "?";
    const char *note;
    GretlType type;
    int scalar = 0;
    int r = 0, c = 0;
    int size = 0;

    key = bundled_item_get_key(bi);
    data = bundled_item_get_data(bi, &type, &size);

    if (data == NULL || type == GRETL_TYPE_STRING) {
	return;
    }

    if (type == GRETL_TYPE_MATRIX) {
	gretl_matrix *m = data;

	if (gretl_is_null_matrix(m)) {
	    return;
	} else if (vector_suitable_for_series(m)) {
	    type = GRETL_TYPE_SERIES;
	} else {
	    r = m->rows;
	    c = m->cols;
	}
    } else if (type == GRETL_TYPE_SERIES && size > dataset->n) {
	type = GRETL_TYPE_MATRIX;
	r = size;
	c = 1;
    } else if (gretl_is_scalar_type(type)) {
	scalar = 1;
    }

    typestr = gretl_type_get_name(type);
    note = bundled_item_get_note(bi);
    keystr = double_underscores_new((gchar *) key);

    if (r > 0 && c > 0) {
	if (note != NULL) {
	    label = g_strdup_printf("%s (%s: %s, %d x %d)", keystr,
				    typestr, note, r, c);
	} else {
	    label = g_strdup_printf("%s (%s, %d x %d)", keystr,
				    typestr, r, c);
	}
    } else if (scalar) {
	if (type == GRETL_TYPE_DOUBLE) {
	    label = g_strdup_printf("%s (scalar: %g)", keystr,
				    *(double *) data);
	} else if (type == GRETL_TYPE_INT || type == GRETL_TYPE_BOOL) {
	    label = g_strdup_printf("%s (scalar: %d)", keystr,
				    *(int *) data);
	} else if (type == GRETL_TYPE_UNSIGNED) {
	    label = g_strdup_printf("%s (scalar: %d)", keystr,
				    *(unsigned int *) data);
	}
    } else if (note != NULL) {
	label = g_strdup_printf("%s (%s: %s)", keystr, typestr, note);
    } else {
	label = g_strdup_printf("%s (%s)", keystr, typestr);
    }

    g_free(keystr);

    action = gtk_action_new(key, label, NULL, NULL);
    g_signal_connect(G_OBJECT(action), "activate",
		     G_CALLBACK(save_bundled_item_call),
		     g_object_get_data(G_OBJECT(menu), "vwin"));

    item = gtk_action_create_menu_item(action);
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);

    g_free(label);
}

static void add_kalman_items_to_menu (GtkWidget *menu,
				      kalman *K)
{
    GtkAction *action;
    GtkWidget *item;
    gchar *label;
    char **S;
    int i, ns = 0;

    S = kalman_bundle_get_matrix_names(K, &ns);

    for (i=0; i<ns; i++) {
	label = g_strdup_printf("%s (matrix)", S[i]);
	action = gtk_action_new(S[i], label, NULL, NULL);
	g_signal_connect(G_OBJECT(action), "activate",
			 G_CALLBACK(save_bundled_item_call),
			 g_object_get_data(G_OBJECT(menu), "vwin"));
	item = gtk_action_create_menu_item(action);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
	g_free(label);
    }

    strings_array_free(S, ns);
    ns = 0;

    S = kalman_bundle_get_scalar_names(K, &ns);

    for (i=0; i<ns; i++) {
	if (S[i] == NULL) {
	    /* just in case */
	    break;
	}
	label = g_strdup_printf("%s (scalar)", S[i]);
	action = gtk_action_new(S[i], label, NULL, NULL);
	g_signal_connect(G_OBJECT(action), "activate",
			 G_CALLBACK(save_bundled_item_call),
			 g_object_get_data(G_OBJECT(menu), "vwin"));
	item = gtk_action_create_menu_item(action);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
	g_free(label);
    }

    strings_array_free(S, ns);
}

static void check_for_saveable (gpointer key,
				gpointer value,
				gpointer data)
{
    int *pn = (int *) data;
    GretlType type;
    void *val;

    val = bundled_item_get_data((bundled_item *) value, &type, NULL);
    if (val == NULL) {
	/* "can't happen" */
	return;
    }
    if (type == GRETL_TYPE_STRING) {
	/* not useful in GUI? */
	return;
    }
    if (type == GRETL_TYPE_MATRIX) {
	gretl_matrix *m = val;

	if (gretl_is_null_matrix(m)) {
	    /* no use to man nor beast? */
	    return;
	}
    }

    *pn += 1;
}

static int any_saveable_content (gretl_bundle *b)
{
    int n = gretl_bundle_get_n_keys(b);

    if (n > 0) {
	GHashTable *ht = (GHashTable *) gretl_bundle_get_content(b);

	n = 0;
	g_hash_table_foreach(ht, check_for_saveable, &n);
    }

    return n;
}

GtkWidget *make_bundle_content_menu (windata_t *vwin)
{
    gretl_bundle *bundle = vwin->data;
    GtkWidget *menu = NULL;

    if (gretl_bundle_get_type(bundle) == BUNDLE_KALMAN) {
	kalman *K = gretl_bundle_get_private_data(bundle);

	if (K != NULL) {
	    menu = gtk_menu_new();
	    g_object_set_data(G_OBJECT(menu), "vwin", vwin);
	    add_kalman_items_to_menu(menu, K);
	}
    }

    if (any_saveable_content(bundle)) {
	GList *blist = gretl_bundle_get_sorted_items(bundle);

	if (menu == NULL) {
	    menu = gtk_menu_new();
	    g_object_set_data(G_OBJECT(menu), "vwin", vwin);
	}
	g_list_foreach(blist, add_blist_item_to_menu, menu);
	g_list_free(blist);
    }

    return menu;
}

GtkWidget *make_bundle_plot_menu (windata_t *vwin)
{
    gretl_bundle *bundle = vwin->data;
    gchar *plotfunc;
    GtkWidget *menu = NULL;

    plotfunc = get_bundle_special_function(bundle, BUNDLE_PLOT);

    if (plotfunc != NULL) {
	ufunc *fun = NULL;
	const char **S = NULL;
	int ng = 0;

	if (strcmp(plotfunc, "builtin")) {
	    fun = get_user_function_by_name(plotfunc);
	}

	if (fun != NULL) {
	    S = fn_param_value_labels(fun, 1, &ng);
	}

	if (S != NULL) {
	    /* the plotfunc has some options available */
	    GtkAction *action;
	    GtkWidget *item;
	    gchar *aname;
	    int i;

	    menu = gtk_menu_new();

	    for (i=0; i<ng; i++) {
		aname = g_strdup_printf("%s:%d", plotfunc, i);
		action = gtk_action_new(aname, S[i], NULL, NULL);
		g_signal_connect(G_OBJECT(action), "activate",
				 G_CALLBACK(bundle_plot_call),
				 vwin);
		item = gtk_action_create_menu_item(action);
		gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
		g_free(aname);
	    }
	}
	g_free(plotfunc);
    }

    return menu;
}

GtkWidget *make_bundle_save_menu (windata_t *vwin)
{
    gretl_bundle *bundle = vwin->data;
    GtkWidget *menu = gtk_menu_new();
    GtkAction *action;
    GtkWidget *item;

    action = gtk_action_new("SaveAs", _("_Save text..."),
			    NULL, NULL);
    g_signal_connect(G_OBJECT(action), "activate",
		     G_CALLBACK(model_output_save), vwin);
    item = gtk_action_create_menu_item(action);
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);

    action = gtk_action_new("SaveAsIcon", _("Save bundle to session as _icon"),
			    NULL, NULL);
    g_signal_connect(G_OBJECT(action), "activate",
		     G_CALLBACK(bundle_add_as_icon), vwin);
    item = gtk_action_create_menu_item(action);
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
    vwin_record_action(vwin, action);

    if (get_user_var_by_data(bundle)) {
	gtk_action_set_sensitive(action, FALSE);
    }

    return menu;
}

static int set_sample_from_model (void *ptr, int role)
{
    MODEL *pmod = NULL;
    int range_set = 0;
    int err = 0;

    if (role == VIEW_MODEL) {
	/* called from single-eqn model window */
	pmod = ptr;
    }

    /* first restore the full dataset */
    err = restore_full_sample(dataset, NULL);

    /* then, if the model was subsampled, restore the subsample */
    if (!err) {
	if (pmod != NULL && pmod->submask != NULL) {
	    err = restrict_sample_from_mask(pmod->submask, dataset,
					    OPT_NONE);
	    range_set = 1;
	} else {
	    /* VAR, VECM or something */
	    int t1 = 0, t2 = 0;

	    model_get_t1_t2(ptr, role, &t1, &t2);
	    if (t1 == 0 && t2 == 0) {
		err = E_DATA;
	    } else if (t1 != dataset->t1 || t2 != dataset->t2) {
		dataset->t1 = t1;
		dataset->t2 = t2;
		range_set = 1;
	    }
	}
    }

    if (err) {
	gui_errmsg(err);
    } else {
	if (range_set) {
	    char comment[64];

	    restore_sample_state(TRUE);
	    if (pmod != NULL) {
		sprintf(comment, "# restored sample from model %d\n", pmod->ID);
	    } else {
		strcpy(comment, "# restored sample from model\n");
	    }
	    add_command_to_stack(comment, 0);
	} else {
	    restore_sample_state(FALSE);
	    lib_command_strcpy("smpl --full");
	    record_command_verbatim();
	}

	mark_session_changed();
	set_sample_label(dataset);
    }

    return err;
}

/* maybe_set_sample_from_model: return TRUE if the problem situation
   (sample mismatch) is successfully handled, else FALSE.
*/

static gboolean maybe_set_sample_from_model (windata_t *vwin)
{
    const char *msg = N_("The model sample differs from the dataset sample,\n"
			 "so some menu options will be disabled.\n\n"
			 "Do you want to restore the sample on which\n"
			 "this model was estimated?");
    int resp, err = 0;

    resp = yes_no_dialog(NULL, _(msg), vwin_toplevel(vwin));

    if (resp == GRETL_NO) {
	return FALSE;
    }

    err = set_sample_from_model(vwin->data, vwin->role);

    return (err == 0);
}

static gint check_model_menu (GtkWidget *w, GdkEventButton *eb,
			      gpointer data)
{
    windata_t *vwin = (windata_t *) data;
    MODEL *pmod = vwin->data;
    GtkAction *action;
    int resampled = 0;
    int between = 0;
    gboolean s, ok = TRUE;

    if (RQ_SPECIAL_MODEL(pmod)) {
	return FALSE;
    }

    if (dataset == NULL || dataset->Z == NULL) {
	flip(vwin->ui, "/menubar/File/SaveAsIcon", FALSE);
	flip(vwin->ui, "/menubar/File/SaveAndClose", FALSE);
	flip(vwin->ui, "/menubar/Edit/Copy", FALSE);
	flip(vwin->ui, "/menubar/Tests", FALSE);
	flip(vwin->ui, "/menubar/Graphs", FALSE);
	flip(vwin->ui, "/menubar/Analysis", FALSE);
	return FALSE;
    }

    if (pmod->ci == MLE || pmod->ci == GMM ||
	pmod->ci == MPOLS || pmod->ci == BIPROBIT) {
	return FALSE;
    }

    if (model_sample_problem(pmod, dataset)) {
	resampled = (pmod->submask == RESAMPLED);
	between = gretl_is_between_model(pmod);
	ok = FALSE;
    }

    /* check current menu state */
    action = gtk_ui_manager_get_action(vwin->ui, "/menubar/Save/uhat");
    s = gtk_action_is_sensitive(action);

    if (s == ok) {
	/* no need to flip state */
	return FALSE;
    }

    if (s && !ok) {
	if (resampled) {
	    infobox(_("The model sample differs from the dataset sample,\n"
		      "so some menu options will be disabled."));
	} else if (!between) {
	    /* give option to restore model sample */
	    ok = maybe_set_sample_from_model(vwin);
	    if (ok) {
		return FALSE;
	    }
	}
    }

    flip(vwin->ui, "/menubar/Analysis/Forecasts", ok);
    flip(vwin->ui, "/menubar/Save/yhat", ok);
    flip(vwin->ui, "/menubar/Save/uhat", ok);
    flip(vwin->ui, "/menubar/Save/uhat2", ok);
    flip(vwin->ui, "/menubar/Save/NewVar", ok);

    if (resampled) {
	flip(vwin->ui, "/menubar/Tests", FALSE);
	flip(vwin->ui, "/menubar/Analysis/DisplayAFR", FALSE);
	flip(vwin->ui, "/menubar/Analysis/Bootstrap", FALSE);
	flip(vwin->ui, "/menubar/Graphs", FALSE);
    }

    if (between) {
	flip(vwin->ui, "/menubar/Edit/Revise", FALSE);
    }

    return FALSE;
}

static gint check_VAR_menu (GtkWidget *w, GdkEventButton *eb,
			    gpointer data)
{
    windata_t *vwin = (windata_t *) data;
    GtkAction *action;
    gboolean s, ok = TRUE;

    if (complex_subsampled()) {
	ok = FALSE;
    }

    action = gtk_ui_manager_get_action(vwin->ui, "/menubar/Tests");
    s = gtk_action_is_sensitive(action);

    if (s == ok) {
	/* no need to flip state */
	return FALSE;
    }

    flip(vwin->ui, "/menubar/Edit/Revise", ok);
    flip(vwin->ui, "/menubar/Tests", ok);
    flip(vwin->ui, "/menubar/Save", ok);
    flip(vwin->ui, "/menubar/Graphs", ok);
    flip(vwin->ui, "/menubar/Analysis/Forecasts", ok);
    flip(vwin->ui, "/menubar/Analysis/VarIrf", ok);
    flip(vwin->ui, "/menubar/Analysis/VarDecomp", ok);

    if (!ok) {
	warnbox(_("dataset is subsampled"));
    }

    return FALSE;
}

static int object_overwrite_ok (const char *name, GretlType t,
				GtkWidget *parent)
{
    gchar *info = name_conflict_message(name, t);
    gchar *msg = g_strdup_printf("%s\n%s", info, _("OK to overwrite it?"));
    int resp;

    resp = yes_no_dialog("gretl", msg, parent);
    g_free(info);
    g_free(msg);

    return (resp == GRETL_YES);
}

/* note: returns non-zero if the varname is not acceptable */

static int real_gui_validate_varname (const char *name,
				      GretlType t,
				      int allow_overwrite,
				      GtkWidget *parent)
{
    int i, n = strlen(name);
    char namebit[VNAMELEN];
    unsigned char c;
    int err = 0;

    *namebit = '\0';

    if (n > VNAMELEN - 1) {
	strncat(namebit, name, VNAMELEN - 1);
	errbox_printf(_("Variable name %s... is too long\n"
			"(the max is %d characters)"), namebit,
		      VNAMELEN - 1);
	err = 1;
    } else if (!(isalpha(*name))) {
	errbox_printf(_("First char of name ('%c') is bad\n"
			"(first must be alphabetical)"), *name);
	err = 1;
    } else {
	for (i=1; i<n && !err; i++) {
	    c = (unsigned char) name[i];

	    if ((!(isalpha(c)) && !(isdigit(c)) && c != '_') || c > 127) {
		errbox_printf(_("Name contains an illegal char (in place %d)\n"
				"Use only unaccented letters, digits and underscore"),
			      i + 1);
		err = 1;
	    }
	}
    }

    if (!err && t != GRETL_TYPE_NONE) {
	/* check for variable type collisions */
	GretlType t0 = gretl_type_from_name(name, dataset);

	if (t0 != GRETL_TYPE_NONE) {
	    /* there's already a variable of this name */
	    if (t == t0 && allow_overwrite) {
		/* the types agree: overwrite? */
		err = !object_overwrite_ok(name, t, parent);
	    } else {
		/* the types disgree: won't work */
		gchar *msg = name_conflict_message(name, t0);

		errbox(msg);
		g_free(msg);
		err = 1;
	    }
	}
    }

    return err;
}

/* The "gui_validate_varname" family: both functions below check the
   putative @name for legality as a gretl variable name and return
   non-zero if it's not legal. In addition, both return non-zero if
   the name is valid but belongs to an existing variable of a type
   other than @type.

   They diverge in this respect:

   gui_validate_varname_strict: unconditionally returns non-zero
   if there's an existing variable of the same name, even if it's
   of type @type.

   gui_validate_varname: checks with the user whether overwriting is
   OK in the case where a variable of type @type already exists; if
   so, it's assumed that the distinction between redefining a variable
   and adding a new variable is handled by the caller.
*/

int gui_validate_varname_strict (const char *name, GretlType type,
				 GtkWidget *parent)
{
    return real_gui_validate_varname(name, type, 0, parent);
}

int gui_validate_varname (const char *name, GretlType type,
			  GtkWidget *parent)
{
    return real_gui_validate_varname(name, type, 1, parent);
}

gint popup_menu_handler (GtkWidget *widget, GdkEventButton *event,
			 gpointer data)
{
    if (right_click(event)) {
	gtk_menu_popup(GTK_MENU(data), NULL, NULL, NULL, NULL,
		       event->button, event->time);
	return TRUE;
    }

    return FALSE;
}

void add_popup_item (const gchar *label, GtkWidget *menu,
		     GCallback callback,
		     gpointer data)
{
    GtkWidget *item;

    item = gtk_menu_item_new_with_label(label);
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
    g_signal_connect(G_OBJECT(item), "activate",
		     G_CALLBACK(callback), data);
    gtk_widget_show(item);
}

void *gui_get_plugin_function (const char *funcname)
{
    void *func;

    func = get_plugin_function(funcname);
    if (func == NULL) {
	errbox(gretl_errmsg_get());
    }

    return func;
}

char *double_underscores (char *targ, const char *src)
{
    char *p = targ;

    while (*src) {
	if (*src == '_') {
	    *p++ = '_';
	    *p++ = '_';
	} else {
	    *p++ = *src;
	}
	src++;
    }
    *p = '\0';

    return targ;
}

gchar *double_underscores_new (const char *src)
{
    const char *s = src;
    gchar *ret;
    int n = 0;

    while (*s && (s = strchr(s, '_')) != NULL) {
	n++;
	s++;
    }

    ret = g_malloc(strlen(src) + n + 1);

    return double_underscores(ret, src);
}

char *adjust_fontspec_string (char *targ, const char *src,
			      int mod)
{
    char *p, c0, c1;

    strcpy(targ, src);

    if (mod == ADD_COMMA) {
	c0 = ' ';
	c1 = ',';
    } else {
	c0 = ',';
	c1 = ' ';
    }

    p = strrchr(targ, c0);
    if (p != NULL && isdigit(p[1])) {
	*p = c1;
    }

    return targ;
}

static int got_printable_output (PRN *prn)
{
    int ret = 0;

    if (prn == NULL) {
	warnbox(_("No output was produced"));
    } else {
	const char *buf = gretl_print_get_buffer(prn);

	if (string_is_blank(buf)) {
	    warnbox(_("No output was produced"));
	    gretl_print_destroy(prn);
	} else {
	    ret = 1;
	}
    }

    return ret;
}

#ifdef G_OS_WIN32

/* MS Windows variants of functions to exec some third-party
   programs */

static void win32_run_R_sync (const char *buf, gretlopt opt)
{
    PRN *prn = NULL;
    int err;

    if (bufopen(&prn)) {
	return;
    }

    err = execute_R_buffer(buf, dataset, opt, prn);

    if (err) {
	gui_errmsg(err);
    } else {
	view_buffer(prn, 78, 350, _("gretl: script output"),
		    PRINT, NULL);
    }
}

/* win32 version */

void run_foreign_script (gchar *buf, int lang, gretlopt opt)
{
    const char *fname = NULL;
    int err;

    opt |= OPT_G;

    /* note: as things stand, the @fname we obtain here
       (composed in gretl_foreign.c) will be in the locale
       encoding, ready to pass on the Windows command line
       as in "foreign.exe fname"; this composite string is
       given to gretl_spawn() or gretl_win32_pipe_output()
       below.
    */

    err = write_gretl_foreign_script(buf, lang, opt, dataset, &fname);

    if (err) {
	gui_errmsg(err);
    } else {
	PRN *prn = NULL;
	gchar *cmd = NULL;

	if (bufopen(&prn)) {
	    return;
	}

	if (lang == LANG_OX) {
	    cmd = g_strdup_printf("\"%s\" \"%s\"", gretl_oxl_path(), fname);
	} else if (lang == LANG_PYTHON) {
	    cmd = g_strdup_printf("\"%s\" \"%s\"", gretl_python_path(), fname);
	} else if (lang == LANG_JULIA) {
	    cmd = g_strdup_printf("\"%s\" \"%s\"", gretl_julia_path(), fname);
	} else if (lang == LANG_STATA) {
	    cmd = g_strdup_printf("\"%s\" /e do \"%s\"", gretl_stata_path(), fname);
	} else if (lang == LANG_OCTAVE) {
	    cmd = g_strdup_printf("\"%s\" -q \"%s\"", gretl_octave_path(), fname);
	}

	if (lang == LANG_STATA) {
	    gchar *buf = NULL;

	    gretl_chdir(gretl_workdir());
	    remove("gretltmp.log");
	    err = gretl_spawn(cmd);

	    if (g_file_get_contents("gretltmp.log", &buf, NULL, NULL)) {
		pputs(prn, buf);
		g_free(buf);
		pputc(prn, '\n');
	    }
	} else {
	    err = gretl_win32_pipe_output(cmd, gretl_dotdir(), prn);
	}

	if (got_printable_output(prn)) {
	    /* note: this check destroys @prn on failure */
	    view_buffer(prn, 78, 350, _("gretl: script output"), PRINT, NULL);
	} else if (err) {
	    gui_errmsg(err);
	}

	g_free(cmd);
    }
}

#else /* some non-Windows functions follow */

int browser_open (const char *url)
{
# if defined(OS_OSX)
    return osx_open_url(url);
# else
    return gretl_fork("Browser", url, NULL);
# endif
}

/* Start an R session in asynchronous (interactive) mode.
   Note that there's a separate win32 function for this
   in gretlwin32.c.
*/

static void start_R_async (void)
{
    char *s0 = NULL, *s1 = NULL, *s2 = NULL;
    int n = -1;

    s0 = mymalloc(64);
    s1 = mymalloc(32);
    s2 = mymalloc(32);

    if (s0 != NULL && s1 != NULL && s2 != NULL) {
	*s0 = *s1 = *s2 = '\0';
	/* probably "xterm -e R" or similar */
	n = sscanf(Rcommand, "%63s %31s %31s", s0, s1, s2);
    }

    if (n == 0) {
	errbox(_("No command was supplied to start R"));
    } else if (n > 0) {
	char *supp1 = "--no-init-file";
	char *supp2 = "--no-restore-data";
	gchar *argv[6];
	GError *error = NULL;
	gboolean ok;
	int i = 0;

	argv[i++] = s0;
	if (n > 1) {
	    argv[i++] = s1;
	}
	if (n > 2) {
	    argv[i++] = s2;
	}
	argv[i++] = supp1;
	argv[i++] = supp2;
	argv[i++] = NULL;

	ok = g_spawn_async(NULL,
			   argv,
			   NULL,
			   G_SPAWN_SEARCH_PATH,
			   NULL,
			   NULL,
			   NULL,
			   &error);

	if (error != NULL) {
	    errbox(error->message);
	    g_error_free(error);
	} else if (!ok) {
	    gui_errmsg(E_EXTERNAL);
	    g_error_free(error);
	}
    }

    free(s0);
    free(s1);
    free(s2);
}

/* run R, Ox, etc., in synchronous (batch) mode and display the
   results in a gretl window: non-Windows variant
*/

static void run_prog_sync (char **argv, int lang)
{
    gchar *sout = NULL;
    gchar *errout = NULL;
    gint status = 0;
    GError *gerr = NULL;
    PRN *prn = NULL;

    if (lang == LANG_STATA) {
	/* control location of Stata log file */
	gretl_chdir(gretl_workdir());
	remove("gretltmp.log");
    }

    g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
		 NULL, NULL, &sout, &errout,
		 &status, &gerr);

    if (gerr != NULL) {
	errbox(gerr->message);
	g_error_free(gerr);
    } else if (status != 0) {
	if (errout != NULL && *errout != '\0') {
	    if (strlen(errout) < MAXLEN) {
		errbox(errout);
	    } else {
		bufopen(&prn);
		pputs(prn, errout);
	    }
	} else if (sout != NULL && *sout != '\0') {
	    if (strlen(sout) < MAXLEN) {
		errbox(sout);
	    } else {
		bufopen(&prn);
		pputs(prn, sout);
	    }
	} else {
	    errbox_printf("%s exited with status %d", argv[0], status);
	}
    } else if (lang == LANG_STATA) {
	/* read log file */
	gchar *buf = NULL;

	if (g_file_get_contents("gretltmp.log", &buf, NULL, NULL)) {
	    bufopen(&prn);
	    pputs(prn, buf);
	    g_free(buf);
	    pputc(prn, '\n');
	}
    } else {
	/* read good old stdout */
	if (!string_is_blank(sout)) {
	    bufopen(&prn);
	    pputs(prn, sout);
	}
    }

    if (got_printable_output(prn)) {
	view_buffer(prn, 78, 350, _("gretl: script output"), PRINT, NULL);
    }

    g_free(sout);
    g_free(errout);
}

static void run_R_sync (void)
{
    gchar *argv[] = {
	"R",
	"--no-save",
	"--no-init-file",
	"--no-restore-data",
	"--slave",
	NULL
    };

    run_prog_sync(argv, LANG_R);
}

void run_foreign_script (gchar *buf, int lang, gretlopt opt)
{
    const char *fname = NULL;
    int err;

    opt |= OPT_G;

    err = write_gretl_foreign_script(buf, lang, opt, dataset, &fname);

    if (err) {
	gui_errmsg(err);
    } else {
	gchar *argv[6];

	if (lang == LANG_OCTAVE && (opt & OPT_Y)) {
	    gretl_chdir(gretl_workdir());
	}

	if (lang == LANG_OX) {
	    argv[0] = (gchar *) gretl_oxl_path();
	    argv[1] = (gchar *) fname;
	    argv[2] = NULL;
	} else if (lang == LANG_PYTHON) {
	    argv[0] = (gchar *) gretl_python_path();
	    argv[1] = (gchar *) fname;
	    argv[2] = NULL;
	} else if (lang == LANG_JULIA) {
	    argv[0] = (gchar *) gretl_julia_path();
	    argv[1] = (gchar *) fname;
	    argv[2] = NULL;
	} else if (lang == LANG_STATA) {
	    argv[0] = (gchar *) gretl_stata_path();
	    argv[1] = (gchar *) "-q";
	    argv[2] = (gchar *) "-b";
	    argv[3] = (gchar *) "do";
	    argv[4] = (gchar *) fname;
	    argv[5] = NULL;
	} else if (lang == LANG_OCTAVE) {
	    argv[0] = (gchar *) gretl_octave_path();
	    argv[1] = (gchar *) "-q";
	    argv[2] = (gchar *) fname;
	    argv[3] = NULL;
	}

	run_prog_sync(argv, lang);
    }
}

#endif /* !G_OS_WIN32 */

/* driver for starting R, either interactive or in batch mode */

void start_R (const char *buf, int send_data, int interactive)
{
    gretlopt Ropt = OPT_G;
    int err = 0;

    if (send_data && !data_status) {
	warnbox(_("Please open a data file first"));
	return;
    }

    if (interactive) {
	Ropt |= OPT_I;
    }

    if (send_data) {
	Ropt |= OPT_D;
	if (annual_data(dataset) || quarterly_or_monthly(dataset)) {
	    const char *opts[] = {
		N_("multiple time series object"),
		N_("data frame")
	    };
	    int resp;

	    resp = radio_dialog(NULL, _("Send data as"), opts, 2, 0, 0, NULL);
	    if (resp < 0) {
		return;
	    } else if (resp == 1) {
		Ropt |= OPT_F;
	    }
	}
    }

    /* On Windows in non-interactive mode, don't write
       these files here; that will be handled later
    */
#ifdef G_OS_WIN32
    if (interactive) {
	err = write_gretl_R_files(buf, dataset, Ropt);
    }
#else
    err = write_gretl_R_files(buf, dataset, Ropt);
#endif

    if (err) {
	gui_errmsg(err);
	delete_gretl_R_files();
    } else if (interactive) {
#ifdef G_OS_WIN32
	win32_start_R_async();
#else
	start_R_async();
#endif
    } else {
	/* non-interactive */
#ifdef G_OS_WIN32
	win32_run_R_sync(buf, Ropt);
#else
	run_R_sync();
#endif
    }
}

void verbose_gerror_report (GError *gerr, const char *src)
{
    fprintf(stderr, "GError details from %s\n"
	    " message: '%s'\n domain = %d, code = %d\n",
	    src, gerr->message, gerr->domain, gerr->code);
}

/* Note: simplified 2020-12-08 on the assumption that filenames
   within gretl will be in UTF-8 on all platforms.
*/

int gretl_file_get_contents (const gchar *fname, gchar **contents,
			     gsize *size)
{
    GError *gerr = NULL;
    gboolean ok = 0;

    ok = g_file_get_contents(fname, contents, size, &gerr);

    if (gerr != NULL) {
	verbose_gerror_report(gerr, "g_file_get_contents");
	errbox(gerr->message);
	g_error_free(gerr);
    }

    return ok ? 0 : E_FOPEN;
}

const char *print_today (void)
{
    static char timestr[16];
    struct tm *local;
    time_t t;

    t = time(NULL);
    local = localtime(&t);
    strftime(timestr, 15, "%Y-%m-%d", local);

    return timestr;
}
