/*
 *  Copyright (c) by Allin Cottrell
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/* datafiles.c : for gretl */

#undef COLL_DEBUG 

#include "gretl.h"
#include "datafiles.h"
#include "database.h"
#include "filelists.h"
#include "gretl_www.h"
#include "menustate.h"
#include "fnsave.h"
#include "fncall.h"
#include "treeutils.h"

#include "gretl_xml.h"
#include "gretl_func.h"

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

#define N_BROWSER_TYPES 5
#define BROWSER_BUSY    1
#define BROWSER_OK      0

static GtkWidget *browsers[N_BROWSER_TYPES];

static GtkWidget *files_window (windata_t *fdata);
static GtkWidget *files_notebook (windata_t *fdata, int code);
static int populate_notebook_filelists (windata_t *fdata, 
					GtkWidget *notebook,
					int code);

typedef struct _file_collection file_collection;

struct _file_collection {
    char *path;
    char *descfile;
    char *title;
    int which;
    GtkWidget *page;
};

enum {
    STACK_PUSH,
    STACK_POP_DATA,
    STACK_POP_PS,
    STACK_RESET_DATA,
    STACK_RESET_PS,
    STACK_SORT_DATA,
    STACK_SORT_PS,
    STACK_DESTROY
};

enum {
    RECOG_OK,
    RECOG_ERROR,
    RECOG_NOT
};

enum {
    COLL_NONE,
    COLL_DATA,
    COLL_PS
};

#define REMOTE_ACTION(c) (c == REMOTE_DB || c == REMOTE_FUNC_FILES)

static char *full_path (char *s1, const char *s2)
{
    static char fpath[FILENAME_MAX];
    int n = strlen(s1);

    if (s1[n-1] == '.') {
	s1[n-1] = '\0';
	n--;
    }
    
    if (s1[n-1] == SLASH) {
	sprintf(fpath, "%s%s", s1, s2);
    } else {
	sprintf(fpath, "%s%c%s", s1, SLASH, s2);
    }

#ifdef COLL_DEBUG
    fprintf(stderr, "full_path: got '%s' from '%s' + '%s'\n",
	    fpath, s1, s2);
#endif

    return fpath;
}

static char *unslash (const char *s)
{
    char *dest = g_strdup(s);

    if (dest != NULL) {
	size_t n = strlen(dest);

	if (dest[n-1] == '\\' || dest[n-1] == '/') {
	    dest[n-1] = '\0';
	}
    }

    return dest;
}

static int recognized_collection (file_collection *coll)
{
    const file_collection recognized_data[] = {
	{ "wooldridge", "jw_descriptions", "Wooldridge", COLL_DATA, NULL },
	{ "gujarati", "dg_descriptions", "Gujarati", COLL_DATA, NULL },
	{ "pwt56", "descriptions", "Penn World Table", COLL_DATA, NULL },
	{ NULL, NULL, NULL, COLL_DATA, NULL }
    }; 
    const file_collection recognized_ps[] = {
	{ "pwt56", "ps_descriptions", "Penn World Table", COLL_PS, NULL },
	{ NULL, NULL, NULL, COLL_PS, NULL }
    }; 
    int i;

    for (i=0; recognized_data[i].path != NULL; i++) {
	if (strstr(coll->path, recognized_data[i].path) &&
	    !strcmp(coll->descfile, recognized_data[i].descfile)) {
	    coll->title = malloc(strlen(recognized_data[i].title) + 1);
	    if (coll->title == NULL) {
		return RECOG_ERROR;
	    }
	    strcpy(coll->title, recognized_data[i].title);
	    coll->which = COLL_DATA;
	    return RECOG_OK;
	}
    }

    for (i=0; recognized_ps[i].path != NULL; i++) {
	if (strstr(coll->path, recognized_ps[i].path) &&
	    !strcmp(coll->descfile, recognized_ps[i].descfile)) {
	    coll->title = malloc(strlen(recognized_ps[i].title) + 1);
	    if (coll->title == NULL) {
		return RECOG_ERROR;
	    }
	    strcpy(coll->title, recognized_ps[i].title);
	    coll->which = COLL_PS;
	    return RECOG_OK;
	}
    }

    return RECOG_NOT;
} 

static int get_title_from_descfile (file_collection *coll)
{
    FILE *fp;
    char *test;
    char line[64], title[24];
    int err = 0;

    test = full_path(coll->path, coll->descfile);

    fp = gretl_fopen(test, "r");

    if (fp == NULL) {
	err = 1;
    } else if (fgets(line, sizeof line, fp) == NULL) {
	err = 1;
    } else {
	chopstr(line);

	if (sscanf(line, "# %23[^:]", title) != 1) {
	    err = 1;
	} else {
	    coll->title = g_strdup(title);
	    if (coll->title == NULL) {
		err = 1;
	    }
	}
    } 

    if (fp != NULL) {
	fclose(fp);
    }

    return err;
}

static void free_file_collection (file_collection *coll)
{
    free(coll->path);
    free(coll->descfile);
    if (coll->title != NULL) {
	free(coll->title);
    }
    free(coll);
}  

static file_collection *file_collection_new (const char *path,
					     const char *descfile,
					     int *err)
{
    file_collection *coll;
    int chk;

    *err = 0;

    coll = malloc(sizeof *coll);
    if (coll == NULL) {
	*err = 1;
	return NULL;
    }

    coll->title = NULL;
    
    coll->path = gretl_strdup(path);
    if (coll->path == NULL) {
	free(coll);
	*err = 1;
	return NULL;
    }

    coll->descfile = gretl_strdup(descfile);
    if (coll->descfile == NULL) {
	free(coll->path);
	free(coll);
	*err = 1;
	return NULL;
    }  

    if (strstr(coll->descfile, "ps_")) {
	coll->which = COLL_PS;
    } else {
	coll->which = COLL_DATA;
    }

    chk = recognized_collection(coll);

    if (chk == RECOG_ERROR) {
	free_file_collection(coll);
	*err = 1;
	coll = NULL;
    } else if (chk == RECOG_NOT) {
	chk = get_title_from_descfile(coll);
	if (chk) {
	    free_file_collection(coll);
	    coll = NULL;
	}
    }
    
    return coll;
}

static int compare_colls (const void *a, const void *b)
{
    const file_collection *ca = *(const file_collection **) a;
    const file_collection *cb = *(const file_collection **) b;
     
    return strcmp(ca->title, cb->title);
}

static void collection_stack_sort (file_collection **colls, int n)
{
    file_collection *tmp;
    int i;

    for (i=0; i<n; i++) {
	if (!strcmp(colls[i]->title, "Gretl")) {
	    if (i > 0) {
		tmp = colls[0];
		colls[0] = colls[i];
		colls[i] = tmp;
	    }
	    break;
	}
    }

    qsort(colls + 1, n - 1, sizeof *colls, compare_colls);
}

static file_collection *collection_stack (file_collection *coll, int op)
{
    static file_collection **datacoll;
    static file_collection **pscoll;
    static int n_data;
    static int n_data_popped;
    static int n_ps;
    static int n_ps_popped;
    file_collection *ret = NULL;
    int j;

    if (op == STACK_PUSH && coll != NULL) {
	if (coll->which == COLL_DATA) {
	    datacoll = realloc(datacoll, (n_data + 1) * sizeof *datacoll);
	    if (datacoll != NULL) {
		datacoll[n_data++] = coll;
		ret = coll;
	    }
	} else if (coll->which == COLL_PS) {
	    pscoll = realloc(pscoll, (n_ps + 1) * sizeof *pscoll);
	    if (pscoll != NULL) {
		pscoll[n_ps++] = coll;
		ret = coll;
	    }
	}
    } else if (op == STACK_POP_DATA && n_data_popped < n_data) {
	ret = datacoll[n_data_popped++];
    } else if (op == STACK_POP_PS && n_ps_popped < n_ps) {
	ret = pscoll[n_ps_popped++];
    } else if (op == STACK_RESET_DATA) {
	n_data_popped = 0;
    } else if (op == STACK_RESET_PS) {
	n_ps_popped = 0;
    } else if (op == STACK_SORT_DATA) {
	collection_stack_sort(datacoll, n_data);
    } else if (op == STACK_SORT_PS) {
	collection_stack_sort(pscoll, n_ps);
    } else if (op == STACK_DESTROY) {
        for (j=0; j<n_data; j++) {
	    free_file_collection(datacoll[j]);
	}
        free(datacoll);
        datacoll = NULL;
        n_data = 0;
        n_data_popped = 0;

        for (j=0; j<n_ps; j++) {
	    free_file_collection(pscoll[j]);
	}
        free(pscoll);
        pscoll = NULL;
        n_ps = 0;
        n_ps_popped = 0;
    } 

    return ret;
}

static int push_collection (file_collection *collection)
{
    return (collection_stack(collection, STACK_PUSH) == NULL);
}

static file_collection *pop_data_collection (void)
{
    return collection_stack(NULL, STACK_POP_DATA);
}

static file_collection *pop_ps_collection (void)
{
    return collection_stack(NULL, STACK_POP_PS);
}

void destroy_file_collections (void)
{
    collection_stack(NULL, STACK_DESTROY);
}

static void reset_data_stack (void)
{
    collection_stack(NULL, STACK_RESET_DATA);
}

static void reset_ps_stack (void)
{
    collection_stack(NULL, STACK_RESET_PS);
}

static void sort_data_stack (void)
{
    collection_stack(NULL, STACK_SORT_DATA);
}

static void sort_ps_stack (void)
{
    collection_stack(NULL, STACK_SORT_PS);
}

static int get_file_collections_from_dir (const char *dname, DIR *dir)
{
    file_collection *coll;
    struct dirent *dirent;
    size_t n;
    int err = 0;

    while (!err && (dirent = readdir(dir))) { 
	if (strstr(dirent->d_name, "descriptions")) {
#ifdef COLL_DEBUG
	    fprintf(stderr, "   %s: looking at '%s'\n", dname, dirent->d_name);
#endif
	    n = strlen(dirent->d_name);
	    if (!strcmp(dirent->d_name + n - 12, "descriptions")) {
		coll = file_collection_new(dname, dirent->d_name, &err);
		if (coll != NULL && !err) {
		    err = push_collection(coll);
		}
	    }
	}
    }

    return err;
}

static int dont_go_there (const char *s)
{
    int ret = 0;

    if (!strcmp(s, "..") || strstr(s, ".inp") || strstr(s, ".gdt") || 
	strstr(s, ".gretl") || strstr(s, ".hdr")) {
	ret = 1;
    }

    return ret;
}

static int seek_file_collections (const char *topdir)
{
    DIR *dir, *try;  
    struct dirent *dirent;
    char *subdir;
    int err = 0;
    char *tmp = unslash(topdir);

    dir = opendir(tmp);
    if (dir == NULL) {
	return 1;
    }

#ifdef COLL_DEBUG
    fprintf(stderr, "seeking file collections in '%s'\n", topdir);
#endif

    while (!err && (dirent = readdir(dir))) {
	if (!dont_go_there(dirent->d_name)) {
	    if (strcmp(dirent->d_name, ".")) {
		subdir = full_path(tmp, dirent->d_name);
	    } else {
		subdir = tmp;
	    }
	    try = opendir(subdir);
	    if (try != NULL) {
#ifdef COLL_DEBUG
		fprintf(stderr, " trying in subdir '%s'\n", subdir);
#endif
		err = get_file_collections_from_dir(subdir, try);
#ifdef COLL_DEBUG
		fprintf(stderr, " result: err = %d\n", err);
#endif
		closedir(try);
	    }
	}
    }

    closedir(dir);

    free(tmp);

    return err;
}

#ifdef COLL_DEBUG
static void print_collection (const file_collection *coll)
{
    printf("path = '%s'\n", coll->path);
    printf("descfile = '%s'\n", coll->descfile);
    if (coll->title != NULL && *coll->title != '\0') {
	printf("title = '%s'\n", coll->title);
    }
}

static void print_data_collections (void)
{
    file_collection *coll;

    printf("\n*** Data collections:\n");
    while ((coll = pop_data_collection())) {
	print_collection(coll);
    }
    reset_data_stack();
}

static void print_script_collections (void)
{
    file_collection *coll;

    printf("\n*** Script collections:\n");

    while ((coll = pop_ps_collection())) {
	print_collection(coll);
    }
    reset_ps_stack();
}
#endif

static int build_file_collections (void)
{
    static int built;
    static int err;

    if (!built) {
	err = seek_file_collections(paths.datadir);
	if (!err) {
	    err = seek_file_collections(paths.scriptdir);
	}
	if (!err) {
	    err = seek_file_collections(paths.userdir);
	}
	if (!err) {
	    sort_data_stack();
	    sort_ps_stack();
	}
	built = 1;
    }

#ifdef COLL_DEBUG
    print_data_collections();
    print_script_collections();
#endif

    return err;
}

char *strip_extension (char *s)
{
    char *p = strrchr(s, '.');
    
    if (p != NULL && 
	(!strcmp(p, ".gdt") || !strcmp(p, ".inp") ||
	 !strcmp(p, ".bin") || !strcmp(p, ".gfn") ||
	 !strcmp(p, ".bn7"))) {
	*p = '\0';
    }

    return s;
}

static int read_file_descriptions (windata_t *win, gpointer p)
{
    FILE *fp;
    GtkListStore *store;
    GtkTreeSelection *selection;
    GtkTreeIter iter;
    char line[MAXLEN];
    char *index;
    file_collection *coll = (file_collection *) p;

    index = full_path(coll->path, coll->descfile);

    fp = gretl_fopen(index, "r");
    if (fp == NULL) {
	return 1;
    }

    store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(win->listbox)));
    gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
    
    while (fgets(line, sizeof line, fp)) {
	char fname[24], descrip[80], data[64];

	if (*line == '#') continue;

	if (win->role == TEXTBOOK_DATA) {
	    if (sscanf(line, " \"%23[^\"]\",\"%79[^\"]\"", 
		       fname, descrip) == 2) {
		gtk_list_store_append(store, &iter);
		gtk_list_store_set(store, &iter, 
				   0, strip_extension(fname), 
				   1, descrip, -1);
	    }
	} else { /* script files */
	    if (sscanf(line, " \"%23[^\"]\",\"%79[^\"]\",\"%63[^\"]\"", 
		       fname, descrip, data) == 3) {
		gtk_list_store_append(store, &iter);
		gtk_list_store_set(store, &iter, 
				   0, strip_extension(fname), 
				   1, descrip, 
				   2, data, -1);
	    }
	}
    }

    fclose(fp);

    /* select the first row */
    gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(win->listbox));
    gtk_tree_selection_select_iter(selection, &iter);
    
    return 0;
}

static void display_datafile_info (GtkWidget *w, gpointer data)
{
    char hdrname[MAXLEN];
    windata_t *vwin = (windata_t *) data;
    char *descrip;
    PRN *prn;
    gchar *fname;
    file_collection *coll;

    tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), vwin->active_var,
			 0, &fname);
    coll = g_object_get_data(G_OBJECT(vwin->listbox), "coll");

    build_path(hdrname, coll->path, fname, ".gdt");
    g_free(fname);

    descrip = gretl_get_gdt_description(hdrname);

    if (descrip != NULL) {
	prn = gretl_print_new_with_buffer(descrip);
	view_buffer(prn, 80, 320, _("gretl: data header"), INFO, NULL);
    } else {
	errbox(_("Failed to retrieve description of data"));
	fprintf(stderr, I_("didn't get description from %s\n"), hdrname);
    }
}

void browser_open_data (GtkWidget *w, gpointer data)
{
    windata_t *vwin = (windata_t *) data;
    gchar *datname;
    file_collection *coll;

    tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), vwin->active_var, 
			 0, &datname);
    coll = g_object_get_data(G_OBJECT(vwin->listbox), "coll");

    build_path(tryfile, coll->path, datname, ".gdt");
    g_free(datname);

    set_datapage(coll->title);

    verify_open_data(vwin, OPEN_DATA);
} 

void browser_open_ps (GtkWidget *w, gpointer data)
{
    windata_t *vwin = (windata_t *) data;
    gchar *fname;
    file_collection *coll;

    tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), vwin->active_var, 
			 0, &fname);
    coll = g_object_get_data(G_OBJECT(vwin->listbox), "coll");

    build_path(scriptfile, coll->path, fname, ".inp");
    g_free(fname);

    /* close the calling window */
    gtk_widget_destroy(GTK_WIDGET(vwin->w));

    set_scriptpage(coll->title);

    view_file(scriptfile, 0, 0, 78, 370, VIEW_SCRIPT);
} 

enum {
    VIEW_FN_PKG_INFO,
    VIEW_FN_PKG_CODE,
    LOAD_FN_PKG,
    EDIT_FN_PKG,
    DELETE_FN_PKG,
    CALL_FN_PKG
};

static int gui_load_user_functions (const char *fname)
{
    int err;

    err = load_user_function_file(fname);
    if (err) {
	gui_errmsg(err);
    } 

    return err;
}

static int gui_delete_fn_pkg (const char *fname, windata_t *vwin)
{
    char *msg = g_strdup_printf(_("Really delete %s?"), fname);
    int err;
    
    if (yes_no_dialog(_("gretl: delete"), msg, 0) != GRETL_YES) {
        return 0;
    }

    err = remove(fname);
    if (err) {
	errbox("Couldn't delete %s", fname);
    } else {
	GtkTreeModel *mod;
	GtkTreeIter iter;
	int i = 0;

	mod = gtk_tree_view_get_model(GTK_TREE_VIEW(vwin->listbox));
	gtk_tree_model_get_iter_first(mod, &iter);
	while (i < vwin->active_var) {
	    gtk_tree_model_iter_next(mod, &iter);
	    i++;
	}
	gtk_list_store_remove(GTK_LIST_STORE(mod), &iter);
    }

    return err;
}

windata_t *gui_show_function_info (const char *fname, int role)
{
    char *pkgname = NULL;
    windata_t *vwin = NULL;
    PRN *prn;
    int err;

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

    if (role == VIEW_FUNC_INFO) {
	err = get_function_file_info(fname, prn, &pkgname);
    } else {
	err = get_function_file_code(fname, prn, &pkgname);
    }
	
    if (err) {
	gretl_print_destroy(prn);
	gui_errmsg(err);
    } else {
	gchar *title;

	title = g_strdup_printf("gretl: %s", (pkgname)? 
				pkgname : _("function code"));
	vwin = view_buffer(prn, 78, 350, title, role, NULL);
	strcpy(vwin->fname, fname);
	if (strstr(fname, "dltmp")) {
	    set_window_delete_filename(vwin);
	}
	g_free(title);
    }

    free(pkgname);

    return vwin;
}

static void browser_functions_handler (windata_t *vwin, int task)
{
    char fnfile[FILENAME_MAX];
    gchar *fname;
    gchar *dir;
    int dircol = 0;
    int err = 0;

    if (vwin->role == FUNC_FILES) {
	dircol = 4;
    } else if (vwin->role != REMOTE_FUNC_FILES) {
	dummy_call();
	return;
    }

    tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), vwin->active_var, 
			 0, &fname);

    if (dircol != 0) {
	tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), vwin->active_var, 
			     dircol, &dir);
	build_path(fnfile, dir, fname, ".gfn");
	g_free(dir);
    } else {
	strcpy(fnfile, fname);
    }

    g_free(fname);

    if (task == LOAD_FN_PKG) {
	err = gui_load_user_functions(fnfile);
    } else if (task == DELETE_FN_PKG) {
	err = gui_delete_fn_pkg(fnfile, vwin);
    } else if (task == VIEW_FN_PKG_INFO) {
	gui_show_function_info(fnfile, VIEW_FUNC_INFO);
    } else if (task == VIEW_FN_PKG_CODE) {
	gui_show_function_info(fnfile, VIEW_FUNC_CODE);
    } else if (task == EDIT_FN_PKG) {
	edit_function_package(fnfile, &err);
    } else if (task == CALL_FN_PKG) {
	call_function_package(fnfile, vwin->w, &err);
    }

    if (!err && (task == EDIT_FN_PKG || task == CALL_FN_PKG)) {
	maybe_update_func_files_window(0);
    }
}

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

    browser_functions_handler(vwin, LOAD_FN_PKG);
} 

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

    browser_functions_handler(vwin, EDIT_FN_PKG);
}

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

    browser_functions_handler(vwin, CALL_FN_PKG);
}

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

    browser_functions_handler(vwin, VIEW_FN_PKG_INFO);
}

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

    browser_functions_handler(vwin, VIEW_FN_PKG_CODE);
}

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

    browser_functions_handler(vwin, DELETE_FN_PKG);
} 

static void set_browser_status (windata_t *vwin, int status)
{
    int i = vwin->role - TEXTBOOK_DATA;

    if (status == BROWSER_BUSY) {
	browsers[i] = vwin->w;
    } else {
	browsers[i] = NULL;
    }
} 

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

    set_browser_status(vwin, BROWSER_OK);
    free_windata(NULL, data);
}

static gpointer get_browser_ptr (int role)
{
    int i = role - TEXTBOOK_DATA;

    return (gpointer) &(browsers[i]);
}

static gpointer get_browser (int role)
{
    int i = role - TEXTBOOK_DATA;

    return browsers[i];
}

static int browser_busy (guint code)
{
    int ret = 0;

    if (code >= TEXTBOOK_DATA && code <= REMOTE_DB) {
	GtkWidget *w;

	w = browsers[code - TEXTBOOK_DATA];
	if (w != NULL) {
	    gdk_window_raise(w->window);
	    ret = 1;
	}
    }

    return ret;
}

static void new_package_callback (GtkWidget *w , gpointer p)
{
    file_save(NULL, SAVE_FUNCTIONS, NULL);
}

static void close_files_viewer (GtkWidget *w, windata_t *vwin)
{
    gtk_widget_destroy(vwin->w);
} 

static void build_datafiles_popup (windata_t *vwin)
{
    if (vwin->popup == NULL) {
	vwin->popup = gtk_menu_new();

	add_popup_item(_("Info"), vwin->popup, 
		       G_CALLBACK(display_datafile_info), 
		       vwin);
	add_popup_item(_("Open"), vwin->popup, 
		       G_CALLBACK(browser_open_data), 
		       vwin);
    }
}   

static void build_funcfiles_popup (windata_t *vwin)
{
    if (vwin->popup == NULL) {
	vwin->popup = gtk_menu_new();

	if (vwin->role == FUNC_FILES) {
	    add_popup_item(_("Edit"), vwin->popup, 
			   G_CALLBACK(browser_edit_func), 
			   vwin);
	    add_popup_item(_("Info"), vwin->popup, 
			   G_CALLBACK(display_function_info), 
			   vwin);
	    add_popup_item(_("View code"), vwin->popup, 
			   G_CALLBACK(display_function_code), 
			   vwin);
	    add_popup_item(_("Execute"), vwin->popup, 
			   G_CALLBACK(browser_call_func), 
			   vwin);
	    add_popup_item(_("Delete"), vwin->popup, 
			   G_CALLBACK(browser_del_func), 
			   vwin);
	    add_popup_item(_("New"), vwin->popup, 
			   G_CALLBACK(new_package_callback), 
			   vwin);
	} else {
	    add_popup_item(_("Info"), vwin->popup, 
			   G_CALLBACK(file_info_from_server), 
			   vwin);
	    add_popup_item(_("Install"), vwin->popup, 
			   G_CALLBACK(install_file_from_server), 
			   vwin);
	    add_popup_item(_("Execute"), vwin->popup, 
			   G_CALLBACK(browser_call_func), 
			   vwin);
	}
    }
}

enum {
    BTN_EDIT = 1,
    BTN_INFO,
    BTN_CODE,
    BTN_INDX,
    BTN_INST,
    BTN_EXEC,
    BTN_DEL,
    BTN_NEW,
    BTN_FIND,
    BTN_OPEN,
    BTN_CLOSE
};

struct files_item {
    const char *str;
    int action;
    const gchar *icon;
};

#ifndef GTK_STOCK_INFO
# define GTK_STOCK_INFO GRETL_STOCK_INFO
#endif
#ifndef GTK_STOCK_EDIT
# define GTK_STOCK_EDIT GRETL_STOCK_EDIT
#endif

static struct files_item files_items[] = {
    { N_("Open"),      BTN_OPEN,  GTK_STOCK_OK },
    { N_("Edit"),      BTN_EDIT,  GTK_STOCK_EDIT },
    { N_("Info"),      BTN_INFO,  GTK_STOCK_INFO },
    { N_("View code"), BTN_CODE,  GTK_STOCK_PROPERTIES },
    { N_("Index"),     BTN_INDX,  GTK_STOCK_INDEX },
    { N_("Install"),   BTN_INST,  GTK_STOCK_SAVE },
    { N_("Execute"),   BTN_EXEC,  GTK_STOCK_EXECUTE },
    { N_("Delete"),    BTN_DEL,   GTK_STOCK_DELETE },
    { N_("New"),       BTN_NEW,   GTK_STOCK_NEW },
    { N_("Find"),      BTN_FIND,  GTK_STOCK_FIND },
    { N_("Close"),     BTN_CLOSE, GTK_STOCK_CLOSE },
    { NULL, 0, NULL }
};

static void make_filesbar (windata_t *vwin)
{
    GtkWidget *hbox, *button;
    int i;

    hbox = gtk_hbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(vwin->vbox), hbox, FALSE, FALSE, 0);

    vwin->mbar = gtk_toolbar_new();
    gtk_box_pack_start(GTK_BOX(hbox), vwin->mbar, FALSE, FALSE, 0);

    for (i=0; files_items[i].str != NULL; i++) {
	void (*toolfunc)() = NULL;

	if (files_items[i].action == BTN_CLOSE) {
	    toolfunc = close_files_viewer;
	} else if (files_items[i].action == BTN_FIND) {
	    toolfunc = datafile_find;
	} else if (vwin->role == FUNC_FILES) {
	    if (files_items[i].action == BTN_EDIT) {
		toolfunc = browser_edit_func;
	    } else if (files_items[i].action == BTN_INFO) {
		toolfunc = display_function_info;
	    } else if (files_items[i].action == BTN_CODE) {
		toolfunc = display_function_code;
	    } else if (files_items[i].action == BTN_EXEC) {
		toolfunc = browser_call_func;
	    } else if (files_items[i].action == BTN_DEL) {
		toolfunc = browser_del_func;
	    } else if (files_items[i].action == BTN_NEW) {
		toolfunc = new_package_callback;
	    } 
	} else if (vwin->role == REMOTE_FUNC_FILES) {
	    if (files_items[i].action == BTN_INFO) {
		toolfunc = file_info_from_server;
	    } else if (files_items[i].action == BTN_INST) {
		toolfunc = install_file_from_server;
	    } else if (files_items[i].action == BTN_EXEC) {
		toolfunc = browser_call_func;
	    } 
	} else if (vwin->role == NATIVE_DB) {
	    if (files_items[i].action == BTN_INDX) {
		toolfunc = open_db_index;
	    }
	} else if (vwin->role == REMOTE_DB) {
	    if (files_items[i].action == BTN_INDX) {
		toolfunc = open_remote_db_index;
	    } else if (files_items[i].action == BTN_INST) {
		toolfunc = install_file_from_server;
	    }
	} else if (vwin->role == TEXTBOOK_DATA) {
	    if (files_items[i].action == BTN_OPEN) {
		toolfunc = browser_open_data;
	    } else if (files_items[i].action == BTN_INFO) {
		toolfunc = display_datafile_info;
	    }
	} else if (vwin->role == PS_FILES) {
	    if (files_items[i].action == BTN_OPEN) {
		toolfunc = browser_open_ps;
	    }
	}

	if (toolfunc == NULL) {
	    continue;
	}

	button = gtk_image_new();
	gtk_image_set_from_stock(GTK_IMAGE(button), files_items[i].icon, 
				 GTK_ICON_SIZE_MENU);
        gtk_toolbar_append_item(GTK_TOOLBAR(vwin->mbar),
				NULL, _(files_items[i].str), NULL,
				button, toolfunc, vwin);
    }

    gtk_widget_show(vwin->mbar);
    gtk_widget_show(hbox);
}

void display_files (gpointer p, guint code, GtkWidget *w)
{
    GtkWidget *filebox, *button;
    GtkWidget *button_box;
    windata_t *vwin;
    int col1w = 0;
    int err = 0;

    if (browser_busy(code)) {
	return;
    }

    vwin = mymalloc(sizeof *vwin);
    if (vwin == NULL) {
	return;
    }

    windata_init(vwin);

    vwin->role = code;
    vwin->w = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    g_signal_connect(G_OBJECT(vwin->w), "destroy",
		     G_CALLBACK(free_browser),
		     vwin);

    set_browser_status(vwin, BROWSER_BUSY);

    switch (code) {
    case PS_FILES:
	gtk_window_set_title(GTK_WINDOW(vwin->w), 
			     _("gretl: practice files"));
	break;
    case FUNC_FILES:
	gtk_window_set_title(GTK_WINDOW(vwin->w), 
			     _("gretl: function packages"));
	break;
    case TEXTBOOK_DATA:
	gtk_window_set_title(GTK_WINDOW(vwin->w), 
			     _("gretl: data files"));
	break;
    case NATIVE_DB:
	gtk_window_set_title(GTK_WINDOW(vwin->w), 
			     _("gretl: database files"));
	break;
    case REMOTE_DB:
	gtk_window_set_title(GTK_WINDOW(vwin->w), 
			     _("gretl: databases on server"));
	gtk_widget_set_usize(vwin->w, 640, 480);
	break;
    case REMOTE_FUNC_FILES:
	gtk_window_set_title(GTK_WINDOW(vwin->w), 
			     _("gretl: function packages on server"));
	break;
    }

    /* set up grids */
    vwin->vbox = gtk_vbox_new(FALSE, 1);
    gtk_box_set_spacing(GTK_BOX(vwin->vbox), 4);
    gtk_container_set_border_width(GTK_CONTAINER(vwin->vbox), 4);
    gtk_container_add(GTK_CONTAINER(vwin->w), vwin->vbox);

    make_filesbar(vwin);

    if (code == TEXTBOOK_DATA || code == PS_FILES) {
	filebox = files_notebook(vwin, code);
    } else {
	filebox = files_window(vwin);
    }

    gtk_box_pack_start(GTK_BOX(vwin->vbox), filebox, TRUE, TRUE, 0);

    g_object_set_data(G_OBJECT(vwin->w), "vwin", vwin);

    if (code == TEXTBOOK_DATA) { 
	file_collection *coll;

	/* create popup menu */
	build_datafiles_popup(vwin);

	while ((coll = pop_data_collection())) {
	    g_signal_connect(G_OBJECT(coll->page), "button_press_event",
			     G_CALLBACK(popup_menu_handler), 
			     vwin->popup);
	}
	reset_data_stack();
    } else if (code == FUNC_FILES || code == REMOTE_FUNC_FILES) {
	build_funcfiles_popup(vwin);
	g_signal_connect(G_OBJECT(vwin->listbox), "button_press_event",
			 G_CALLBACK(popup_menu_handler), 
			 vwin->popup);
    }

    if (REMOTE_ACTION(code)) {
	GtkWidget *hbox;

	hbox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(vwin->vbox), hbox, FALSE, FALSE, 0);
	vwin->status = gtk_label_new(_("Network status: OK"));
	gtk_label_set_justify(GTK_LABEL(vwin->status), GTK_JUSTIFY_LEFT);
	gtk_box_pack_start(GTK_BOX(hbox), vwin->status, FALSE, FALSE, 0);
    } 

    button_box = gtk_hbox_new(FALSE, 5);
    gtk_box_set_homogeneous(GTK_BOX(button_box), TRUE);
    gtk_box_pack_start(GTK_BOX(vwin->vbox), button_box, FALSE, FALSE, 0);

    button = gtk_button_new_with_label(_("Close"));
    gtk_box_pack_start(GTK_BOX(button_box), button, FALSE, TRUE, 0);
    g_signal_connect(G_OBJECT(button), "clicked",
		     G_CALLBACK(delete_widget), vwin->w);

    /* put stuff into list box(es) */
    if (code == TEXTBOOK_DATA || code == PS_FILES) {
	err = populate_notebook_filelists(vwin, filebox, code);
    } else if (code == FUNC_FILES) {
	err = populate_filelist(vwin, &col1w);
	if (col1w > 15) {
	    /* widen the file box if need be */
	    gint w, h;

	    gtk_widget_get_size_request(filebox, &w, &h);
	    w += 100;
	    gtk_widget_set_size_request(filebox, w, h);
	}
    } else {
	err = populate_filelist(vwin, NULL);
    }

    if (err) {
	gtk_widget_destroy(vwin->w);
    } else {
	gtk_widget_show_all(vwin->w); 
	gtk_widget_grab_focus(vwin->listbox);
    }
}

static int get_func_info (const char *fname, const char *fndir,
			  char **pdesc, char **pver)
{
    char fullname[FILENAME_MAX];
    int err = 0;

    build_path(fullname, fndir, fname, NULL);
    *pdesc = get_function_file_header(fullname, pver, &err);
    if (err) {
	gui_errmsg(err);
    }

    return err;
}

static int
read_fn_files_in_dir (int role, DIR *dir, const char *fndir, 
		      GtkListStore *store, GtkTreeIter *iter,
		      int *maxlen)
{
    struct dirent *dirent;
    char fullname[MAXLEN];
    gboolean loaded;
    char *fname;
    char *descrip;
    char *version;
    int n, nfn = 0;
    int err;

    while ((dirent = readdir(dir)) != NULL) {
	if (!strcmp(dirent->d_name, ".") ||
	    !strcmp(dirent->d_name, "..")) {
	    continue;
	}
	fname = g_strdup(dirent->d_name);
	if (fname == NULL) {
	    break;
	}
	n = strlen(fname);
	if (!g_ascii_strcasecmp(fname + n - 4, ".gfn")) {
	    err = get_func_info(fname, fndir, &descrip, &version);
	    if (!err) {
		gtk_list_store_append(store, iter);
		build_path(fullname, fndir, fname, NULL);
		n -= 4;
		fname[n] = '\0';
		if (n > *maxlen) {
		    *maxlen = n;
		}
		loaded = function_package_is_loaded(fullname);
		gtk_list_store_set(store, iter, 
				   0, fname, 
				   1, version,
				   2, descrip, 
				   3, loaded, 
				   4, fndir, -1);
		free(descrip);
		free(version);
		nfn++;
	    }
	}
	g_free(fname);
    }

    return nfn;
}

gint populate_func_list (windata_t *vwin, int *wid)
{
    GtkListStore *store;
    GtkTreeIter iter;
    char fndir[FILENAME_MAX];
    DIR *dir;
    int maxlen = 0;
    int nfn = 0;

    store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vwin->listbox)));
    gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);

    /* pick up any function files in system dir */

    build_path(fndir, paths.gretldir, "functions", NULL);
    dir = opendir(fndir);
    if (dir != NULL) {
	nfn += read_fn_files_in_dir(vwin->role, dir, fndir, store, &iter,
				    &maxlen);
	closedir(dir);
    }

    /* pick up any function files in the user's personal dir */

    build_path(fndir, paths.userdir, "functions", NULL);
    dir = opendir(fndir);
    if (dir != NULL) {
	nfn += read_fn_files_in_dir(vwin->role, dir, fndir, store, &iter,
				    &maxlen);
	closedir(dir);
    }

    if (nfn == 0) {
	errbox(_("No function files found"));
	/* FIXME don't leak list store? */
	return 1;
    } 

    if (wid != NULL) {
	*wid = maxlen;
    }

    return 0;
}

/* update loaded status after run, edit calls */

void maybe_update_func_files_window (int editing)
{
    GtkWidget *w = get_browser(FUNC_FILES);
    windata_t *vwin = NULL;
    GtkWidget *lbox = NULL;

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

    if (vwin != NULL) {
	lbox = vwin->listbox;
    }

    if (editing && lbox != NULL) {
	GtkListStore *store;

	store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(lbox)));	
	gtk_list_store_clear(store);
	populate_func_list(vwin, NULL);
	return;
    }

    if (lbox != NULL) {
	GtkListStore *store = 
	    GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(lbox)));
	GtkTreeIter iter;
	char fullname[MAXLEN];
	gchar *fname;
	gchar *fdir;
	gboolean loaded;

	if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter)) {
	    return;
	}

	while (1) {
	    gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 0, &fname, 
			       4, &fdir, -1);
	    sprintf(fullname, "%s%c%s.gfn", fdir, SLASH, fname);
	    g_free(fname);
	    g_free(fdir);
	    loaded = function_package_is_loaded(fullname);
	    gtk_list_store_set(store, &iter, 3, loaded, -1);
	    if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter)) {
		break;
	    }
	}
    }
}

gint populate_filelist (windata_t *vwin, gpointer p)
{
    if (vwin->role == NATIVE_DB) {
	return populate_dbfilelist(vwin);
    } else if (vwin->role == REMOTE_DB) {
	return populate_remote_db_list(vwin);
    } else if (vwin->role == REMOTE_FUNC_FILES) {
	return populate_remote_func_list(vwin);
    } else if (vwin->role == FUNC_FILES) {
	return populate_func_list(vwin, p);
    } else {
	return read_file_descriptions(vwin, p);
    }
}

static GtkWidget *files_window (windata_t *vwin) 
{
    const char *data_titles[] = {
	N_("File"), 
	N_("Summary")
    };
    const char *ps_titles[] = {
	N_("Script"), 
	N_("Topic"), 
	N_("Data")
    };
    const char *db_titles[] = {
	N_("Database"), 
	N_("Source")
    };
    const char *remote_db_titles[] = {
	N_("Database"), 
	N_("Source"), 
	N_("Local status")
    };
    const char *func_titles[] = {
	N_("Package"), 
	N_("Version"),
	N_("Summary"), 
	N_("Loaded?")
    };
    const char *remote_func_titles[] = {
	N_("Package"), 
	N_("Version"),
	N_("Summary"), 
	N_("Local status")
    };

    GType types_2[] = {
	G_TYPE_STRING,
	G_TYPE_STRING
    };
    GType types_3[] = {
	G_TYPE_STRING,
	G_TYPE_STRING,
	G_TYPE_STRING
    };
    GType types_4[] = {
	G_TYPE_STRING,
	G_TYPE_STRING,
	G_TYPE_STRING,
	G_TYPE_STRING
    };
    GType types_5[] = {
	G_TYPE_STRING,
	G_TYPE_STRING,
	G_TYPE_STRING,
	G_TYPE_BOOLEAN,
	G_TYPE_STRING
    };

    const char **titles = data_titles;
    GType *types = types_2;

    int full_width = 500, file_height = 260;
    int hidden_col = 0;
    int use_tree = 0;

    GtkWidget *box;
    int cols = 2;

    switch (vwin->role) {
    case NATIVE_DB:
	titles = db_titles;
	cols = 3;
	hidden_col = TRUE;
	break;
    case REMOTE_DB:
	titles = remote_db_titles;
	cols = 3;
	full_width = 580;
	use_tree = 1;
	break;
    case PS_FILES:
	titles = ps_titles;
	cols = 3;
	full_width = 560;
	file_height = 300;
	break;
    case FUNC_FILES:
	titles = func_titles;
	cols = 5;
	types = types_5;
	hidden_col = TRUE;
	full_width = 560;
	break;
    case REMOTE_FUNC_FILES:
	titles = remote_func_titles;
	cols = 4;
	types = types_4;
	full_width = 580;
	break;
    default:
	break;
    }

    if (cols == 3) {
	types = types_3;
    }

    full_width *= gui_scale;
    file_height *= gui_scale;

    box = gtk_vbox_new(FALSE, 0);
    gtk_widget_set_size_request(box, full_width, file_height);
    vwin_add_list_box(vwin, GTK_BOX(box), cols, hidden_col, 
		      types, titles, use_tree);
    gtk_widget_show(box);

    return box;
}

static void 
switch_file_page_callback (GtkNotebook *notebook, GtkNotebookPage *page,
			   guint page_num, windata_t *vwin)
{
    gpointer p = g_object_get_data(G_OBJECT(notebook), "browse_ptr");

    if (p != NULL) {
	GtkWidget *w = *(GtkWidget **) p;

	if (w != NULL) {
	    char wnum[4];

	    sprintf(wnum, "%d", (int) page_num);
	    vwin->listbox = g_object_get_data(G_OBJECT(notebook), wnum);
	}
    }
}

/* below: Construct a set of notebook pages for either data files
   (Ramanathan, Wooldridge, etc.) or practice scripts.  Creates the
   pages but does not yet fill them out.
*/

static GtkWidget *files_notebook (windata_t *vwin, int code)
{
    GtkWidget *notebook;
    GtkWidget *listpage;
    GtkWidget *label;
    char wnum[4];
    file_collection *coll;
    int j;

    if (code != TEXTBOOK_DATA && code != PS_FILES) {
	return NULL;
    }

    build_file_collections(); /* FIXME check for errors */

    notebook = gtk_notebook_new();

    j = 0;

    while (1) {
	if (code == TEXTBOOK_DATA) {
	    coll = pop_data_collection();
	} else {
	    coll = pop_ps_collection();
	}

	if (coll == NULL) break;

	listpage = files_window(vwin);
	label = gtk_label_new(coll->title);
	gtk_widget_show(label);
	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), listpage, label);
	coll->page = vwin->listbox;
	sprintf(wnum, "%d", j);
	g_object_set_data(G_OBJECT(notebook), wnum, coll->page);
	g_object_set_data(G_OBJECT(coll->page), "coll", coll);
	j++;
    }
    
    if (code == TEXTBOOK_DATA) {
	reset_data_stack();
    } else {
	reset_ps_stack();
    }

    g_object_set_data(G_OBJECT(GTK_NOTEBOOK(notebook)), "browse_ptr",
		      get_browser_ptr(vwin->role));
    g_signal_connect(G_OBJECT(GTK_NOTEBOOK(notebook)), "switch-page",
		     G_CALLBACK(switch_file_page_callback),
		     vwin);

    gtk_widget_show(notebook);

    return notebook;
}

/* below: fill out a set of notebook pages (for data files
   or scripts files), entering the details into the page.
*/

static int populate_notebook_filelists (windata_t *vwin, 
					GtkWidget *notebook,
					int code)
{
    file_collection *coll;
    const char *title;
    int gotcol = 0;
    int j;

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

    while (1) {
	if (code == TEXTBOOK_DATA) {
	    coll = pop_data_collection();
	} else {
	    coll = pop_ps_collection();
	}

	if (coll != NULL) {
	    gotcol = 1;
	} else {
	    break;
	}

	vwin->listbox = coll->page;
	populate_filelist(vwin, coll);
    }

    if (!gotcol) {
	return 1;
    }

    if (code == TEXTBOOK_DATA) {
	reset_data_stack();
    } else {
	reset_ps_stack();
    }

    j = 0;

    if (code == TEXTBOOK_DATA) {
	title = get_datapage();
	if (*title != '\0') {
	    while ((coll = pop_data_collection())) {
		if (!strcmp(coll->title, title)) {
		    break;
		}
		j++;
	    }
	} else {
	    coll = pop_data_collection();
	}
    } else {
	title = get_scriptpage();
	if (*title != '\0') {
	    while ((coll = pop_ps_collection())) {
		if (!strcmp(coll->title, title)) {
		    break;
		}
		j++;
	    }
	} else {
	    coll = pop_ps_collection();
	}
    }

    vwin->listbox = coll->page;

    if (code == TEXTBOOK_DATA) {
	reset_data_stack();
    } else {
	reset_ps_stack();
    }

    gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), j);

    gtk_widget_grab_focus(vwin->listbox);

    return 0;
} 







syntax highlighted by Code2HTML, v. 0.9.1