/*
 *  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 "winstack.h"
#include "textbuf.h"
#include "tabwin.h"

#ifdef G_OS_WIN32
# include "gretlwin32.h"
#endif

#define TDEBUG 0

struct tabwin_t_ {
    int role;             /* what's tabwin doing? */
    GtkWidget *main;      /* top-level GTK window */
    GtkWidget *hbox;      /* horizontal box to hold menu bar */
    GtkWidget *mbar;      /* menu bar */
    GtkWidget *tabs;      /* notebook for tabs */
    GtkWidget *dialog;    /* associated dialog */
    GtkWidget *dlg_owner; /* the tab that "owns" dialog */
};

GtkTargetEntry tabwin_drag_targets[] = {
    { "text/uri-list",  0, GRETL_FILENAME },
};

/* We support a tabbed editor for hansl scripts, one for
   "alt" (foreign) scripts, and also a tabbed viewer for
   gretl models -- unless we're gretl_edit, in which case
   there's a single integrated tabbed script editor.
*/

static tabwin_t *tabhansl;
static tabwin_t *tabalt;
static tabwin_t *tabmod;

#ifdef GRETL_EDIT
static const char *hansl_title = N_("gretl_edit");
#else
static const char *hansl_title = N_("gretl: script editor");
static const char *alt_title = N_("gretl: foreign script editor");
#endif

static void undock_tabbed_viewer (GtkWidget *w, windata_t *vwin);

static void tabwin_destroy (GtkWidget *w, tabwin_t *tabwin)
{
    if (tabwin == tabhansl) {
	tabhansl = NULL;
    } else if (tabwin == tabalt) {
	tabalt = NULL;
    } else if (tabwin == tabmod) {
	tabmod = NULL;
    }
    free(tabwin);

#ifdef GRETL_EDIT
    gtk_main_quit();
#endif
}

static tabwin_t *vwin_get_tabwin (windata_t *vwin)
{
    return g_object_get_data(G_OBJECT(vwin->topmain), "tabwin");
}

static gboolean maybe_block_tabedit_quit (tabwin_t *tabwin,
					  GtkWidget *parent)
{
    GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);
    int np = gtk_notebook_get_n_pages(notebook);
    gboolean ret = FALSE;

    if (np > 1) {
	gchar *msg = g_strdup_printf(_("Editing %d scripts: really quit?"), np);
	gint resp;

	gtk_window_present(GTK_WINDOW(parent));
	resp = yes_no_dialog(_(hansl_title), msg, parent);
	if (resp != GRETL_YES) {
	    ret = TRUE;
	}
	g_free(msg);
    } else if (np == 1) {
	GtkWidget *page = gtk_notebook_get_nth_page(notebook, 0);

	if (page != NULL) {
	    windata_t *vwin = g_object_get_data(G_OBJECT(page), "vwin");

	    if (vwin_content_changed(vwin)) {
		gtk_window_present(GTK_WINDOW(parent));
		ret = query_save_text(NULL, NULL, vwin);
	    }
	}
    }

    return ret;
}

/* called from winstack.c on program exit: @w will
   be the top-level of a tabbed window */

gboolean tabwin_exit_check (GtkWidget *w)
{
    tabwin_t *tabwin = g_object_get_data(G_OBJECT(w), "tabwin");

    if (tabwin->role != EDIT_HANSL &&
	!editing_alt_script(tabwin->role)) {
	return FALSE;
    }

    return maybe_block_tabedit_quit(tabwin, w);
}

/* called on delete-event: @w is the top-level */

static gboolean tabedit_quit_check (GtkWidget *w, GdkEvent *event,
				    tabwin_t *tabwin)
{
    if (tabwin->role != EDIT_HANSL &&
	!editing_alt_script(tabwin->role)) {
	return FALSE;
    }

    return maybe_block_tabedit_quit(tabwin, w);
}

/* activate or de-activate a tab's closer button */

static void viewer_tab_show_closer (GtkNotebook *notebook,
				    GtkWidget *tab,
				    gboolean show)
{
    GtkWidget *lbl = gtk_notebook_get_tab_label(notebook, tab);
    GtkWidget *button = g_object_get_data(G_OBJECT(lbl), "closer");

    if (button != NULL) {
	if (show) {
	    gtk_widget_show(button);
	} else {
	    gtk_widget_hide(button);
	}
    }
}

static void tabwin_remove_toolbar (tabwin_t *tabwin)
{
#if TDEBUG
    fprintf(stderr, "*** tabwin_remove_toolbar: removing toolbar at %p\n",
	    (void *) tabwin->mbar);
#endif
    gtk_container_remove(GTK_CONTAINER(tabwin->hbox),
			 tabwin->mbar);
    tabwin->mbar = NULL;
}

static void tabwin_insert_toolbar (tabwin_t *tabwin, windata_t *vwin)
{
#if TDEBUG
    fprintf(stderr, "*** tabwin_insert_toolbar: inserting toolbar at %p\n",
	    (void *) vwin->mbar);
#endif
    gtk_box_pack_start(GTK_BOX(tabwin->hbox), vwin->mbar,
		       TRUE, TRUE, 0);
    gtk_widget_show_all(vwin->mbar);
    tabwin->mbar = vwin->mbar;
}

static void page_removed_callback (GtkNotebook *notebook,
				   GtkWidget *child,
				   gint pgnum,
				   gpointer data)
{
    int np = gtk_notebook_get_n_pages(notebook);

#if TDEBUG
    fprintf(stderr, "*** page_removed_callback: child=%p\n", (void *) child);
#endif

    if (np < 5) {
	gtk_notebook_popup_disable(notebook);
    }

    if (np == 1) {
	/* only one tab left after removal: this page should
	   not display its own closer button, nor should it
	   be detachable
	*/
	GtkWidget *tab = gtk_notebook_get_nth_page(notebook, 0);

	gtk_notebook_set_tab_detachable(notebook, tab, FALSE);
	viewer_tab_show_closer(notebook, tab, FALSE);
    }
}

static void page_added_callback (GtkNotebook *notebook,
				 GtkWidget *child,
				 gint pgnum,
				 gpointer data)
{
    int i, np = gtk_notebook_get_n_pages(notebook);
    GtkWidget *tab;

    if (np >= 5) {
	gtk_notebook_popup_enable(notebook);
    }

    if (np > 1) {
	for (i=0; i<np; i++) {
	    tab = gtk_notebook_get_nth_page(notebook, i);
	    gtk_notebook_set_tab_detachable(notebook, tab, TRUE);
	    viewer_tab_show_closer(notebook, tab, TRUE);
	}
    } else {
	tab = gtk_notebook_get_nth_page(notebook, 0);
	viewer_tab_show_closer(notebook, tab, FALSE);
    }
}

/* callback for tab-specific close button: this should be
   invoked only if there's at least one tab left after
   trashing the selected one
*/

static void tabwin_tab_close (GtkWidget *w, windata_t *vwin)
{
    tabwin_t *tabwin = vwin_get_tabwin(vwin);
    GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);
    gint pg = gtk_notebook_page_num(notebook, vwin->main);

#if TDEBUG
    fprintf(stderr, "*** tabwin_tab_close: vwin = %p\n", (void *) vwin);
#endif

    if (vwin->main == tabwin->dlg_owner) {
	/* this tab has a dialog open: don't close it */
	gtk_window_present(GTK_WINDOW(tabwin->dialog));
	return;
    }

    if ((tabwin->role == EDIT_HANSL ||
	 editing_alt_script(tabwin->role)) &&
	vwin_content_changed(vwin)) {
	gint cancel = query_save_text(NULL, NULL, vwin);

	if (cancel) {
	    return;
	}
    }

    /* note: vwin->mbar is packed under tabwin, so it will not
       get destroyed automatically when the page is removed
    */
    if (tabwin->mbar != NULL && tabwin->mbar == vwin->mbar) {
	tabwin_remove_toolbar(tabwin);
    }

    /* relinquish the extra reference */
#if TDEBUG
    fprintf(stderr, " unrefing toolbar at %p\n", (void *) vwin->mbar);
#endif
    g_object_unref(vwin->mbar);

    gtk_notebook_remove_page(notebook, pg);
}

void tabwin_tab_destroy (windata_t *vwin)
{
    tabwin_t *tabwin = vwin_get_tabwin(vwin);
    GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);

#if TDEBUG
    fprintf(stderr, "*** tabwin_tab_destroy: vwin = %p\n", (void *) vwin);
#endif

    if (gtk_notebook_get_n_pages(notebook) > 1) {
	gint pg = gtk_notebook_page_num(notebook, vwin->main);

	if (tabwin->mbar != NULL && tabwin->mbar == vwin->mbar) {
	    tabwin_remove_toolbar(tabwin);
	}
	vwin->mbar = NULL;
	gtk_notebook_remove_page(notebook, pg);
    } else {
	gtk_widget_destroy(vwin->topmain);
    }
}

/* on switching the current page, put the new page's
   toolbar into place in tabwin (and remove the old
   one, if present)
*/

static gboolean switch_page_callback (GtkNotebook *tabs,
				      gpointer arg1,
				      gint pgnum,
				      tabwin_t *tabwin)
{
    GtkWidget *tab = gtk_notebook_get_nth_page(tabs, pgnum);
    windata_t *vwin = NULL;

    if (tab != NULL) {
	vwin = g_object_get_data(G_OBJECT(tab), "vwin");
    }

#if TDEBUG
    fprintf(stderr, "*** switch_page_callback: tab=%p, vwin=%p\n",
	    (void *) tab, (void *) vwin);
#endif

    if (vwin != NULL) {
	if (tabwin->mbar != NULL && tabwin->mbar != vwin->mbar) {
	    /* there's an "old" toolbar in place */
	    tabwin_remove_toolbar(tabwin);
	}
	if (vwin->mbar != NULL && vwin->mbar != tabwin->mbar) {
	    /* a "new" toolbar should be shown */
	    tabwin_insert_toolbar(tabwin, vwin);
	}
    }

    return FALSE;
}

/* callback for the "create-window" signal */

static GtkNotebook *detach_tab_callback (GtkNotebook *book,
					 GtkWidget *page,
					 gint x, gint y,
					 gpointer data)
{
    windata_t *vwin = g_object_get_data(G_OBJECT(page), "vwin");

    if (vwin != NULL) {
	undock_tabbed_viewer(NULL, vwin);
    }

    /* return NULL since we're not adding the detached
       tab to another GtkNotebook */

    return NULL;
}

/* avoid excessive padding in a tab's close button */

static void no_button_padding (GtkWidget *w)
{
    static int style_done;

    gtk_widget_set_name(w, "closer");

    if (!style_done) {
	gtk_rc_parse_string("style \"closer-style\"\n{\n"
			    "  GtkWidget::focus-padding = 0\n"
			    "  GtkWidget::focus-line-width = 0\n"
			    "  xthickness = 0\n"
			    "  ythickness = 0\n"
			    "}\n"
			    "widget \"*.closer\" style \"closer-style\"");
	style_done = 1;
    }
}

/* put a tab-specific close button next to the tab's label */

static void viewer_tab_add_closer (GtkWidget *tab, windata_t *vwin)
{
    GtkWidget *img, *button;

    img = gtk_image_new_from_stock(GTK_STOCK_CLOSE,
				   GTK_ICON_SIZE_MENU);
    button = gtk_button_new();
    gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
    gtk_container_set_border_width(GTK_CONTAINER(button), 0);
    no_button_padding(button);
    gtk_container_add(GTK_CONTAINER(button), img);
    g_signal_connect(G_OBJECT(button), "clicked",
		     G_CALLBACK(tabwin_tab_close),
		     vwin);
    gtk_container_add(GTK_CONTAINER(tab), button);
    g_object_set_data(G_OBJECT(tab), "closer", button);
}

/* try to ensure unique dummy title strings for unsaved
   new scripts */

static gchar *untitled_title (tabwin_t *tabwin)
{
    GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);
    GtkWidget *page;
    windata_t *vwin;
    int i, np, idx = 0;

    np = gtk_notebook_get_n_pages(notebook);

    for (i=0; i<np; i++) {
	page = gtk_notebook_get_nth_page(notebook, i);
	vwin = g_object_get_data(G_OBJECT(page), "vwin");
	if (strstr(vwin->fname, "script_tmp") != NULL) {
	    idx++;
	}
    }

    if (idx > 0) {
	return g_strdup_printf("%s(%d)", _("untitled"), idx);
    } else {
	return g_strdup(_("untitled"));
    }
}

/* create and add tab with filename and closer button */

static GtkWidget *make_viewer_tab (tabwin_t *tabwin,
				   windata_t *vwin,
				   const gchar *info)
{
    GtkNotebook *notebook;
    gchar *title = NULL;
    const gchar *fname = NULL;
    GtkWidget *tab;
    GtkWidget *label;
    GtkWidget *mlabel;

    tab = gtk_hbox_new(FALSE, 5);
    gtk_container_set_border_width(GTK_CONTAINER(tab), 0);

    if (tabwin->role == EDIT_HANSL || editing_alt_script(tabwin->role)) {
	if (strstr(info, "script_tmp") != NULL) {
	    title = untitled_title(tabwin);
	} else {
	    title = title_from_filename(info, tabwin->role, FALSE);
	    fname = info;
	}
    } else if (info != NULL) {
	title = g_strdup(info);
    } else {
	title = g_strdup("unknown");
    }

    label = gtk_label_new(title);
    mlabel = gtk_label_new(fname == NULL ? title : fname);
    gtk_container_add(GTK_CONTAINER(tab), label);
    g_object_set_data(G_OBJECT(tab), "label", label);
    g_object_set_data(G_OBJECT(tab), "mlabel", mlabel);
    g_free(title);

    viewer_tab_add_closer(tab, vwin);
    gtk_widget_set_size_request(tab, -1, 18);
    gtk_widget_show_all(tab);

    notebook = GTK_NOTEBOOK(tabwin->tabs);
    gtk_notebook_append_page_menu(notebook, vwin->main,
				  tab, mlabel);

    return tab;
}

/* Note: provides a means of connecting catch_viewer_key(),
   for a viewer that's embedded in a GtkNotebook.
*/

static gint catch_tabwin_key (GtkWidget *w, GdkEventKey *key,
			      tabwin_t *tabwin)
{
    GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);
    gint pg = gtk_notebook_get_current_page(notebook);
    GtkWidget *tab = gtk_notebook_get_nth_page(notebook, pg);
    windata_t *vwin = g_object_get_data(G_OBJECT(tab), "vwin");

    return catch_viewer_key(w, key, vwin);
}

/* Not sure why this is required, but without this callback
   attached, the "drag-data-received" is triggered twice for
   each drag-and-drop action. Seems like a GTK quirk.
*/

static gboolean
tabwin_handle_drop (GtkWidget *widget,
		    GdkDragContext *context,
		    int x,
		    int y,
		    guint time,
		    gpointer user_data)
{
    return TRUE;
}

static void
tabwin_handle_drag  (GtkWidget *widget,
		     GdkDragContext *context,
		     gint x,
		     gint y,
		     GtkSelectionData *data,
		     guint info,
		     guint time,
		     gpointer p)
{
    const guchar *seldata = NULL;
    gboolean success = 0;
    gchar *dfname = NULL;
    char tmp[MAXLEN];
    int pos, skip = 5;

    if (data != NULL) {
	seldata = gtk_selection_data_get_data(data);
    }

    if (info != GRETL_FILENAME) {
	goto drag_finish;
    }

    /* ignore the wrong sort of data */
    if (data == NULL || (dfname = (gchar *) seldata) == NULL ||
	strlen(dfname) <= 5 || strncmp(dfname, "file:", 5)) {
	goto drag_finish;
    }

    if (strncmp(dfname, "file://", 7) == 0) skip = 7;
#ifdef G_OS_WIN32
    if (strncmp(dfname, "file:///", 8) == 0) skip = 8;
#endif

    /* there may be multiple files: we ignore all but the first */
    *tmp = '\0';
    if ((pos = gretl_charpos('\r', dfname)) > 0 ||
	(pos = gretl_charpos('\n', dfname) > 0)) {
	strncat(tmp, dfname + skip, pos - skip);
    } else {
	strcat(tmp, dfname + skip);
    }

    /* handle spaces and such */
    unescape_url(tmp);

    if (has_suffix(tmp, ".inp")) {
	/* FIXME generalize? */
	set_tryfile(tmp);
	success = do_open_script(EDIT_HANSL);
    }

 drag_finish:

    gtk_drag_finish(context, success, FALSE, time);
}

/* build a tabbed viewer/editor */

static tabwin_t *make_tabbed_viewer (int role)
{
    tabwin_t *tabwin;
    GtkWidget *vbox;

    tabwin = mymalloc(sizeof *tabwin);

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

    tabwin->role = role;
    tabwin->dialog = NULL;
    tabwin->dlg_owner = NULL;

    /* top-level window */
    tabwin->main = gretl_gtk_window();
#ifdef GRETL_EDIT
    gtk_window_set_title(GTK_WINDOW(tabwin->main), _(hansl_title));
    g_signal_connect(G_OBJECT(tabwin->main), "delete-event",
		     G_CALLBACK(tabedit_quit_check), tabwin);
#else
    if (role == EDIT_HANSL) {
	gtk_window_set_title(GTK_WINDOW(tabwin->main), _(hansl_title));
 	g_signal_connect(G_OBJECT(tabwin->main), "delete-event",
			 G_CALLBACK(tabedit_quit_check), tabwin);
    } else if (editing_alt_script(role)) {
	gtk_window_set_title(GTK_WINDOW(tabwin->main), _(alt_title));
 	g_signal_connect(G_OBJECT(tabwin->main), "delete-event",
			 G_CALLBACK(tabedit_quit_check), tabwin);
    } else {
	gtk_window_set_title(GTK_WINDOW(tabwin->main), _("gretl: models"));
    }
#endif
    g_signal_connect(G_OBJECT(tabwin->main), "destroy",
		     G_CALLBACK(tabwin_destroy), tabwin);
    g_object_set_data(G_OBJECT(tabwin->main), "tabwin", tabwin);

    /* vertically oriented container */
    vbox = gtk_vbox_new(FALSE, 1);
    gtk_box_set_spacing(GTK_BOX(vbox), 0);
    gtk_container_set_border_width(GTK_CONTAINER(vbox), 0);
    gtk_container_add(GTK_CONTAINER(tabwin->main), vbox);

    /* box to hold menu bar */
    tabwin->hbox = gtk_hbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(vbox), tabwin->hbox, FALSE, FALSE, 0);
    tabwin->mbar = NULL;

    /* notebook with its signal handlers */
    tabwin->tabs = gtk_notebook_new();
    gtk_notebook_popup_enable(GTK_NOTEBOOK(tabwin->tabs));
    gtk_notebook_set_scrollable(GTK_NOTEBOOK(tabwin->tabs), TRUE);
    g_signal_connect(G_OBJECT(tabwin->tabs), "switch-page",
		     G_CALLBACK(switch_page_callback), tabwin);
    g_signal_connect(G_OBJECT(tabwin->tabs), "create-window",
		     G_CALLBACK(detach_tab_callback), tabwin);
    g_signal_connect(G_OBJECT(tabwin->tabs), "page-added",
		     G_CALLBACK(page_added_callback), tabwin);
    g_signal_connect(G_OBJECT(tabwin->tabs), "page-removed",
		     G_CALLBACK(page_removed_callback), tabwin);
    gtk_container_add(GTK_CONTAINER(vbox), tabwin->tabs);

#ifndef G_OS_WIN32
    set_wm_icon(tabwin->main);
#endif

    return tabwin;
}

static tabwin_t *get_tabwin_for_role (int role, int *starting)
{
    tabwin_t *tabwin = NULL;

#ifdef GRETL_EDIT /* experiment? */
    if (tabhansl != NULL) {
	tabwin = tabhansl;
    } else {
	*starting = 1;
	tabhansl = tabwin = make_tabbed_viewer(role);
    }
#else
    if (role == EDIT_HANSL) {
	if (tabhansl != NULL) {
	    tabwin = tabhansl;
	} else {
	    *starting = 1;
	    tabhansl = tabwin = make_tabbed_viewer(role);
	}
    } else if (editing_alt_script(role)) {
	if (tabalt != NULL) {
	    tabwin = tabalt;
	} else {
	    *starting = 1;
	    tabalt = tabwin = make_tabbed_viewer(role);
	}
    } else if (role == VIEW_MODEL) {
	if (tabmod != NULL) {
	    tabwin = tabmod;
	} else {
	    *starting = 1;
	    tabmod = tabwin = make_tabbed_viewer(role);
	}
    }
#endif

    return tabwin;
}

/* Create a viewer/editor tab, as an alternative to a stand-alone
   window. We build the tabbed top-level if need be, otherwise we
   stick a new tab into the existing window.
*/

windata_t *viewer_tab_new (int role, const char *info,
			   gpointer data)
{
    tabwin_t *tabwin;
    windata_t *vwin;
    int starting = 0;
    gulong handler_id;

    tabwin = get_tabwin_for_role(role, &starting);
    if (tabwin == NULL) {
	return NULL;
    }

#ifdef GRETL_EDIT
    if (editor == NULL) {
	set_editor(tabwin->main);
    }
#endif

    vwin = vwin_new(role, data);
    if (vwin == NULL) {
	return NULL;
    }

    vwin->flags = VWIN_TABBED;
    vwin->main = gtk_hbox_new(FALSE, 0);
    g_object_set_data(G_OBJECT(vwin->main), "vwin", vwin);
    handler_id = g_signal_connect(G_OBJECT(vwin->main), "destroy",
				  G_CALLBACK(free_windata), vwin);
    g_object_set_data(G_OBJECT(vwin->main), "destroy-id",
		      GUINT_TO_POINTER(handler_id));

#if TDEBUG
    fprintf(stderr, "*** viewer_tab_new: vwin=%p, main hbox=%p\n",
	    (void *) vwin, (void *) vwin->main);
#endif

    make_viewer_tab(tabwin, vwin, info);
    vwin->topmain = tabwin->main;

    vwin->vbox = gtk_vbox_new(FALSE, 1);
    gtk_container_set_border_width(GTK_CONTAINER(vwin->vbox), 1);
    gtk_container_add(GTK_CONTAINER(vwin->main), vwin->vbox);

    if (starting) {
	window_list_add(tabwin->main, role);
	if (!vwin_editing_script(role)) {
	    g_signal_connect(G_OBJECT(tabwin->main), "key-press-event",
			     G_CALLBACK(catch_tabwin_key), tabwin);
	    vwin->flags |= WVIN_KEY_SIGNAL_SET;
	}
    }

    return vwin;
}

/* called when a new editor tab is added: if this is the
   first such tab then tabwin->mbar will be NULL, otherwise
   if will be some other page's mbar, which will have to
   be swapped out
*/

void tabwin_register_toolbar (windata_t *vwin)
{
    tabwin_t *tabwin = vwin_get_tabwin(vwin);
    gulong handler_id;

    /* take out a reference to @vwin's toolbar to prevent
       its auto-destruction; also ensure that the pointer
       goes to NULL on destruction
    */
    g_object_ref(vwin->mbar);
    handler_id = g_signal_connect(G_OBJECT(vwin->mbar), "destroy",
				  G_CALLBACK(gtk_widget_destroyed),
				  &vwin->mbar);
    g_object_set_data(G_OBJECT(vwin->mbar), "destroy-id",
		      GUINT_TO_POINTER(handler_id));

#if TDEBUG
    fprintf(stderr, "*** tabwin_register_toolbar: vwin=%p has toolbar=%p\n",
	    (void *) vwin, (void *) vwin->mbar);
    fprintf(stderr, "    new handler_id for mbar destruction callback: %lu\n",
	    handler_id);
#endif

    if (tabwin->mbar != NULL) {
	tabwin_remove_toolbar(tabwin);
    }

    tabwin_insert_toolbar(tabwin, vwin);
}

/* This is used, inter alia, for an "untitled" tab:
   to set its real filename when it is saved
*/

void tabwin_tab_set_title (windata_t *vwin, const char *title)
{
    tabwin_t *tabwin = vwin_get_tabwin(vwin);
    GtkWidget *tab, *label;

    tab = gtk_notebook_get_tab_label(GTK_NOTEBOOK(tabwin->tabs),
				     vwin->main);

    /* label shown on the tab itself */
    label = g_object_get_data(G_OBJECT(tab), "label");
    if (label != NULL) {
	gtk_label_set_text(GTK_LABEL(label), title);
    }

    /* label shown in tab-switching right-click menu */
    label = g_object_get_data(G_OBJECT(tab), "mlabel");
    if (label != NULL) {
	const gchar *mtext = title;

	if (vwin_editing_script(tabwin->role) && vwin->fname[0] != '\0') {
	    mtext = vwin->fname;
	}
	gtk_label_set_text(GTK_LABEL(label), mtext);
    }
}

const gchar *tabwin_tab_get_title (windata_t *vwin)
{
    tabwin_t *tabwin = vwin_get_tabwin(vwin);
    GtkWidget *tab, *label;

    tab = gtk_notebook_get_tab_label(GTK_NOTEBOOK(tabwin->tabs),
				     vwin->main);
    if (tab != NULL) {
	label = g_object_get_data(G_OBJECT(tab), "label");
	if (label != NULL) {
	    return gtk_label_get_text(GTK_LABEL(label));
	}
    }

    return NULL;
}

/* set or unset the "modified flag" (trailing asterisk on
   the filename) for the tab label for a page in tabbed
   editor
*/

void tabwin_tab_set_status (windata_t *vwin)
{
    gboolean unsaved = (vwin->flags & VWIN_CONTENT_CHANGED);
    tabwin_t *tabwin = vwin_get_tabwin(vwin);
    GtkWidget *tab, *label;
    const gchar *text, *p;
    gchar *modtext = NULL;

    tab = gtk_notebook_get_tab_label(GTK_NOTEBOOK(tabwin->tabs),
				     vwin->main);
    label = g_object_get_data(G_OBJECT(tab), "label");

    if (label != NULL) {
	text = gtk_label_get_text(GTK_LABEL(label));
	p = strstr(text, " *");
	if (unsaved && p == NULL) {
	    modtext = g_strdup_printf("%s *", text);
	} else if (!unsaved && p != NULL) {
	    modtext = g_strndup(text, strlen(text) - 2);
	}
    }

    if (modtext != NULL) {
	gtk_label_set_text(GTK_LABEL(label), modtext);
	label = g_object_get_data(G_OBJECT(tab), "mlabel");
	if (label != NULL) {
	    gtk_label_set_text(GTK_LABEL(label), modtext);
	}
	g_free(modtext);
    }
}

void show_tabbed_viewer (windata_t *vwin)
{
    tabwin_t *tabwin = vwin_get_tabwin(vwin);
    GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);
    int np = gtk_notebook_get_n_pages(notebook);

    gtk_widget_show_all(vwin->main);

    if (np > 1) {
	int pgnum = gtk_notebook_page_num(notebook, vwin->main);

	gtk_notebook_set_current_page(notebook, pgnum);
    }

    if (!gtk_widget_get_visible(tabwin->main)) {
	gtk_widget_show_all(tabwin->main);
    }

    if (vwin->role == EDIT_HANSL) {
	gtk_drag_dest_set(vwin->text,
			  GTK_DEST_DEFAULT_ALL,
			  tabwin_drag_targets, 1,
			  GDK_ACTION_COPY);
	g_signal_connect(G_OBJECT(vwin->text), "drag-drop",
			 G_CALLBACK(tabwin_handle_drop),
			 tabwin);
	g_signal_connect(G_OBJECT(vwin->text), "drag-data-received",
			 G_CALLBACK(tabwin_handle_drag),
			 tabwin);
    }

    gtk_window_present(GTK_WINDOW(tabwin->main));
}

/* move among the editor tabs via keyboard */

void tabwin_navigate (windata_t *vwin, guint key)
{
    tabwin_t *tabwin = vwin_get_tabwin(vwin);
    GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);

    if (key == GDK_less || key == GDK_Page_Up) {
	gtk_notebook_prev_page(notebook);
    } else if (key == GDK_greater || key == GDK_Page_Down) {
	gtk_notebook_next_page(notebook);
    } else {
	/* numeric value, 1 to 9 */
	gtk_notebook_set_current_page(notebook, key - 1);
    }
}

static void size_new_toplevel (windata_t *vwin)
{
    int cw = get_char_width(vwin->text);
    int hsize, vsize;

    if (vwin->role == EDIT_HANSL || editing_alt_script(vwin->role)) {
	hsize = SCRIPT_WIDTH;
	vsize = SCRIPT_HEIGHT;
    } else {
	hsize = 63; /* MODEL_WIDTH ? */
	vsize = MODEL_HEIGHT;
    }

    hsize *= cw;
    hsize += 48;

    gtk_window_set_default_size(GTK_WINDOW(vwin->main), hsize, vsize);
}

static gchar *title_from_vwin (windata_t *vwin)
{
    if (vwin->role == VIEW_MODEL) {
	MODEL *pmod = vwin->data;

	return g_strdup_printf(_("gretl: model %d"), pmod->ID);
    } else {
	return title_from_filename(vwin->fname, vwin->role, TRUE);
    }
}

/* show or hide the New and Open toolbar items, which occupy
   the first two slots on the toolbar
*/

void script_editor_show_new_open (windata_t *vwin, gboolean show)
{
    GtkToolItem *item0, *item1;

    item0 = gtk_toolbar_get_nth_item(GTK_TOOLBAR(vwin->mbar), 0);
    item1 = gtk_toolbar_get_nth_item(GTK_TOOLBAR(vwin->mbar), 1);

    if (show) {
	gtk_widget_show(GTK_WIDGET(item0));
	gtk_widget_show(GTK_WIDGET(item1));
    } else {
	gtk_widget_hide(GTK_WIDGET(item0));
	gtk_widget_hide(GTK_WIDGET(item1));
    }
}

/* response to pulling a script or model out of the tabbed
   context: we need to give the content its own window
*/

static void undock_tabbed_viewer (GtkWidget *w, windata_t *vwin)
{
    tabwin_t *tabwin = vwin_get_tabwin(vwin);
    GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);
    GtkWidget *page = vwin->main;
    gulong handler_id;
    gchar *title;

    if (gtk_notebook_get_n_pages(notebook) < 2) {
	/* we won't do this if there's only one page in the
	   viewer */
	return;
    }

    /* disconnect stuff */
    vwin->main = vwin->topmain = NULL;

    /* remove signals and data from hbox (ex vwin->main) */
    g_object_steal_data(G_OBJECT(page), "vwin");
    g_signal_handlers_disconnect_by_func(page,
					 free_windata,
					 vwin);
    handler_id = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(vwin->mbar),
						    "destroy-id"));
#if TDEBUG
    fprintf(stderr, "*** undock_tabbed_viewer: w = %p, vwin = %p, page = %p\n",
	    (void *) w, (void *) vwin, page);
    fprintf(stderr, "    handler_id from mbar: %lu\n", handler_id);
#endif
    if (handler_id > 0) {
	g_signal_handler_disconnect(vwin->mbar, handler_id);
	g_object_steal_data(G_OBJECT(vwin->mbar), "destroy-id");
    }

    /* extract vwin->vbox from its tabbed holder */
    g_object_ref(vwin->vbox);
    gtk_container_remove(GTK_CONTAINER(page), vwin->vbox);
#if GTK_MAJOR_VERSION >= 3
    gtk_notebook_detach_tab(GTK_NOTEBOOK(notebook), page);
#else /* GTK2 */
    gtk_container_remove(GTK_CONTAINER(notebook), page);
    /* tweak vbox params */
    gtk_box_set_spacing(GTK_BOX(vwin->vbox), 4);
    gtk_container_set_border_width(GTK_CONTAINER(vwin->vbox), 4);
#endif

    /* build new shell for @vwin */
    vwin->main = gretl_gtk_window();
    title = title_from_vwin(vwin);
    gtk_window_set_title(GTK_WINDOW(vwin->main), title);
    g_free(title);
    handler_id = g_signal_connect(G_OBJECT(vwin->main), "destroy",
				  G_CALLBACK(free_windata), vwin);
    g_object_set_data(G_OBJECT(vwin->main), "destroy-id",
		      GUINT_TO_POINTER(handler_id));
    g_object_set_data(G_OBJECT(vwin->main), "vwin", vwin);
    size_new_toplevel(vwin);

#if TDEBUG
    fprintf(stderr, "    undock: new main=%p, mbar=%p\n",
	    (void *) vwin->main, (void *) vwin->mbar);
    fprintf(stderr, "    new handler_id for main destruction: %lu\n",
	    handler_id);
#endif

    /* add box for toolbar, pack it, drop extra ref., then
       remove the "tabbed" flag (note that the tabbed flag
       is wanted so that vwin_pack_toolbar will put the
       toolbar up top)
    */
    vwin_pack_toolbar(vwin);
    g_object_unref(vwin->mbar);
    vwin->flags &= ~VWIN_TABBED;

    if (vwin->role == EDIT_HANSL || editing_alt_script(vwin->role)) {
	/* invalidate some menubar items */
	script_editor_show_new_open(vwin, FALSE);
	/* connect delete signal for single-script window */
	g_signal_connect(G_OBJECT(vwin->main), "delete-event",
			 G_CALLBACK(query_save_text), vwin);
    }

    /* put vbox into new top-level window and drop extra ref. */
    gtk_container_add(GTK_CONTAINER(vwin->main), vwin->vbox);
    g_object_unref(vwin->vbox);

    /* add to window list and attach window-key signals */
    window_list_add(vwin->main, vwin->role);

    /* add key-catcher for single-item window */
    g_signal_connect(G_OBJECT(vwin->main), "key-press-event",
		     G_CALLBACK(catch_viewer_key), vwin);

#ifndef G_OS_WIN32
    set_wm_icon(vwin->main);
#endif

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

#if TDEBUG
    fprintf(stderr, "undock_tabbed_viewer: done\n");
#endif
}

static void dock_viewer (GtkWidget *w, windata_t *vwin)
{
    tabwin_t *tabwin = NULL;
    GtkWidget *oldmain;
    GtkWidget *box;
    gulong handler_id;
    gchar *info = NULL;

    if (vwin->role == VIEW_MODEL) {
	tabwin = tabmod;
    } else if (vwin->role == EDIT_HANSL) {
	tabwin = tabhansl;
    } else if (editing_alt_script(vwin->role)) {
	tabwin = tabalt;
    }

    if (tabwin == NULL) {
	return;
    }

#if TDEBUG
    fprintf(stderr, "dock_viewer: starting on vwin at %p\n",
	    (void *) vwin);
#endif

    oldmain = vwin->main;
    gtk_widget_hide(oldmain);

    /* disconnect */
    vwin->main = NULL;

    /* remove data, and also remove destruction-related signals,
       from stand-alone vwin->main */
    g_object_steal_data(G_OBJECT(oldmain), "vwin");
    g_signal_handlers_disconnect_by_func(oldmain,
					 free_windata,
					 vwin);
    g_signal_handlers_disconnect_by_func(oldmain,
					 query_save_text,
					 vwin);

    /* grab info for title */
    if (vwin->role == EDIT_HANSL || editing_alt_script(vwin->role)) {
	info = g_strdup(vwin->fname);
    } else {
	const gchar *tmp = gtk_window_get_title(GTK_WINDOW(oldmain));

	if (!strncmp(tmp, "gretl: ", 7)) {
	    tmp += 7;
	}
	info = g_strdup(tmp);
    }

    /* extract vwin->vbox from oldmain and trash oldmain */
    g_object_ref(vwin->vbox);
    gtk_container_remove(GTK_CONTAINER(oldmain), vwin->vbox);
    gtk_widget_destroy(oldmain);

#if TDEBUG
    fprintf(stderr, "dock_viewer: vwin->vbox at %p\n",
	    (void *) vwin->vbox);
#endif

    if (vwin->role == EDIT_HANSL || editing_alt_script(vwin->role)) {
	script_editor_show_new_open(vwin, TRUE);
    }

    /* extract vwin->mbar */
    g_object_ref(vwin->mbar);
    box = gtk_widget_get_parent(vwin->mbar);
    gtk_container_remove(GTK_CONTAINER(box), vwin->mbar);
    gtk_widget_destroy(box);

    /* create new vwin->main, etc. */
    vwin->main = gtk_hbox_new(FALSE, 0);
    g_object_set_data(G_OBJECT(vwin->main), "vwin", vwin);
    handler_id = g_signal_connect(G_OBJECT(vwin->main), "destroy",
				  G_CALLBACK(free_windata), vwin);
    g_object_set_data(G_OBJECT(vwin->main), "destroy-id",
		      GUINT_TO_POINTER(handler_id));

    make_viewer_tab(tabwin, vwin, info);
    vwin->topmain = tabwin->main;
    vwin->flags = VWIN_TABBED;
    g_free(info);

    /* tweak vbox params and insert */
    gtk_box_set_spacing(GTK_BOX(vwin->vbox), 1);
    gtk_container_set_border_width(GTK_CONTAINER(vwin->vbox), 1);
    gtk_container_add(GTK_CONTAINER(vwin->main), vwin->vbox);
    g_object_unref(vwin->vbox);

    /* repack toolbar in tabwin */
    tabwin_register_toolbar(vwin);
    g_object_unref(vwin->mbar);

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

#if TDEBUG
    fprintf(stderr, "dock_viewer: done\n");
#endif
}

gboolean window_is_undockable (windata_t *vwin)
{
#ifndef GRETL_EDIT
    if (vwin->topmain != NULL) {
	tabwin_t *tabwin = vwin_get_tabwin(vwin);

	if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(tabwin->tabs)) > 1) {
	    return TRUE;
	}
    }
#endif

    return FALSE;
}

gboolean window_is_dockable (windata_t *vwin)
{
#ifndef GRETL_EDIT
    if (vwin->topmain == NULL) {
	if (vwin->role == EDIT_HANSL && tabhansl != NULL) {
	    return TRUE;
	} else if (editing_alt_script(vwin->role) && tabalt != NULL) {
	    return TRUE;
	} else if (vwin->role == VIEW_MODEL && tabmod != NULL) {
	    return TRUE;
	}
    }
#endif

    return FALSE;
}

void add_undock_popup_item (GtkWidget *menu, windata_t *vwin)
{
    GtkWidget *item;

    item = gtk_menu_item_new_with_label(_("Move to new window"));
    g_signal_connect(G_OBJECT(item), "activate",
		     G_CALLBACK(undock_tabbed_viewer),
		     vwin);
    gtk_widget_show(item);
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
}

void add_dock_popup_item (GtkWidget *menu, windata_t *vwin)
{
    GtkWidget *item;

    item = gtk_menu_item_new_with_label(_("Move to tabbed window"));
    g_signal_connect(G_OBJECT(item), "activate",
		     G_CALLBACK(dock_viewer),
		     vwin);
    gtk_widget_show(item);
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
}

windata_t *tabwin_get_editor_for_file (const char *filename,
				       GtkWidget *w)
{
    tabwin_t *tabwin = NULL;
    windata_t *ret = NULL;

    if (tabhansl != NULL && w == tabhansl->main) {
	tabwin = tabhansl;
    } else if (tabalt != NULL && w == tabalt->main) {
	tabwin = tabalt;
    }

    if (tabwin != NULL) {
	GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);
	int i, n = gtk_notebook_get_n_pages(notebook);
	GtkWidget *tab;
	windata_t *vwin;

	for (i=0; i<n; i++) {
	    tab = gtk_notebook_get_nth_page(notebook, i);
	    vwin = g_object_get_data(G_OBJECT(tab), "vwin");
	    if (vwin != NULL && !strcmp(filename, vwin->fname)) {
		ret = vwin;
		break;
	    }
	}
    }

    return ret;
}

windata_t *get_tab_for_data (const gpointer data,
			     GtkWidget *w)
{
    windata_t *ret = NULL;

    if (tabmod != NULL && w == tabmod->main) {
	GtkNotebook *notebook = GTK_NOTEBOOK(tabmod->tabs);
	int i, n = gtk_notebook_get_n_pages(notebook);
	GtkWidget *tab;
	windata_t *vwin;

	for (i=0; i<n; i++) {
	    tab = gtk_notebook_get_nth_page(notebook, i);
	    vwin = g_object_get_data(G_OBJECT(tab), "vwin");
	    if (vwin != NULL && vwin->data == data) {
		ret = vwin;
		break;
	    }
	}
    }

    return ret;
}

void tabwin_tab_present (windata_t *vwin)
{
    tabwin_t *tabwin = g_object_get_data(G_OBJECT(vwin->topmain),
					 "tabwin");
    GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);
    int i, n = gtk_notebook_get_n_pages(notebook);
    GtkWidget *tab;

    for (i=0; i<n; i++) {
	tab = gtk_notebook_get_nth_page(notebook, i);
	if (vwin == g_object_get_data(G_OBJECT(tab), "vwin")) {
	    gint pg = gtk_notebook_page_num(notebook, vwin->main);

	    gtk_notebook_set_current_page(notebook, pg);
	    break;
	}
    }

    gtk_window_present(GTK_WINDOW(vwin->topmain));
}

void tabwin_close_models_viewer (GtkWidget *w)
{
    if (tabmod != NULL && w == tabmod->main) {
	gtk_widget_destroy(w);
    }
}

static void tabwin_unregister_dialog (GtkWidget *w, tabwin_t *tabwin)
{
    if (tabwin != NULL && (tabwin == tabmod ||
			   tabwin == tabhansl ||
			   tabwin == tabalt)) {
	/* @tabwin will be an invalid pointer if it
	   got a delete-event before execution gets
	   here
	*/
	GtkWidget *tab = tabwin->dlg_owner;

#if TDEBUG
	fprintf(stderr, "*** unregister_dialog: owner = %p\n", (void *) tab);
#endif

	if (tab != NULL) {
	    windata_t *vwin = g_object_get_data(G_OBJECT(tab), "vwin");
	    GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);

	    gtk_widget_set_sensitive(GTK_WIDGET(vwin->mbar), TRUE);
	    if (gtk_notebook_get_n_pages(notebook) > 1) {
		gtk_notebook_set_tab_detachable(notebook, tab, TRUE);
	    }
	    tabwin->dlg_owner = NULL;
	}
	tabwin->dialog = NULL;
    }
}

/* Called when a tabbed viewer spawns a dialog that becomes
   invalid if the currently active tab is destroyed. We make
   make the current tab undestroyable and undetachable for the
   duration.
*/

void tabwin_register_dialog (GtkWidget *w, gpointer p)
{
    tabwin_t *tabwin = g_object_get_data(G_OBJECT(p), "tabwin");
    GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);
    gint pg = gtk_notebook_get_current_page(notebook);
    GtkWidget *tab = gtk_notebook_get_nth_page(notebook, pg);
    windata_t *vwin = g_object_get_data(G_OBJECT(tab), "vwin");

#if TDEBUG
    fprintf(stderr, "*** tabwin_register_dialog: w = %p, tab=%p\n",
	    (void *) w, (void *) tab);
#endif

    gtk_widget_set_sensitive(vwin->mbar, FALSE);
    gtk_notebook_set_tab_detachable(notebook, tab, FALSE);

    tabwin->dialog = w;
    tabwin->dlg_owner = tab;

    g_signal_connect(G_OBJECT(w), "destroy",
		     G_CALLBACK(tabwin_unregister_dialog),
		     tabwin);
}

int viewer_n_siblings (windata_t *vwin)
{
    tabwin_t *tabwin = vwin_get_tabwin(vwin);
    int n = 0;

    if (tabwin != NULL) {
	n = gtk_notebook_get_n_pages(GTK_NOTEBOOK(tabwin->tabs));
	if (n > 0) n--;
    }

    return n;
}

int highest_numbered_var_in_tabwin (tabwin_t *tabwin,
				    const DATASET *dset)
{
    int vmax = 0;

    if (tabwin->role == VIEW_MODEL) {
	GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);
	int n = gtk_notebook_get_n_pages(notebook);
	const MODEL *pmod;
	windata_t *vwin;
	GtkWidget *tab;
	int i, m_vmax;

	for (i=0; i<n; i++) {
	    tab = gtk_notebook_get_nth_page(notebook, i);
	    vwin = g_object_get_data(G_OBJECT(tab), "vwin");
	    pmod = vwin->data;
	    m_vmax = highest_numbered_var_in_model(pmod, dset);
	    if (m_vmax > vmax) {
		vmax = m_vmax;
	    }
	}
    }

    return vmax;
}

void list_add_tabwin_models (tabwin_t *tabwin, GList **plist)
{
    if (tabwin->role == VIEW_MODEL) {
	GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);
	int n = gtk_notebook_get_n_pages(notebook);
	windata_t *vwin;
	GtkWidget *tab;
	int i;

	for (i=0; i<n; i++) {
	    tab = gtk_notebook_get_nth_page(notebook, i);
	    vwin = g_object_get_data(G_OBJECT(tab), "vwin");
	    if (vwin != NULL && vwin->data != NULL) {
		*plist = g_list_append(*plist, vwin->data);
	    }
	}
    }
}

windata_t *window_get_active_vwin (GtkWidget *window)
{
    windata_t *vwin = g_object_get_data(G_OBJECT(window), "vwin");

    if (vwin == NULL) {
	tabwin_t *tabwin = g_object_get_data(G_OBJECT(window), "tabwin");

	if (tabwin != NULL) {
	    GtkNotebook *notebook = GTK_NOTEBOOK(tabwin->tabs);
	    gint pg = gtk_notebook_get_current_page(notebook);
	    GtkWidget *tab = gtk_notebook_get_nth_page(notebook, pg);

	    vwin = g_object_get_data(G_OBJECT(tab), "vwin");
	}
    }

    return vwin;
}
