1 /*
2  *  gretl -- Gnu Regression, Econometrics and Time-series Library
3  *  Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 /* datafiles.c : for gretl */
21 
22 #define COLL_DEBUG 0
23 #define GFN_DEBUG 0
24 
25 #include "gretl.h"
26 #include "datafiles.h"
27 #include "database.h"
28 #include "filelists.h"
29 #include "gretl_www.h"
30 #include "menustate.h"
31 #include "fnsave.h"
32 #include "fncall.h"
33 #include "treeutils.h"
34 #include "selector.h"
35 #include "toolbar.h"
36 #include "winstack.h"
37 #include "fileselect.h"
38 #include "dlgutils.h"
39 #include "textbuf.h"
40 
41 #include "gretl_xml.h"
42 #include "gretl_func.h"
43 #include "addons_utils.h"
44 
45 #include <sys/stat.h>
46 #include <unistd.h>
47 #include <errno.h>
48 
49 static GtkWidget *files_vbox (windata_t *vwin);
50 static GtkWidget *files_notebook (windata_t *vwin, int role);
51 static int populate_notebook_filelists (windata_t *vwin,
52 					GtkWidget *notebook,
53 					int role);
54 static gint populate_gfn_list (windata_t *vwin);
55 
56 typedef struct _file_collection file_collection;
57 
58 struct _file_collection {
59     char *path;
60     char *descfile;
61     char *title;
62     int type;
63     GtkWidget *listbox;
64 };
65 
66 enum {
67     COLL_DATA,
68     COLL_PS,
69     COLL_MAX
70 };
71 
72 enum {
73     PKG_ATTR_RES = 1 << 0,
74     PKG_ATTR_DOC = 1 << 1
75 };
76 
77 #define GFN_DIRNAME_COL 5
78 #define GFN_FLAGS_COL 6
79 
80 #define REMOTE_ACTION(c) (c == REMOTE_DB || \
81                           c == REMOTE_FUNC_FILES || \
82                           c == REMOTE_DATA_PKGS || \
83                           c == REMOTE_ADDONS || \
84 	                  c == DBNOMICS_DB || \
85 	                  c == DBNOMICS_SERIES)
86 
87 #define DBNOMICS_ACTION(c) (c == DBNOMICS_DB || c == DBNOMICS_SERIES)
88 
89 static GList *collections[COLL_MAX];
90 
role_to_index(int role)91 static int role_to_index (int role)
92 {
93     if (role == TEXTBOOK_DATA) {
94 	return COLL_DATA;
95     } else if (role == PS_FILES) {
96 	return COLL_PS;
97     } else {
98 	return -1;
99     }
100 }
101 
collections_for_role(int role)102 static GList *collections_for_role (int role)
103 {
104     int i = role_to_index(role);
105 
106     if (i >= 0 && collections[i] != NULL) {
107 	return g_list_first(collections[i]);
108     } else {
109 	return NULL;
110     }
111 }
112 
113 static void
114 read_fn_files_in_dir (GDir *dir, const char *path,
115 		      GtkListStore *store,
116 		      GtkTreeIter *iter,
117 		      int *nfn);
118 
full_path(char * s1,const char * s2)119 static char *full_path (char *s1, const char *s2)
120 {
121     static char fpath[FILENAME_MAX];
122     int n = strlen(s1);
123 
124     if (s1[n-1] == '.') {
125 	s1[n-1] = '\0';
126 	n--;
127     }
128 
129     if (IS_SLASH(s1[n-1])) {
130 	sprintf(fpath, "%s%s", s1, s2);
131     } else {
132 	sprintf(fpath, "%s%c%s", s1, SLASH, s2);
133     }
134 
135 #if COLL_DEBUG > 1
136     fprintf(stderr, "full_path: got '%s' from '%s' + '%s'\n",
137 	    fpath, s1, s2);
138 #endif
139 
140     return fpath;
141 }
142 
143 /* check for a few known, older, file collections whose
144    descriptions files do not conform to the now-standard
145    pattern
146 */
147 
is_oldstyle_collection(file_collection * coll,int * err)148 static int is_oldstyle_collection (file_collection *coll, int *err)
149 {
150     const file_collection std_data[] = {
151 	{ "wooldridge", "jw_descriptions", "Wooldridge", COLL_DATA, NULL },
152 	{ "gujarati", "dg_descriptions", "Gujarati", COLL_DATA, NULL },
153 	{ "pwt56", "descriptions", "PWT 56", COLL_DATA, NULL }
154     };
155     const file_collection std_ps = {
156 	"pwt56", "ps_descriptions", "PWT 56", COLL_PS, NULL
157     };
158     int i;
159 
160     for (i=0; i<3; i++) {
161 	if (strstr(coll->path, std_data[i].path) &&
162 	    !strcmp(coll->descfile, std_data[i].descfile)) {
163 	    coll->title = gretl_strdup(std_data[i].title);
164 	    if (coll->title == NULL) {
165 		*err = E_ALLOC;
166 	    } else {
167 		coll->type = COLL_DATA;
168 	    }
169 	    return 1;
170 	}
171     }
172 
173     if (strstr(coll->path, std_ps.path) &&
174 	!strcmp(coll->descfile, std_ps.descfile)) {
175 	coll->title = gretl_strdup(std_ps.title);
176 	if (coll->title == NULL) {
177 	    *err = E_ALLOC;
178 	} else {
179 	    coll->type = COLL_PS;
180 	}
181 	return 1;
182     }
183 
184     return 0;
185 }
186 
187 /* return non-zero only on fatal error */
188 
get_title_from_descfile(file_collection * coll)189 static int get_title_from_descfile (file_collection *coll)
190 {
191     char line[64], title[24];
192     char *test;
193     FILE *fp;
194     int err = 0;
195 
196     test = full_path(coll->path, coll->descfile);
197     fp = gretl_fopen(test, "rb");
198 
199     if (fp != NULL && fgets(line, sizeof line, fp) != NULL) {
200 	gretl_strstrip(line);
201 	if (sscanf(line, "# %23[^:]", title) == 1) {
202 	    coll->title = gretl_strdup(title);
203 	    if (coll->title == NULL) {
204 		err = E_ALLOC;
205 	    }
206 	}
207     }
208 
209     if (fp != NULL) {
210 	fclose(fp);
211     }
212 
213     return err;
214 }
215 
free_file_collection(file_collection * coll)216 static void free_file_collection (file_collection *coll)
217 {
218     free(coll->path);
219     free(coll->descfile);
220     free(coll->title);
221     free(coll);
222 }
223 
file_collection_new(const char * path,const char * descfile,int * err)224 static file_collection *file_collection_new (const char *path,
225 					     const char *descfile,
226 					     int *err)
227 {
228     file_collection *coll = malloc(sizeof *coll);
229 
230     if (coll == NULL) {
231 	*err = E_ALLOC;
232 	return NULL;
233     }
234 
235     coll->type = -1;
236     coll->title = NULL;
237     coll->path = gretl_strdup(path);
238     coll->descfile = gretl_strdup(descfile);
239 
240     if (coll->path == NULL || coll->descfile == NULL) {
241 	*err = E_ALLOC;
242     } else {
243 	int os = is_oldstyle_collection(coll, err);
244 
245 	if (!*err && !os) {
246 	    if (strstr(coll->descfile, "ps_")) {
247 		coll->type = COLL_PS;
248 	    } else {
249 		coll->type = COLL_DATA;
250 	    }
251 	    *err = get_title_from_descfile(coll);
252 	}
253     }
254 
255     if (*err || coll->title == NULL) {
256 	free_file_collection(coll);
257 	coll = NULL;
258     }
259 
260     return coll;
261 }
262 
compare_colls(const void * a,const void * b)263 static int compare_colls (const void *a, const void *b)
264 {
265     const file_collection *ca = a;
266     const file_collection *cb = b;
267 
268     if (!strcmp(ca->title, "Gretl")) {
269 	return -1;
270     } else if (!strcmp(cb->title, "Gretl")) {
271 	return 1;
272     } else {
273 	return strcmp(ca->title, cb->title);
274     }
275 }
276 
push_collection(file_collection * collection)277 static void push_collection (file_collection *collection)
278 {
279     int i = collection->type;
280 
281     collections[i] = g_list_append(collections[i], collection);
282 }
283 
destroy_file_collections(void)284 void destroy_file_collections (void)
285 {
286     GList *L;
287     int i;
288 
289     for (i=0; i<COLL_MAX; i++) {
290 	if (collections[i] != NULL) {
291 	    L = g_list_first(collections[i]);
292 	    while (L) {
293 		free_file_collection(L->data);
294 		L = L->next;
295 	    }
296 	    g_list_free(collections[i]);
297 	    collections[i] = NULL;
298 	}
299     }
300 }
301 
sort_files_stack(int role)302 static void sort_files_stack (int role)
303 {
304     int i = role_to_index(role);
305 
306     if (i >= 0 && collections[i] != NULL) {
307 	collections[i] = g_list_sort(collections[i], compare_colls);
308     }
309 }
310 
311 /* Returns the number of file collections found and pushed;
312    writes non-zero to @err if something show-stopping
313    occurs
314 */
315 
get_file_collections_from_dir(const char * path,GDir * dir,int * err)316 static int get_file_collections_from_dir (const char *path, GDir *dir,
317 					  int *err)
318 {
319     file_collection *coll;
320     const gchar *dname;
321     int n = 0;
322 
323     while (!*err && (dname = g_dir_read_name(dir))) {
324 	/* we're looking for a filename that ends with "descriptions" */
325 	if (strstr(dname, "descriptions")) {
326 	    size_t len = strlen(dname);
327 
328 #if COLL_DEBUG
329 	    fprintf(stderr, "   %s: looking at '%s'\n", path, dname);
330 #endif
331 	    if (!strcmp(dname + len - 12, "descriptions")) {
332 		coll = file_collection_new(path, dname, err);
333 		if (coll != NULL) {
334 		    push_collection(coll);
335 		    n++;
336 		}
337 	    }
338 	}
339     }
340 
341     return n;
342 }
343 
dont_go_there(const char * s)344 static int dont_go_there (const char *s)
345 {
346     int ret = 0;
347 
348     if (!strcmp(s, "..") || strstr(s, ".inp") || strstr(s, ".gdt") ||
349 	strstr(s, ".gretl") || strstr(s, ".hdr")) {
350 	ret = 1;
351     }
352 
353     return ret;
354 }
355 
356 /* Returns the number of collections found; @err is set to
357    non-zero only if something show-stopping occurs
358 */
359 
seek_file_collections(const char * basedir,SearchType stype,int * err)360 static int seek_file_collections (const char *basedir,
361 				  SearchType stype,
362 				  int *err)
363 {
364     gchar *path = NULL;
365     GDir *topdir;
366     const char *dname;
367     int n_coll = 0;
368 
369 #if COLL_DEBUG
370     fprintf(stderr, "*** seek_file_collections: basedir='%s', type=%d\n",
371 	    basedir, stype);
372 #endif
373 
374     if (stype == DATA_SEARCH) {
375 	path = g_strdup_printf("%sdata", basedir);
376     } else if (stype == SCRIPT_SEARCH) {
377 	path = g_strdup_printf("%sscripts", basedir);
378     } else {
379 	/* USER_SEARCH */
380 	path = g_strdup(basedir);
381 	trim_slash(path);
382     }
383 
384     topdir = gretl_opendir(path);
385     if (topdir == NULL) {
386 	g_free(path);
387 	return 0;
388     }
389 
390 #if COLL_DEBUG
391     fprintf(stderr, "*** seek_file_collections: path='%s'\n", path);
392 #endif
393 
394     while (!*err && (dname = g_dir_read_name(topdir))) {
395 	if (!dont_go_there(dname)) {
396 	    gchar *subpath;
397 	    GDir *subdir;
398 
399 #if COLL_DEBUG > 1
400 	    fprintf(stderr, " dname = '%s'\n", dname);
401 #endif
402 	    subpath = g_build_path("/", path, dname, NULL);
403 	    subdir = gretl_opendir(subpath);
404 	    if (subdir != NULL) {
405 #if COLL_DEBUG
406 		fprintf(stderr, " trying in subdir '%s'\n", subpath);
407 #endif
408 		n_coll += get_file_collections_from_dir(subpath, subdir, err);
409 #if COLL_DEBUG
410 		if (*err) {
411 		    fprintf(stderr, " result: err = %d\n", *err);
412 		}
413 #endif
414 		g_dir_close(subdir);
415 	    }
416 	    g_free(subpath);
417 	}
418     }
419 
420     g_dir_close(topdir);
421     g_free(path);
422 
423 #if COLL_DEBUG
424     fprintf(stderr, "*** found %d collections\n", n_coll);
425 #endif
426 
427     return n_coll;
428 }
429 
430 #if COLL_DEBUG
431 
print_collection(const file_collection * coll)432 static void print_collection (const file_collection *coll)
433 {
434     fprintf(stderr, "path = '%s'\n", coll->path);
435     fprintf(stderr, "descfile = '%s'\n", coll->descfile);
436     if (coll->title != NULL && *coll->title != '\0') {
437 	fprintf(stderr, "title = '%s'\n", coll->title);
438     }
439 }
440 
print_collections(int role)441 static void print_collections (int role)
442 {
443     GList *L;
444     int i;
445 
446     if (role == TEXTBOOK_DATA) {
447 	fputs("\n*** Data collections:\n", stderr);
448     } else {
449 	fputs("\n*** Script collections:\n", stderr);
450     }
451 
452     for (i=0; i<COLL_MAX; i++) {
453 	L = g_list_first(collections[i]);
454 	while (L) {
455 	    print_collection(L->data);
456 	    L = L->next;
457 	}
458     }
459 }
460 
461 #endif /* COLL_DEBUG */
462 
build_file_collections(int role)463 static int build_file_collections (int role)
464 {
465     static int built;
466     static int err;
467 
468     if (!built && !err) {
469 	const char *wd;
470 	int derr[3] = {0};
471 	int serr[3] = {0};
472 	int uerr[2] = {0};
473 	int nd = 0;
474 	int ns = 0;
475 	int nu = 0;
476 	int i;
477 
478 	gretl_error_clear();
479 
480 	nd += seek_file_collections(gretl_home(), DATA_SEARCH, &derr[0]);
481 	ns += seek_file_collections(gretl_home(), SCRIPT_SEARCH, &serr[0]);
482 #ifdef OS_OSX
483 	nd += seek_file_collections(gretl_app_support_dir(), DATA_SEARCH, &derr[1]);
484 	ns += seek_file_collections(gretl_app_support_dir(), SCRIPT_SEARCH, &serr[1]);
485 #else
486 	nd += seek_file_collections(gretl_dotdir(), DATA_SEARCH, &derr[1]);
487 	ns += seek_file_collections(gretl_dotdir(), SCRIPT_SEARCH, &serr[1]);
488 #endif
489 
490 	nu += seek_file_collections(gretl_workdir(), USER_SEARCH, &uerr[0]);
491 	wd = maybe_get_default_workdir();
492 	if (wd != NULL) {
493 	    nu += seek_file_collections(wd, USER_SEARCH, &uerr[1]);
494 	    nd += seek_file_collections(wd, DATA_SEARCH, &derr[2]);
495 	    ns += seek_file_collections(wd, SCRIPT_SEARCH, &serr[2]);
496 	}
497 
498 	for (i=0; i<3; i++) {
499 	    /* help to diagnose any errors? */
500 	    if (derr[i]) {
501 		fprintf(stderr, "data seek %d gave error %d\n", i+1, derr[i]);
502 	    }
503 	    if (serr[i]) {
504 		fprintf(stderr, "script seek %d gave error %d\n", i+1, serr[i]);
505 	    }
506 	    if (i < 2 && uerr[i]) {
507 		fprintf(stderr, "user file seek %d gave error %d\n", i+1, uerr[i]);
508 	    }
509 	}
510 	if (role == TEXTBOOK_DATA) {
511 	    if (nd == 0) {
512 		gretl_errmsg_ensure("file_collections: no data files found");
513 		errbox(gretl_errmsg_get());
514 		err = 1;
515 	    }
516 	} else if (role == PS_FILES) {
517 	    if (ns == 0) {
518 		gretl_errmsg_ensure("file_collections: no script files found");
519 		errbox(gretl_errmsg_get());
520 		err = 1;
521 	    }
522 	}
523 	if (!err) {
524 	    if (nd > 0) {
525 		sort_files_stack(TEXTBOOK_DATA);
526 	    }
527 	    if (ns > 0) {
528 		sort_files_stack(PS_FILES);
529 	    }
530 	}
531 	built = 1;
532     }
533 
534 #if COLL_DEBUG
535     print_collections(TEXTBOOK_DATA);
536     print_collections(PS_FILES);
537 #endif
538 
539     return err;
540 }
541 
strip_extension(char * s)542 char *strip_extension (char *s)
543 {
544     char *p = strstr(s, ".tar.gz");
545 
546     if (p != NULL) {
547 	char *q = strstr(s, "_data");
548 
549 	if (q != NULL) {
550 	    *q = '\0';
551 	} else {
552 	    *p = '\0';
553 	}
554     } else {
555 	p = strrchr(s, '.');
556 
557 	if (p != NULL &&
558 	    (!strcmp(p, ".gdt") || !strcmp(p, ".inp") ||
559 	     !strcmp(p, ".bin") || !strcmp(p, ".gfn") ||
560 	     !strcmp(p, ".bn7") || !strcmp(p, ".zip"))) {
561 	    *p = '\0';
562 	}
563     }
564 
565     return s;
566 }
567 
validate_desc_strings(const char * s1,const char * s2,const char * s3)568 static int validate_desc_strings (const char *s1,
569 				  const char *s2,
570 				  const char *s3)
571 {
572     int err = 0;
573 
574     if (!g_utf8_validate(s1, -1, NULL)) {
575 	err = E_DATA;
576     } else if (!g_utf8_validate(s2, -1, NULL)) {
577 	err = E_DATA;
578     } else if (s3 != NULL && !g_utf8_validate(s3, -1, NULL)) {
579 	err = E_DATA;
580     }
581 
582     return err;
583 }
584 
read_file_descriptions(windata_t * win,gpointer p)585 static int read_file_descriptions (windata_t *win, gpointer p)
586 {
587     file_collection *collection = (file_collection *) p;
588     GtkListStore *store;
589     GtkTreeSelection *selection;
590     GtkTreeIter iter;
591     char line[MAXLEN];
592     char *index;
593     FILE *fp;
594     int datacols = 0;
595     int err = 0;
596 
597     index = full_path(collection->path, collection->descfile);
598 
599     fp = gretl_fopen(index, "r");
600     if (fp == NULL) {
601 	return E_FOPEN;
602     }
603 
604     store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(win->listbox)));
605     gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
606 
607     while (fgets(line, sizeof line, fp) && !err) {
608 	char fname[24], descrip[80], data[64];
609 	int nf;
610 
611 	if (*line == '#') continue;
612 
613 	nf = sscanf(line, " \"%23[^\"]\",\"%79[^\"]\",\"%63[^\"]\"",
614 		    fname, descrip, data);
615 
616 	if (nf == 3) {
617 	    err = validate_desc_strings(fname, descrip, data);
618 	    if (!err) {
619 		datacols = 3;
620 		gtk_list_store_append(store, &iter);
621 		gtk_list_store_set(store, &iter,
622 				   0, strip_extension(fname),
623 				   1, descrip,
624 				   2, data, -1);
625 	    }
626 	} else if (nf == 2) {
627 	    err = validate_desc_strings(fname, descrip, NULL);
628 	    if (!err) {
629 		datacols = 2;
630 		gtk_list_store_append(store, &iter);
631 		gtk_list_store_set(store, &iter,
632 				   0, strip_extension(fname),
633 				   1, descrip, -1);
634 	    }
635 	} else {
636 	    ; /* ?? */
637 	}
638     }
639 
640     fclose(fp);
641 
642     if (!err && datacols == 2) {
643 	/* these windows can have either 2 or 3 columns */
644 	GtkTreeViewColumn *col;
645 
646 	col = gtk_tree_view_get_column(GTK_TREE_VIEW(win->listbox), 2);
647 	if (col != NULL) {
648 	    gtk_tree_view_column_set_visible(col, FALSE);
649 	}
650     }
651 
652     if (!err) {
653 	/* select the first row */
654 	gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
655 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(win->listbox));
656 	gtk_tree_selection_select_iter(selection, &iter);
657     }
658 
659     return err;
660 }
661 
show_datafile_info(GtkWidget * w,gpointer data)662 static void show_datafile_info (GtkWidget *w, gpointer data)
663 {
664     char fullname[MAXLEN];
665     windata_t *vwin = (windata_t *) data;
666     file_collection *collection;
667     char *descrip;
668     gchar *filename;
669     int err = 0;
670 
671     tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), vwin->active_var,
672 			 0, &filename);
673     collection = g_object_get_data(G_OBJECT(vwin->listbox), "collection");
674     gretl_build_path(fullname, collection->path, filename, NULL);
675     strcat(fullname, ".gdt");
676     g_free(filename);
677 
678 #if 0
679     fprintf(stderr, "info: active=%d, fullname='%s'\n", vwin->active_var,
680 	    fullname);
681     fprintf(stderr, "collection path='%s'\n", collection->path);
682 #endif
683 
684     descrip = gretl_get_gdt_description(fullname, &err);
685 
686     if (err) {
687 	gui_errmsg(err);
688     } else {
689 	gchar *title = g_strdup_printf("gretl: %s", _("data info"));
690 	PRN *prn;
691 
692 	prn = gretl_print_new_with_buffer(descrip);
693 	view_buffer(prn, 80, 320, title, INFO, NULL);
694 	g_free(title);
695     }
696 }
697 
browser_open_data(GtkWidget * w,gpointer data)698 void browser_open_data (GtkWidget *w, gpointer data)
699 {
700     windata_t *vwin = (windata_t *) data;
701     file_collection *collection;
702     char tmp[MAXLEN];
703     gchar *filename;
704 
705     tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), vwin->active_var,
706 			 0, &filename);
707     collection = g_object_get_data(G_OBJECT(vwin->listbox), "collection");
708     gretl_build_path(tmp, collection->path, filename, NULL);
709     strcat(tmp, ".gdt");
710     set_tryfile(tmp);
711     g_free(filename);
712 
713     set_datapage(collection->title);
714 
715     verify_open_data(vwin, OPEN_DATA);
716 }
717 
browser_open_ps(GtkWidget * w,gpointer data)718 void browser_open_ps (GtkWidget *w, gpointer data)
719 {
720     windata_t *vwin = (windata_t *) data;
721     file_collection *collection;
722     gchar *filename;
723 
724     tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), vwin->active_var,
725 			 0, &filename);
726     collection = g_object_get_data(G_OBJECT(vwin->listbox), "collection");
727     gretl_build_path(scriptfile, collection->path, filename, NULL);
728     strcat(scriptfile, ".inp");
729     g_free(filename);
730 
731     /* close the calling window */
732     gtk_widget_destroy(GTK_WIDGET(vwin->main));
733 
734     set_scriptpage(collection->title);
735 
736     view_script(scriptfile, 0, VIEW_SCRIPT);
737 }
738 
enter_opens_file(GtkWidget * w,GdkEventKey * key,windata_t * vwin)739 static gint enter_opens_file (GtkWidget *w, GdkEventKey *key,
740 			      windata_t *vwin)
741 {
742     if (key->keyval == GDK_Return || key->keyval == GDK_o) {
743 	if (vwin->role == TEXTBOOK_DATA) {
744 	    browser_open_data(w, vwin);
745 	} else if (vwin->role == PS_FILES) {
746 	    browser_open_ps(w, vwin);
747 	}
748 	return TRUE;
749     } else {
750 	return FALSE;
751     }
752 }
753 
browser_delete_current_row(windata_t * vwin)754 static void browser_delete_current_row (windata_t *vwin)
755 {
756     GtkTreeModel *mod;
757     GtkTreeIter iter;
758     int i;
759 
760     mod = gtk_tree_view_get_model(GTK_TREE_VIEW(vwin->listbox));
761     if (!gtk_tree_model_get_iter_first(mod, &iter)) {
762 	return;
763     }
764 
765     for (i=0; ; i++) {
766 	if (i == vwin->active_var) {
767 	    gtk_list_store_remove(GTK_LIST_STORE(mod), &iter);
768 	    break;
769 	} else if (!gtk_tree_model_iter_next(mod, &iter)) {
770 	    break;
771 	}
772     }
773 }
774 
browser_delete_row_by_content(windata_t * vwin,int colnum1,const char * test1,int colnum2,const char * test2)775 static void browser_delete_row_by_content (windata_t *vwin,
776 					   int colnum1,
777 					   const char *test1,
778 					   int colnum2,
779 					   const char *test2)
780 {
781     GtkTreeModel *mod;
782     GtkTreeIter iter;
783     gchar *content;
784     int done = 0;
785 
786     mod = gtk_tree_view_get_model(GTK_TREE_VIEW(vwin->listbox));
787     if (!gtk_tree_model_get_iter_first(mod, &iter)) {
788 	return;
789     }
790 
791     while (1) {
792 	gtk_tree_model_get(mod, &iter, colnum1, &content, -1);
793 	if (content != NULL && !strcmp(content, test1)) {
794 	    if (test2 == NULL) {
795 		done = 1;
796 	    } else {
797 		g_free(content);
798 		gtk_tree_model_get(mod, &iter, colnum2, &content, -1);
799 		if (content != NULL && !strcmp(content, test2)) {
800 		    done = 1;
801 		}
802 	    }
803 	    if (done) {
804 		gtk_list_store_remove(GTK_LIST_STORE(mod), &iter);
805 	    }
806 	}
807 	g_free(content);
808 	if (done || !gtk_tree_model_iter_next(mod, &iter)) {
809 	    break;
810 	}
811     }
812 }
813 
gui_delete_fn_pkg(const char * pkgname,const char * fname,windata_t * vwin)814 static int gui_delete_fn_pkg (const char *pkgname, const char *fname,
815 			      windata_t *vwin)
816 {
817     gchar *msg = NULL;
818     int delete_ok = 0;
819     int delete = 0;
820     int loaded = 0;
821     int unload = 0;
822     int resp, err = 0;
823 
824     if (package_being_edited(pkgname, NULL)) {
825 	warnbox_printf(_("%s: please close this object's window first"),
826 		       pkgname);
827 	return 0;
828     }
829 
830     /* see if the package is loaded in memory */
831     if (get_function_package_by_name(pkgname) != NULL) {
832 	loaded = 1;
833     }
834 
835     /* and see if the user is able to delete the package */
836     if (gretl_write_access((char *) fname) == 0) {
837 	delete_ok = 1;
838     }
839 
840     if (!loaded && !delete_ok) {
841 	infobox_printf(_("Package %s is not loaded, and you do "
842 			 "not have permission to delete it."),
843 		       pkgname);
844 	return 0;
845     }
846 
847     if (loaded && delete_ok) {
848 	const char *opts[] = {
849 	    N_("Unload member functions only"),
850 	    N_("Unload and delete package file"),
851 	};
852 
853 	msg = g_strdup_printf(_("Function package %s"), fname);
854 	resp = radio_dialog(NULL, msg, opts, 2, 0, 0, vwin_toplevel(vwin));
855 	g_free(msg);
856 	if (resp < 0) {
857 	    /* canceled */
858 	    return 0;
859 	} else if (resp == 1) {
860 	    delete = 1;
861 	} else {
862 	    unload = 1;
863 	}
864     } else if (loaded) {
865 	msg = g_strdup_printf(_("Unload package %s?"), pkgname);
866 	resp = yes_no_dialog(NULL, msg, vwin_toplevel(vwin));
867 	g_free(msg);
868 	if (resp == GRETL_NO) {
869 	    return 0;
870 	} else {
871 	    unload = 1;
872 	}
873     } else if (delete_ok) {
874 	msg = g_strdup_printf(_("Really delete %s?"), pkgname);
875 	resp = yes_no_dialog(NULL, msg, vwin_toplevel(vwin));
876 	g_free(msg);
877 	if (resp == GRETL_NO) {
878 	    return 0;
879 	} else {
880 	    delete = 1;
881 	}
882     }
883 
884     if (unload && !delete) {
885 	/* just unload the package from memory */
886 	function_package_unload_full_by_filename(fname);
887     } else if (delete) {
888 	/* remove entry from registry, if present */
889 	gui_function_pkg_unregister(pkgname);
890 	/* unload the package from memory */
891 	if (loaded) {
892 	    function_package_unload_full_by_filename(fname);
893 	}
894 	/* trash the package file(s) */
895 	err = delete_function_package(fname);
896 	if (err) {
897 	    gui_errmsg(err);
898 	} else {
899 	    /* remove package from GUI listing */
900 	    browser_delete_current_row(vwin);
901 	}
902     }
903 
904     return err;
905 }
906 
get_info_width(const char * buf)907 static int get_info_width (const char *buf)
908 {
909     char line[1024];
910     int n, width = 66;
911 
912     bufgets_init(buf);
913 
914     while (bufgets(line, sizeof line, buf)) {
915 	n = strlen(line);
916 	if (n > width && n <= HELP_WIDTH) {
917 	    width = n;
918 	}
919     }
920 
921     bufgets_finalize(buf);
922 
923     return width + 1;
924 }
925 
display_function_package_data(const char * pkgname,const char * path,int role)926 windata_t *display_function_package_data (const char *pkgname,
927 					  const char *path,
928 					  int role)
929 {
930     windata_t *vwin = NULL;
931     PRN *prn = NULL;
932     int err = 0;
933 
934     if (bufopen(&prn)) {
935 	return NULL;
936     }
937 
938     if (role == VIEW_PKG_INFO) {
939 	err = print_function_package_info(path, 1, prn);
940     } else if (role == VIEW_PKG_SAMPLE) {
941 	err = print_function_package_sample(path, tabwidth, prn);
942     } else {
943 	err = print_function_package_code(path, tabwidth, prn);
944     }
945 
946     if (err) {
947 	gretl_print_destroy(prn);
948 	gui_errmsg(err);
949     } else {
950 	gchar *title;
951 
952 	if (role == VIEW_PKG_SAMPLE) {
953 	    title = g_strdup_printf("gretl: %s sample", pkgname);
954 	} else {
955 	    title = g_strdup_printf("gretl: %s", pkgname);
956 	}
957 
958 	if (role == VIEW_PKG_INFO) {
959 	    char *buf = gretl_print_steal_buffer(prn);
960 	    int width = get_info_width(buf);
961 
962 	    vwin = view_formatted_text_buffer(title, buf, width, 350, role);
963 	    free(buf);
964 	    gretl_print_destroy(prn);
965 	} else {
966 	    vwin = view_buffer(prn, 78, 350, title, role, NULL);
967 	}
968 	strcpy(vwin->fname, path);
969 	if (strstr(path, "dltmp")) {
970 	    set_window_delete_filename(vwin);
971 	}
972 	g_free(title);
973     }
974 
975     return vwin;
976 }
977 
978 /* on adding files to gfn view window: ensure that the GTK
979    selection stays in sync with the "active_var" ID
980 */
981 
fix_selected_row(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)982 static void fix_selected_row (GtkTreeModel *model,
983 			      GtkTreePath *path,
984 			      GtkTreeIter *iter,
985 			      gpointer data)
986 {
987     gint idx = gtk_tree_path_get_indices(path)[0];
988     windata_t *vwin = data;
989 
990     vwin->active_var = idx;
991 }
992 
993 /* callback from the file selector where the user has chosen
994    a directory at which to point the gfn browser
995 */
996 
set_alternate_gfn_dir(windata_t * vwin,char * path)997 void set_alternate_gfn_dir (windata_t *vwin, char *path)
998 {
999     GDir *dir;
1000     int replace = 1;
1001     int nfn = 0;
1002 
1003 #if GFN_DEBUG
1004     fprintf(stderr, "set_alternate_gfn_dir: '%s'\n", path);
1005 #endif
1006 
1007     dir = gretl_opendir(path);
1008     if (dir == NULL) {
1009 	/* should never happen, but... */
1010 	return;
1011     }
1012 
1013     /* first pass: just count gfn files in @path */
1014     read_fn_files_in_dir(dir, path, NULL, NULL, &nfn);
1015 
1016     if (nfn == 0) {
1017 	warnbox(_("No function files were found"));
1018 	replace = 0;
1019     } else {
1020 	/* give the user a chance to back out */
1021 	gchar *msg;
1022 
1023 	msg = g_strdup_printf(_("Found %d function file(s).\n"
1024 				"Replace the current listing?"),
1025 			      nfn);
1026 	if (yes_no_dialog("gretl", msg, vwin->main) != GRETL_YES) {
1027 	    replace = 0;
1028 	}
1029 	g_free(msg);
1030     }
1031 
1032     if (replace) {
1033 	/* OK: now rewind, clear, and repopulate the listing using
1034 	   the @path selected by the user
1035 	*/
1036 	GtkListStore *store;
1037 	GtkTreeSelection *sel;
1038 	GtkTreeIter iter;
1039 	gulong sigid;
1040 	int nfn0 = nfn;
1041 
1042 	store = GTK_LIST_STORE(gtk_tree_view_get_model
1043 			       (GTK_TREE_VIEW(vwin->listbox)));
1044 	g_dir_rewind(dir);
1045 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(vwin->listbox));
1046 	sigid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(sel), "menu_check"));
1047 	g_signal_handler_block(sel, sigid);
1048 	gtk_list_store_clear(store);
1049 	g_signal_handler_unblock(sel, sigid);
1050 	nfn = 0;
1051 	gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
1052 	read_fn_files_in_dir(dir, path, store, &iter, &nfn);
1053 
1054 	if (nfn > 0) {
1055 	    gtk_tree_selection_selected_foreach(sel, fix_selected_row, vwin);
1056 	    widget_set_int(vwin->listbox, "altdir", 1);
1057 	    presort_treelist(vwin);
1058 	    listbox_select_first(vwin);
1059 	} else {
1060 	    /* can't happen? */
1061 	    warnbox(_("No function files were found"));
1062 	}
1063 
1064 	if (nfn > 0 && nfn < nfn0) {
1065 	    gchar *msg;
1066 
1067 	    msg = g_strdup_printf("Ignored %d duplicated file(s)", nfn0 - nfn);
1068 	    msgbox(msg, GTK_MESSAGE_WARNING, vwin->main);
1069 	    g_free(msg);
1070 	}
1071     }
1072 
1073     g_dir_close(dir);
1074 }
1075 
gfn_browser_get_alt_path(void)1076 gchar *gfn_browser_get_alt_path (void)
1077 {
1078     windata_t *vwin = get_browser_for_role(FUNC_FILES, NULL);
1079     gchar *path = NULL;
1080 
1081     if (vwin != NULL && widget_get_int(vwin->listbox, "altdir")) {
1082 	tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), 0,
1083 			     GFN_DIRNAME_COL, &path);
1084     }
1085 
1086     return path;
1087 }
1088 
query_remove_gfn_from_registry(const char * pkgname,windata_t * vwin)1089 static void query_remove_gfn_from_registry (const char *pkgname,
1090 					    windata_t *vwin)
1091 {
1092     gchar *msg;
1093     int resp;
1094 
1095     msg = g_strdup_printf(_("Really remove %s from menu?"), pkgname);
1096     resp = yes_no_dialog(NULL, msg, vwin->main);
1097 
1098     if (resp == GRETL_YES) {
1099 	gui_function_pkg_unregister(pkgname);
1100     }
1101 
1102     g_free(msg);
1103 }
1104 
browser_functions_handler(windata_t * vwin,int task)1105 static void browser_functions_handler (windata_t *vwin, int task)
1106 {
1107     char path[FILENAME_MAX];
1108     gchar *pkgname = NULL;
1109     gchar *dir = NULL;
1110     int dircol = 0;
1111 
1112     if (vwin->role == DBNOMICS_TOP && task == VIEW_PKG_DOC) {
1113 	/* special case: not coming from gfn window */
1114 	gchar *docpath = g_build_filename(gretl_home(), "functions",
1115 					  "dbnomics", "dbnomics.pdf",
1116 					  NULL);
1117 	gretl_show_pdf(docpath, NULL);
1118 	g_free(docpath);
1119 	return;
1120     }
1121 
1122     if (vwin->role == FUNC_FILES) {
1123 	dircol = GFN_DIRNAME_COL;
1124     } else if (vwin->role != REMOTE_FUNC_FILES &&
1125 	       vwin->role != PKG_REGISTRY) {
1126 	dummy_call();
1127 	return;
1128     }
1129 
1130     tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), vwin->active_var,
1131 			 0, &pkgname);
1132 
1133     if (dircol != 0) {
1134 	tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), vwin->active_var,
1135 			     dircol, &dir);
1136 	if (task == VIEW_PKG_RESOURCES) {
1137 	    gretl_build_path(path, dir, "examples", NULL);
1138 	} else if (task == VIEW_PKG_DOC) {
1139 	    gretl_build_path(path, dir, pkgname, NULL);
1140 	    strcat(path, ".pdf");
1141 	} else {
1142 	    gretl_build_path(path, dir, pkgname, NULL);
1143 	    strcat(path, ".gfn");
1144 	}
1145     } else {
1146 	strcpy(path, pkgname);
1147     }
1148 
1149     if (vwin->role == FUNC_FILES && task != DELETE_FN_PKG) {
1150 	/* try to ensure we don't get a stale version */
1151 	char test[FILENAME_MAX];
1152 	const char *loaded = NULL;
1153 	gchar *version = NULL;
1154 
1155 	gretl_build_path(test, dir, pkgname, NULL);
1156 	strcat(test, ".gfn");
1157 	if (function_package_is_loaded(test, &loaded)) {
1158 	    tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), vwin->active_var,
1159 				 1, &version);
1160 	    if (loaded != NULL && version != NULL &&
1161 		strcmp(version, loaded)) {
1162 		function_package_unload_by_filename(test);
1163 	    }
1164 	}
1165 	g_free(version);
1166     }
1167 
1168 #if GFN_DEBUG
1169     fprintf(stderr, "browser_functions_handler: active=%d, pkgname='%s'\n"
1170 	    "path='%s'\n", vwin->active_var, pkgname, path);
1171 #endif
1172 
1173     if (task == DELETE_FN_PKG) {
1174 	gui_delete_fn_pkg(pkgname, path, vwin);
1175     } else if (task == VIEW_FN_PKG_INFO) {
1176 	display_function_package_data(pkgname, path, VIEW_PKG_INFO);
1177     } else if (task == VIEW_FN_PKG_SAMPLE) {
1178 	display_function_package_data(pkgname, path, VIEW_PKG_SAMPLE);
1179     } else if (task == VIEW_FN_PKG_CODE) {
1180 	display_function_package_data(pkgname, path, VIEW_PKG_CODE);
1181     } else if (task == MENU_ADD_FN_PKG) {
1182 	gui_function_pkg_query_register(path, vwin->main);
1183     } else if (task == MENU_REMOVE_FN_PKG) {
1184 	query_remove_gfn_from_registry(pkgname, vwin);
1185     } else if (task == VIEW_PKG_RESOURCES) {
1186 	file_selector_with_startdir(OPEN_ANY, path, vwin_toplevel(vwin));
1187     } else if (task == VIEW_PKG_DOC) {
1188 	gretl_show_pdf(path, NULL);
1189     } else if (task == CALL_FN_PKG) {
1190 	/* note: this is the double-click default for the local
1191 	   function package browser; can also be invoked via
1192 	   context menu and toolbar button
1193 	*/
1194 	open_function_package(pkgname, path, vwin);
1195     }
1196 
1197     g_free(pkgname);
1198     g_free(dir);
1199 }
1200 
show_addon_info(GtkWidget * w,gpointer data)1201 static void show_addon_info (GtkWidget *w, gpointer data)
1202 {
1203     windata_t *vwin = (windata_t *) data;
1204     gchar *pkgname = NULL;
1205     gchar *descrip = NULL;
1206     gchar *status = NULL;
1207     int v = vwin->active_var;
1208 
1209     tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), v, 0, &pkgname);
1210     tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), v, 4, &descrip);
1211     tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), v, 3, &status);
1212 
1213     if (pkgname == NULL || descrip == NULL) {
1214 	gui_errmsg(E_DATA);
1215     } else {
1216 	gchar *localver = NULL;
1217 	int done = 0;
1218 
1219 	if (status != NULL) {
1220 	    char *path = gretl_addon_get_path(pkgname);
1221 	    char *ver = NULL, *date = NULL;
1222 
1223 	    if (path != NULL && !strcmp(status, _("Not up to date"))) {
1224 		ver = get_addon_version(path, &date);
1225 		if (ver != NULL && data != NULL) {
1226 		    localver = g_strdup_printf("Installed version is %s (%s)",
1227 					       ver, date);
1228 		}
1229 		free(ver);
1230 		free(date);
1231 	    } else if (path != NULL && !strcmp(status, _("Up to date")) &&
1232 		       display_function_package_data(pkgname, path, VIEW_PKG_INFO)) {
1233 		done = 1;
1234 	    }
1235 	    free(path);
1236 	}
1237 
1238 	if (localver != NULL) {
1239 	    infobox_printf("%s:\n%s\n%s", pkgname, descrip, localver);
1240 	    g_free(localver);
1241 	} else if (!done) {
1242 	    infobox_printf("%s:\n%s", pkgname, descrip);
1243 	}
1244     }
1245 
1246     g_free(pkgname);
1247     g_free(descrip);
1248     g_free(status);
1249 }
1250 
install_addon_callback(GtkWidget * w,gpointer data)1251 static void install_addon_callback (GtkWidget *w, gpointer data)
1252 {
1253     windata_t *vwin = (windata_t *) data;
1254     gchar *pkgname = NULL;
1255     int v = vwin->active_var;
1256 
1257     tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), v, 0, &pkgname);
1258 
1259     if (pkgname == NULL) {
1260 	gui_errmsg(E_DATA);
1261     } else {
1262 	char *local_path = NULL;
1263 	int err = download_addon(pkgname, &local_path);
1264 
1265 	if (!err) {
1266 	    list_store_set_string(GTK_TREE_VIEW(vwin->listbox), v, 3,
1267 				  _("Up to date"));
1268 	    /* if there was an old version of the addon loaded,
1269 	       we need to unload it now so as to get correct
1270 	       information in response to a subsequent call to
1271 	       "check for addons"
1272 	    */
1273 	    function_package_unload_full_by_filename(local_path);
1274 	    free(local_path);
1275 	}
1276 
1277 	g_free(pkgname);
1278     }
1279 }
1280 
1281 /* this function is public because it's called from
1282    doubleclick_action() in callbacks.c
1283 */
1284 
browser_call_func(GtkWidget * w,gpointer data)1285 void browser_call_func (GtkWidget *w, gpointer data)
1286 {
1287     windata_t *vwin = (windata_t *) data;
1288 
1289     browser_functions_handler(vwin, CALL_FN_PKG);
1290 }
1291 
show_function_info(GtkWidget * w,gpointer data)1292 static void show_function_info (GtkWidget *w, gpointer data)
1293 {
1294     windata_t *vwin = (windata_t *) data;
1295 
1296     browser_functions_handler(vwin, VIEW_FN_PKG_INFO);
1297 }
1298 
show_function_code(GtkWidget * w,gpointer data)1299 static void show_function_code (GtkWidget *w, gpointer data)
1300 {
1301     windata_t *vwin = (windata_t *) data;
1302 
1303     browser_functions_handler(vwin, VIEW_FN_PKG_CODE);
1304 }
1305 
show_function_sample(GtkWidget * w,gpointer data)1306 static void show_function_sample (GtkWidget *w, gpointer data)
1307 {
1308     windata_t *vwin = (windata_t *) data;
1309 
1310     browser_functions_handler(vwin, VIEW_FN_PKG_SAMPLE);
1311 }
1312 
show_package_resources(GtkWidget * w,gpointer data)1313 static void show_package_resources (GtkWidget *w, gpointer data)
1314 {
1315     windata_t *vwin = (windata_t *) data;
1316 
1317     browser_functions_handler(vwin, VIEW_PKG_RESOURCES);
1318 }
1319 
show_package_doc(GtkWidget * w,gpointer data)1320 static void show_package_doc (GtkWidget *w, gpointer data)
1321 {
1322     windata_t *vwin = (windata_t *) data;
1323 
1324     browser_functions_handler(vwin, VIEW_PKG_DOC);
1325 }
1326 
browser_del_func(GtkWidget * w,gpointer data)1327 static void browser_del_func (GtkWidget *w, gpointer data)
1328 {
1329     windata_t *vwin = (windata_t *) data;
1330 
1331     browser_functions_handler(vwin, DELETE_FN_PKG);
1332 }
1333 
add_func_to_menu(GtkWidget * w,gpointer data)1334 static void add_func_to_menu (GtkWidget *w, gpointer data)
1335 {
1336     windata_t *vwin = (windata_t *) data;
1337 
1338     browser_functions_handler(vwin, MENU_ADD_FN_PKG);
1339 }
1340 
gfn_registry_remove(GtkWidget * w,gpointer data)1341 static void gfn_registry_remove (GtkWidget *w, gpointer data)
1342 {
1343     windata_t *vwin = (windata_t *) data;
1344 
1345     browser_functions_handler(vwin, MENU_REMOVE_FN_PKG);
1346 }
1347 
get_local_viewer(int remote_role)1348 windata_t *get_local_viewer (int remote_role)
1349 {
1350     windata_t *vwin = NULL;
1351 
1352     if (remote_role == REMOTE_DB) {
1353 	vwin = get_browser_for_role(NATIVE_DB, NULL);
1354     } else if (remote_role == REMOTE_FUNC_FILES) {
1355 	vwin = get_browser_for_role(FUNC_FILES, NULL);
1356     }
1357 
1358     return vwin;
1359 }
1360 
start_new_function_package(GtkWidget * w,gpointer data)1361 void start_new_function_package (GtkWidget *w, gpointer data)
1362 {
1363     windata_t *vwin = (windata_t *) data;
1364 
1365     functions_selection_wrapper(vwin_toplevel(vwin));
1366 }
1367 
build_datafiles_popup(windata_t * vwin)1368 static void build_datafiles_popup (windata_t *vwin)
1369 {
1370     if (vwin->popup == NULL) {
1371 	vwin->popup = gtk_menu_new();
1372 	add_popup_item(_("Info"), vwin->popup,
1373 		       G_CALLBACK(show_datafile_info),
1374 		       vwin);
1375 	add_popup_item(_("Open"), vwin->popup,
1376 		       G_CALLBACK(browser_open_data),
1377 		       vwin);
1378     }
1379 }
1380 
get_menu_add_ok(windata_t * vwin)1381 static int get_menu_add_ok (windata_t *vwin)
1382 {
1383     gchar *pkgname = NULL;
1384     gchar *dirname = NULL;
1385     int ret = 0;
1386 
1387     tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), vwin->active_var,
1388 			 0, &pkgname);
1389     if (pkgname != NULL && !strcmp(pkgname, "ridge")) {
1390 	return 0;
1391     }
1392     tree_view_get_string(GTK_TREE_VIEW(vwin->listbox), vwin->active_var,
1393 			 GFN_DIRNAME_COL, &dirname);
1394 
1395     if (pkgname != NULL && dirname != NULL) {
1396 	char path[FILENAME_MAX];
1397 
1398 	gretl_build_path(path, dirname, pkgname, NULL);
1399 	strcat(path, ".gfn");
1400 	ret = package_is_available_for_menu(pkgname, path);
1401     }
1402 
1403     g_free(pkgname);
1404     g_free(dirname);
1405 
1406     return ret;
1407 }
1408 
check_extra_buttons_state(GtkTreeSelection * sel,windata_t * vwin)1409 static void check_extra_buttons_state (GtkTreeSelection *sel, windata_t *vwin)
1410 {
1411     GtkWidget *button;
1412     gint flags = 0;
1413 
1414     button = g_object_get_data(G_OBJECT(vwin->mbar), "add-button");
1415     if (button != NULL) {
1416 	gtk_widget_set_sensitive(button, get_menu_add_ok(vwin));
1417     }
1418 
1419     /* Get flags from last, hidden int column, to determine
1420        whether we can offer links to a package's "examples"
1421        directory and/or its documentation in PDF format.
1422     */
1423     tree_view_get_int(GTK_TREE_VIEW(vwin->listbox),
1424 		      vwin->active_var, GFN_FLAGS_COL, &flags);
1425 
1426     button = g_object_get_data(G_OBJECT(vwin->mbar), "res-button");
1427     if (button != NULL) {
1428 	gtk_widget_set_sensitive(button, flags & PKG_ATTR_RES);
1429     }
1430 
1431     button = g_object_get_data(G_OBJECT(vwin->mbar), "doc-button");
1432     if (button != NULL) {
1433 	gtk_widget_set_sensitive(button, flags & PKG_ATTR_DOC);
1434     }
1435 }
1436 
connect_menu_adjust_signal(windata_t * vwin)1437 static void connect_menu_adjust_signal (windata_t *vwin)
1438 {
1439     GtkTreeSelection *sel;
1440     gulong id;
1441 
1442     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(vwin->listbox));
1443     id = g_signal_connect(G_OBJECT(sel), "changed",
1444 			  G_CALLBACK(check_extra_buttons_state),
1445 			  vwin);
1446     g_object_set_data(G_OBJECT(sel), "menu_check", GINT_TO_POINTER(id));
1447 }
1448 
build_funcfiles_popup(windata_t * vwin)1449 static void build_funcfiles_popup (windata_t *vwin)
1450 {
1451     vwin->popup = gtk_menu_new();
1452 
1453     if (vwin->role == FUNC_FILES) {
1454 	/* local function files: full menu */
1455 	int add_ok = 0;
1456 	int res_ok = 0;
1457 	int doc_ok = 0;
1458 	GtkWidget *b;
1459 
1460 	b = g_object_get_data(G_OBJECT(vwin->mbar), "add-button");
1461 	if (b != NULL && gtk_widget_is_sensitive(b)) {
1462 	    add_ok = 1;
1463 	}
1464 
1465 	b = g_object_get_data(G_OBJECT(vwin->mbar), "res-button");
1466 	if (b != NULL && gtk_widget_is_sensitive(b)) {
1467 	    res_ok = 1;
1468 	}
1469 
1470 	b = g_object_get_data(G_OBJECT(vwin->mbar), "doc-button");
1471 	if (b != NULL && gtk_widget_is_sensitive(b)) {
1472 	    doc_ok = 1;
1473 	}
1474 
1475 	add_popup_item(_("Info"), vwin->popup,
1476 		       G_CALLBACK(show_function_info),
1477 		       vwin);
1478 	add_popup_item(_("Sample script"), vwin->popup,
1479 		       G_CALLBACK(show_function_sample),
1480 		       vwin);
1481 	add_popup_item(_("View code"), vwin->popup,
1482 		       G_CALLBACK(show_function_code),
1483 		       vwin);
1484 	add_popup_item(_("Execute"), vwin->popup,
1485 		       G_CALLBACK(browser_call_func),
1486 		       vwin);
1487 	if (res_ok) {
1488 	    add_popup_item(_("Resources..."), vwin->popup,
1489 			   G_CALLBACK(show_package_resources),
1490 			   vwin);
1491 	}
1492 	if (add_ok) {
1493 	    add_popup_item(_("Add to menu"), vwin->popup,
1494 			   G_CALLBACK(add_func_to_menu),
1495 			   vwin);
1496 	}
1497 	if (doc_ok) {
1498 	    add_popup_item(_("Help"), vwin->popup,
1499 			   G_CALLBACK(show_package_doc),
1500 			   vwin);
1501 	}
1502 	add_popup_item(_("Unload/delete..."), vwin->popup,
1503 		       G_CALLBACK(browser_del_func),
1504 		       vwin);
1505     } else if (vwin->role == REMOTE_FUNC_FILES) {
1506 	/* files on server: limited menu */
1507 	add_popup_item(_("Info"), vwin->popup,
1508 		       G_CALLBACK(pkg_info_from_server),
1509 		       vwin);
1510 	add_popup_item(_("Install"), vwin->popup,
1511 		       G_CALLBACK(install_file_from_server),
1512 		       vwin);
1513     } else if (vwin->role == REMOTE_ADDONS) {
1514 	add_popup_item(_("Info"), vwin->popup,
1515 		       G_CALLBACK(show_addon_info),
1516 		       vwin);
1517 	add_popup_item(_("Install"), vwin->popup,
1518 		       G_CALLBACK(install_addon_callback),
1519 		       vwin);
1520     } else if (vwin->role == PKG_REGISTRY) {
1521 	add_popup_item(_("Remove"), vwin->popup,
1522 		       G_CALLBACK(gfn_registry_remove),
1523 		       vwin);
1524     }
1525 }
1526 
1527 static gboolean
funcfiles_popup_handler(GtkWidget * w,GdkEventButton * event,gpointer data)1528 funcfiles_popup_handler (GtkWidget *w, GdkEventButton *event, gpointer data)
1529 {
1530     if (right_click(event)) {
1531 	windata_t *vwin = (windata_t *) data;
1532 
1533 	if (vwin->popup != NULL) {
1534 	    gtk_widget_destroy(vwin->popup);
1535 	    vwin->popup = NULL;
1536 	}
1537 
1538 	build_funcfiles_popup(vwin);
1539 
1540 	if (vwin->popup != NULL) {
1541 	    gtk_menu_popup(GTK_MENU(vwin->popup), NULL, NULL, NULL, NULL,
1542 			   event->button, event->time);
1543 	    g_signal_connect(G_OBJECT(vwin->popup), "destroy",
1544 			     G_CALLBACK(gtk_widget_destroyed),
1545 			     &vwin->popup);
1546 	}
1547 
1548 	return TRUE;
1549     }
1550 
1551     return FALSE;
1552 }
1553 
build_db_popup(windata_t * vwin)1554 static void build_db_popup (windata_t *vwin)
1555 {
1556     if (vwin->popup == NULL) {
1557 	vwin->popup = gtk_menu_new();
1558 	if (vwin->role == NATIVE_DB) {
1559 	    add_popup_item(_("List series"), vwin->popup,
1560 			   G_CALLBACK(open_db_index),
1561 			   vwin);
1562 	    add_popup_item(_("Find..."), vwin->popup,
1563 			   G_CALLBACK(listbox_find),
1564 			   vwin);
1565 	} else {
1566 	    add_popup_item(_("List series"), vwin->popup,
1567 			   G_CALLBACK(open_remote_db_index),
1568 			   vwin);
1569 	    add_popup_item(_("Install"), vwin->popup,
1570 			   G_CALLBACK(install_file_from_server),
1571 			   vwin);
1572 	    add_popup_item(_("Find..."), vwin->popup,
1573 			   G_CALLBACK(listbox_find),
1574 			   vwin);
1575 	}
1576     }
1577 }
1578 
build_data_pkg_popup(windata_t * vwin)1579 static void build_data_pkg_popup (windata_t *vwin)
1580 {
1581     if (vwin->popup == NULL) {
1582 	vwin->popup = gtk_menu_new();
1583 	add_popup_item(_("Install"), vwin->popup,
1584 		       G_CALLBACK(install_file_from_server),
1585 		       vwin);
1586     }
1587 }
1588 
show_server_dbs(GtkWidget * w,gpointer p)1589 static void show_server_dbs (GtkWidget *w, gpointer p)
1590 {
1591     display_files(REMOTE_DB, NULL);
1592 }
1593 
show_local_dbs(GtkWidget * w,gpointer p)1594 static void show_local_dbs (GtkWidget *w, gpointer p)
1595 {
1596     display_files(NATIVE_DB, NULL);
1597 }
1598 
show_server_funcs(GtkWidget * w,gpointer p)1599 static void show_server_funcs (GtkWidget *w, gpointer p)
1600 {
1601     display_files(REMOTE_FUNC_FILES, NULL);
1602 }
1603 
show_server_data_pkgs(GtkWidget * w,gpointer p)1604 static void show_server_data_pkgs (GtkWidget *w, gpointer p)
1605 {
1606     display_files(REMOTE_DATA_PKGS, NULL);
1607 }
1608 
show_local_funcs(GtkWidget * w,gpointer p)1609 static void show_local_funcs (GtkWidget *w, gpointer p)
1610 {
1611     display_files(FUNC_FILES, NULL);
1612 }
1613 
show_gfn_registry(GtkWidget * w,windata_t * vwin)1614 static void show_gfn_registry (GtkWidget *w, windata_t *vwin)
1615 {
1616     display_files(PKG_REGISTRY, NULL);
1617 }
1618 
1619 /* Respond when the user has clicked the Directory button
1620    in the function package browser. What exactly we do
1621    here depends on whether the browser is currently in
1622    its default mode (viewing installed packages) or if
1623    it is redirected -- which is flagged by a non-zero
1624    value for "altdir" on the browser's listbox.
1625 */
1626 
alt_funcs_dir(GtkWidget * w,windata_t * vwin)1627 static void alt_funcs_dir (GtkWidget *w, windata_t *vwin)
1628 {
1629     if (widget_get_int(vwin->listbox, "altdir")) {
1630 	const char *opts[] = {
1631 	    N_("Choose another directory"),
1632 	    N_("Revert to installed packages")
1633 	};
1634 	int resp;
1635 
1636 	resp = radio_dialog(NULL, NULL, opts, 2, 0, 0, vwin->main);
1637 
1638 	if (resp == GRETL_CANCEL) {
1639 	    return;
1640 	} else if (resp == 1) {
1641 	    /* revert to installed gfns */
1642 	    widget_set_int(vwin->listbox, "altdir", 0);
1643 	    populate_gfn_list(vwin);
1644 	    listbox_select_first(vwin);
1645 	    return;
1646 	}
1647     }
1648 
1649     /* If not canceled or reverted to default, put up a
1650        dialog to let the user select a directory: the
1651        callback from that is set_alternate_gfn_dir().
1652     */
1653 
1654     file_selector_with_parent(SET_FDIR, FSEL_DATA_VWIN, vwin,
1655                               vwin->main);
1656 }
1657 
alt_db_dir(GtkWidget * w,windata_t * vwin)1658 static void alt_db_dir (GtkWidget *w, windata_t *vwin)
1659 {
1660     file_selector_with_parent(SET_DBDIR, FSEL_DATA_VWIN, vwin,
1661                               vwin->main);
1662 }
1663 
get_dbn_menu(windata_t * vwin)1664 static GtkWidget *get_dbn_menu (windata_t *vwin)
1665 {
1666     GtkWidget *menu = gtk_menu_new();
1667     GtkAction *action;
1668     GtkWidget *item;
1669 
1670     action = gtk_action_new("DBNbrowse", _("Browse..."), NULL, NULL);
1671     g_signal_connect(G_OBJECT(action), "activate",
1672 		     G_CALLBACK(show_files), vwin);
1673     item = gtk_action_create_menu_item(action);
1674     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
1675 
1676     action = gtk_action_new("DBNseries", _("Specific series..."), NULL, NULL);
1677     g_signal_connect(G_OBJECT(action), "activate",
1678 		     G_CALLBACK(dbnomics_specific_series), vwin);
1679     item = gtk_action_create_menu_item(action);
1680     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
1681 
1682     /* don't leak: record pointer to menu so it can
1683        be destroyed when the window is closed */
1684     vwin_record_toolbar_popup(vwin, menu);
1685 
1686     return menu;
1687 }
1688 
1689 enum {
1690     BTN_INFO = 1,
1691     BTN_CODE,
1692     BTN_INDX,
1693     BTN_INST,
1694     BTN_EXEC,
1695     BTN_ADD,
1696     BTN_DEL,
1697     BTN_WWW,
1698     BTN_HOME,
1699     BTN_OPEN,
1700     BTN_DIR,
1701     BTN_RES,
1702     BTN_DOC,
1703     BTN_REG,
1704     BTN_DBN,
1705     BTN_PROP
1706 };
1707 
1708 static GretlToolItem files_items[] = {
1709     { N_("Open"),           GTK_STOCK_OPEN, NULL, BTN_OPEN },
1710     { N_("Select directory"), GTK_STOCK_DIRECTORY, NULL, BTN_DIR },
1711     { N_("Info"),           GTK_STOCK_INFO,       NULL, BTN_INFO },
1712     { N_("Details"),        GTK_STOCK_PROPERTIES, G_CALLBACK(open_dbnomics_series), BTN_PROP },
1713     { N_("Sample script"),  GTK_STOCK_JUSTIFY_LEFT, G_CALLBACK(show_function_sample), BTN_CODE },
1714     { N_("View code"),      GTK_STOCK_PROPERTIES, G_CALLBACK(show_function_code), BTN_CODE },
1715     { N_("Execute"),        GTK_STOCK_EXECUTE,    G_CALLBACK(browser_call_func),  BTN_EXEC },
1716     { N_("List series"),    GTK_STOCK_INDEX,      NULL,                           BTN_INDX },
1717     { N_("Install"),        GTK_STOCK_SAVE,       NULL,                           BTN_INST },
1718     { N_("Resources..."),   GTK_STOCK_OPEN,       G_CALLBACK(show_package_resources), BTN_RES },
1719     { N_("Add to menu"),    GTK_STOCK_ADD,        G_CALLBACK(add_func_to_menu),  BTN_ADD },
1720     { N_("Package registry"), GTK_STOCK_PREFERENCES, G_CALLBACK(show_gfn_registry), BTN_REG },
1721     { N_("Help"),           GRETL_STOCK_PDF,      G_CALLBACK(show_package_doc),  BTN_DOC },
1722     { N_("Unload/delete..."), GTK_STOCK_DELETE,   G_CALLBACK(browser_del_func),  BTN_DEL },
1723     { N_("Look on server"), GTK_STOCK_NETWORK,    NULL,                          BTN_WWW },
1724     { N_("Local machine"),  GTK_STOCK_HOME,       NULL,                          BTN_HOME },
1725     { "DB.NOMICS",          GRETL_STOCK_DBN,      NULL,                          BTN_DBN }
1726 };
1727 
1728 static GretlToolItem pager_items[] = {
1729     { N_("First"),    GTK_STOCK_GOTO_FIRST, G_CALLBACK(dbnomics_pager_call), 1 },
1730     { N_("Previous"), GTK_STOCK_GO_BACK,    G_CALLBACK(dbnomics_pager_call), 2 },
1731     { N_("Next"),     GTK_STOCK_GO_FORWARD, G_CALLBACK(dbnomics_pager_call), 3 },
1732     { N_("Last"),     GTK_STOCK_GOTO_LAST,  G_CALLBACK(dbnomics_pager_call), 4 }
1733 };
1734 
1735 static int n_files_items = G_N_ELEMENTS(files_items);
1736 
1737 #define common_item(f) (f == 0)
1738 
1739 #define local_funcs_item(f) (f == BTN_DEL || f == BTN_CODE || \
1740 			     f == BTN_RES || f == BTN_DOC || \
1741 			     f == BTN_REG)
1742 
files_item_get_callback(GretlToolItem * item,int role)1743 static int files_item_get_callback (GretlToolItem *item, int role)
1744 {
1745     if (common_item(item->flag)) {
1746 	return 1;
1747     } else if (item->flag == BTN_DEL) {
1748 	if (role == PKG_REGISTRY) {
1749 	    item->func = G_CALLBACK(gfn_registry_remove);
1750 	    item->tip = N_("Remove from menu");
1751 	    return 1;
1752 	} else if (role == FUNC_FILES) {
1753 	    item->func = G_CALLBACK(browser_del_func);
1754 	    item->tip = N_("Unload/delete...");
1755 	    return 1;
1756 	}
1757     } else if (local_funcs_item(item->flag)) {
1758 	if (item->flag == BTN_DOC && role == DBNOMICS_TOP) {
1759 	    return 1;
1760 	} else {
1761 	    return (role == FUNC_FILES);
1762 	}
1763     } else if (item->flag == BTN_INST) {
1764 	if (role == REMOTE_ADDONS) {
1765 	    item->func = G_CALLBACK(install_addon_callback);
1766 	    return 1;
1767 	} else {
1768 	    item->func = G_CALLBACK(install_file_from_server);
1769 	    return (role == REMOTE_DB ||
1770 		    role == REMOTE_FUNC_FILES ||
1771 		    role == REMOTE_DATA_PKGS);
1772 	}
1773     } else if (item->flag == BTN_EXEC || item->flag == BTN_ADD) {
1774 	return (role == FUNC_FILES);
1775     } else if (item->flag == BTN_PROP) {
1776 	return (role == DBNOMICS_SERIES);
1777     }
1778 
1779     item->func = NULL;
1780 
1781     if (item->flag == BTN_OPEN) {
1782 	/* open: only data files and scripts */
1783 	if (role == TEXTBOOK_DATA) {
1784 	    item->func = G_CALLBACK(browser_open_data);
1785 	} else if (role == PS_FILES) {
1786 	    item->func = G_CALLBACK(browser_open_ps);
1787 	}
1788     } else if (item->flag == BTN_INFO) {
1789 	if (role == TEXTBOOK_DATA) {
1790 	    item->func = G_CALLBACK(show_datafile_info);
1791 	} else if (role == FUNC_FILES) {
1792 	    item->func = G_CALLBACK(show_function_info);
1793 	} else if (role == REMOTE_FUNC_FILES) {
1794 	    item->func = G_CALLBACK(pkg_info_from_server);
1795 	} else if (role == REMOTE_ADDONS) {
1796 	    item->func = G_CALLBACK(show_addon_info);
1797 	} else if (role == DBNOMICS_DB) {
1798 	    item->func = G_CALLBACK(show_dbnomics_dimensions);
1799 	}
1800     } else if (item->flag == BTN_INDX) {
1801 	/* index: databases only */
1802 	item->tip = N_("List series");
1803 	if (role == NATIVE_DB) {
1804 	    item->func = G_CALLBACK(open_db_index);
1805 	} else if (role == REMOTE_DB) {
1806 	    item->func = G_CALLBACK(open_remote_db_index);
1807 	} else if (role == DBNOMICS_TOP) {
1808 	    item->func = G_CALLBACK(open_dbnomics_provider);
1809 	    item->tip = N_("List datasets");
1810 	} else if (role == DBNOMICS_DB) {
1811 	    item->func = G_CALLBACK(open_dbnomics_dataset);
1812 	}
1813     } else if (item->flag == BTN_WWW) {
1814 	if (role == FUNC_FILES) {
1815 	    item->func = G_CALLBACK(show_server_funcs);
1816 	} else if (role == NATIVE_DB) {
1817 	    item->func = G_CALLBACK(show_server_dbs);
1818 	} else if (role == TEXTBOOK_DATA) {
1819 	    item->func = G_CALLBACK(show_server_data_pkgs);
1820 	}
1821     } else if (item->flag == BTN_HOME) {
1822 	/* home: show only for on-server items */
1823 	if (role == REMOTE_FUNC_FILES) {
1824 	    item->func = G_CALLBACK(show_local_funcs);
1825 	} else if (role == REMOTE_DB) {
1826 	    item->func = G_CALLBACK(show_local_dbs);
1827 	}
1828     } else if (item->flag == BTN_DIR) {
1829 	if (role == FUNC_FILES) {
1830 	    item->func = G_CALLBACK(alt_funcs_dir);
1831 	} else if (role == NATIVE_DB) {
1832 	    item->func = G_CALLBACK(alt_db_dir);
1833 	}
1834     } else if (item->flag == BTN_DBN) {
1835 	if (role == NATIVE_DB) {
1836 	    item->func = G_CALLBACK(dummy_call);
1837 	}
1838     }
1839 
1840     return (item->func != NULL);
1841 }
1842 
filter_remote_funcs(GtkButton * b,windata_t * vwin)1843 static void filter_remote_funcs (GtkButton *b, windata_t *vwin)
1844 {
1845     GtkWidget *combo;
1846     int filter, old_filter;
1847     gchar *s;
1848 
1849     combo = g_object_get_data(G_OBJECT(vwin->main), "filter-combo");
1850     old_filter = widget_get_int(combo, "filter");
1851 
1852     if (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)) == 0) {
1853 	filter = 0;
1854     } else {
1855 	s = combo_box_get_active_text(combo);
1856 	filter = atoi(s + 1); /* "C<number>" */
1857 	g_free(s);
1858     }
1859 
1860     if (filter != old_filter) {
1861 	populate_remote_func_list(vwin, filter);
1862 	widget_set_int(combo, "filter", filter);
1863 	listbox_select_first(vwin);
1864     }
1865 }
1866 
maybe_add_gfn_filter(windata_t * vwin,GtkWidget * hbox)1867 static void maybe_add_gfn_filter (windata_t *vwin,
1868 				  GtkWidget *hbox)
1869 {
1870     char *getbuf = NULL;
1871     int err;
1872 
1873     err = list_remote_function_categories(&getbuf, OPT_NONE);
1874     if (!err && (getbuf == NULL || *getbuf != 'C')) {
1875 	free(getbuf);
1876 	err = 1;
1877     }
1878 
1879 #if 0
1880     fprintf(stderr, "getbuf: '%s'\n", getbuf);
1881 #endif
1882 
1883     if (!err) {
1884 	GtkWidget *combo, *button;
1885 	char line[128];
1886 	gchar *label;
1887 
1888 	bufgets_init(getbuf);
1889 
1890 	combo = gtk_combo_box_text_new();
1891 	g_object_set_data(G_OBJECT(vwin->main), "filter-combo", combo);
1892 	widget_set_int(combo, "filter", 0);
1893 
1894 	combo_box_append_text(combo, _("All packages"));
1895 	while (bufgets(line, sizeof line, getbuf)) {
1896 	    combo_box_append_text(combo, tailstrip(line));
1897 	}
1898 	gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1899 
1900 	bufgets_finalize(getbuf);
1901 	free(getbuf);
1902 
1903 	gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 5);
1904 	label = g_strdup_printf(" %s ", _("filter"));
1905 	button = gtk_button_new_with_label(label);
1906 	g_free(label);
1907 	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
1908 	g_signal_connect(G_OBJECT(button), "clicked",
1909 			 G_CALLBACK(filter_remote_funcs), vwin);
1910 
1911     }
1912 }
1913 
add_dbnomics_menu_button(GretlToolItem * item,windata_t * vwin)1914 static void add_dbnomics_menu_button (GretlToolItem *item,
1915 				      windata_t *vwin)
1916 {
1917     GtkWidget *menu = get_dbn_menu(vwin);
1918 
1919     vwin_toolbar_insert(item, NULL, menu, vwin, -1);
1920 }
1921 
make_files_toolbar(windata_t * vwin)1922 static void make_files_toolbar (windata_t *vwin)
1923 {
1924     GtkWidget *hbox, *button;
1925     GretlToolItem *item;
1926     int i;
1927 
1928     hbox = gtk_hbox_new(FALSE, 0);
1929     gtk_box_pack_start(GTK_BOX(vwin->vbox), hbox, FALSE, FALSE, 0);
1930 
1931     vwin->mbar = gretl_toolbar_new(NULL);
1932 
1933     for (i=0; i<n_files_items; i++) {
1934 	item = &files_items[i];
1935 	if (item->flag == BTN_DBN) {
1936 	    if (vwin->role == NATIVE_DB) {
1937 		add_dbnomics_menu_button(item, vwin);
1938 	    }
1939 	    continue;
1940 	}
1941 	if (files_item_get_callback(item, vwin->role)) {
1942 	    button = gretl_toolbar_insert(vwin->mbar, item, item->func, vwin, -1);
1943 	    if (item->flag == BTN_ADD) {
1944 		g_object_set_data(G_OBJECT(vwin->mbar), "add-button", button);
1945 		gtk_widget_set_sensitive(button, FALSE);
1946 	    } else if (item->flag == BTN_RES) {
1947 		g_object_set_data(G_OBJECT(vwin->mbar), "res-button", button);
1948 		gtk_widget_set_sensitive(button, FALSE);
1949 	    } else if (item->flag == BTN_DOC && vwin->role != DBNOMICS_TOP) {
1950 		/* condition on availablity of PDF documentation */
1951 		g_object_set_data(G_OBJECT(vwin->mbar), "doc-button", button);
1952 		gtk_widget_set_sensitive(button, FALSE);
1953 	    }
1954 	}
1955     }
1956 
1957     if (DBNOMICS_ACTION(vwin->role)) {
1958 	const char *ids[] = {
1959 	    "first-button", "prev-button", "next-button", "last-button"
1960 	};
1961 	for (i=0; i<4; i++) {
1962 	    item = &pager_items[i];
1963 	    button = gretl_toolbar_insert(vwin->mbar, item, item->func, vwin, -1);
1964 	    g_object_set_data(G_OBJECT(vwin->mbar), ids[i], button);
1965 	    widget_set_int(button, "action", item->flag);
1966 	}
1967     }
1968 
1969     gtk_box_pack_start(GTK_BOX(hbox), vwin->mbar, FALSE, FALSE, 0);
1970 
1971     if (vwin->role == REMOTE_FUNC_FILES) {
1972 	maybe_add_gfn_filter(vwin, hbox);
1973     }
1974 
1975     vwin_add_winlist(vwin);
1976     vwin_add_finder(vwin);
1977     gtk_widget_show_all(hbox);
1978 }
1979 
files_title(int code)1980 static gchar *files_title (int code)
1981 {
1982     static char hname[48];
1983     gchar *ret = NULL;
1984 
1985     if (*hname == '\0') {
1986 	const gchar *s = g_get_host_name();
1987 
1988 	if (s != NULL && strlen(s) < 48) {
1989 	    strcpy(hname, s);
1990 	} else {
1991 	    strcpy(hname, _("local machine"));
1992 	}
1993     }
1994 
1995     if (code == NATIVE_DB) {
1996 	ret = g_strdup_printf(_("gretl: databases on %s"), hname);
1997     } else {
1998 	ret = g_strdup_printf(_("gretl: function packages on %s"), hname);
1999     }
2000 
2001     return ret;
2002 }
2003 
2004 /* handle drag of pointer from remote database window
2005    to local one, or remote function package window to
2006    local one
2007 */
2008 
2009 static void
remote_window_handle_drag(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * data,guint info,guint time,gpointer p)2010 remote_window_handle_drag (GtkWidget *widget,
2011 			   GdkDragContext *context,
2012 			   gint x,
2013 			   gint y,
2014 			   GtkSelectionData *data,
2015 			   guint info,
2016 			   guint time,
2017 			   gpointer p)
2018 {
2019     if (data != NULL &&
2020 	(info == GRETL_REMOTE_DB_PTR ||
2021 	 info == GRETL_REMOTE_FNPKG_PTR)) {
2022 	drag_file_from_server(info);
2023     }
2024 }
2025 
set_up_viewer_drag_target(windata_t * vwin)2026 static void set_up_viewer_drag_target (windata_t *vwin)
2027 {
2028     GCallback callback;
2029     int i;
2030 
2031     if (vwin->role == NATIVE_DB) {
2032 	i = GRETL_REMOTE_DB_PTR;
2033 	callback = G_CALLBACK(remote_window_handle_drag);
2034     } else if (vwin->role == FUNC_FILES) {
2035 	i = GRETL_REMOTE_FNPKG_PTR;
2036 	callback = G_CALLBACK(remote_window_handle_drag);
2037     } else {
2038 	return;
2039     }
2040 
2041     gtk_drag_dest_set(vwin->listbox,
2042 		      GTK_DEST_DEFAULT_ALL,
2043 		      &gretl_drag_targets[i], 1,
2044 		      GDK_ACTION_COPY);
2045 
2046     g_signal_connect(G_OBJECT(vwin->listbox), "drag-data-received",
2047 		     callback, NULL);
2048 }
2049 
listbox_select_first(windata_t * vwin)2050 void listbox_select_first (windata_t *vwin)
2051 {
2052     GtkTreeView *view = GTK_TREE_VIEW(vwin->listbox);
2053     GtkTreeModel *model;
2054     GtkTreeSelection *selection;
2055     GtkTreeIter iter;
2056 
2057     model = gtk_tree_view_get_model(view);
2058     gtk_tree_model_get_iter_first(model, &iter);
2059     selection = gtk_tree_view_get_selection(view);
2060     gtk_tree_selection_select_iter(selection, &iter);
2061     gtk_widget_grab_focus(vwin->listbox);
2062 }
2063 
2064 #define notebook_needed(r) (r==TEXTBOOK_DATA || r==PS_FILES)
2065 
display_files(int role,const gchar * path)2066 void display_files (int role, const gchar *path)
2067 {
2068     GtkWidget *filebox;
2069     windata_t *vwin;
2070     gchar *title = NULL;
2071     gchar *mypath = NULL;
2072     int err = 0;
2073 
2074     vwin = get_browser_for_role(role, path);
2075     if (vwin != NULL) {
2076 	gtk_window_present(GTK_WINDOW(vwin->main));
2077 	return;
2078     }
2079 
2080     if (role == PKG_REGISTRY && n_user_handled_packages() == 0) {
2081 	infobox(_("The gui package registry is empty"));
2082 	return;
2083     }
2084 
2085     if (role == FUNC_FILES || role == NATIVE_DB) {
2086 	title = files_title(role);
2087     } else if (role == PS_FILES) {
2088 	title = g_strdup(_("gretl: example scripts"));
2089     } else if (role == TEXTBOOK_DATA) {
2090 	title = g_strdup(_("gretl: data files"));
2091     } else if (role == REMOTE_DB) {
2092 	title = g_strdup(_("gretl: databases on server"));
2093     } else if (role == DBNOMICS_TOP) {
2094 	title = g_strdup(_("gretl: DB.NOMICS providers"));
2095     } else if (role == REMOTE_FUNC_FILES) {
2096 	title = g_strdup(_("gretl: function packages on server"));
2097     } else if (role == REMOTE_DATA_PKGS) {
2098 	title = g_strdup(_("gretl: data packages on server"));
2099     } else if (role == REMOTE_ADDONS) {
2100 	title = g_strdup(_("gretl: addons"));
2101     } else if (role == PKG_REGISTRY) {
2102 	title = g_strdup(_("gretl: packages on menus"));
2103     } else if (role == DBNOMICS_DB) {
2104 	mypath = g_strdup(path);
2105 	title = g_strdup_printf("gretl: %s datasets", path);
2106     } else if (role == DBNOMICS_SERIES) {
2107 	mypath = g_strdup(path);
2108 	title = g_strdup_printf("gretl: %s", path);
2109     }
2110 
2111     vwin = gretl_browser_new(role, title);
2112     g_free(title);
2113 
2114     if (role == REMOTE_DB) {
2115 	gtk_window_set_default_size(GTK_WINDOW(vwin->main), 640, 480);
2116     }
2117 
2118     /* vertical box to hold file-listing widget and other elements */
2119     vwin->vbox = gtk_vbox_new(FALSE, 1);
2120     gtk_box_set_spacing(GTK_BOX(vwin->vbox), 4);
2121     gtk_container_set_border_width(GTK_CONTAINER(vwin->vbox), 4);
2122     gtk_container_add(GTK_CONTAINER(vwin->main), vwin->vbox);
2123 
2124     make_files_toolbar(vwin);
2125 
2126     if (notebook_needed(role)) {
2127 	/* we'll need more than one tab */
2128 	filebox = files_notebook(vwin, role);
2129     } else {
2130 	/* no tabs needed */
2131 	filebox = files_vbox(vwin);
2132     }
2133 
2134     if (filebox == NULL) {
2135 	gtk_widget_destroy(vwin->main);
2136 	return;
2137     }
2138 
2139     gtk_box_pack_start(GTK_BOX(vwin->vbox), filebox, TRUE, TRUE, 0);
2140 
2141     if (role == TEXTBOOK_DATA) {
2142 	file_collection *collection;
2143 	GList *L = collections_for_role(role);
2144 
2145 	build_datafiles_popup(vwin);
2146 	while (L) {
2147 	    collection = L->data;
2148 	    g_signal_connect(G_OBJECT(collection->listbox), "button-press-event",
2149 			     G_CALLBACK(popup_menu_handler),
2150 			     vwin->popup);
2151 	    L = L->next;
2152 	}
2153     } else if (role == FUNC_FILES || role == REMOTE_FUNC_FILES ||
2154 	       role == REMOTE_ADDONS || role == PKG_REGISTRY) {
2155 	g_signal_connect(G_OBJECT(vwin->listbox), "button-press-event",
2156 			 G_CALLBACK(funcfiles_popup_handler),
2157 			 vwin);
2158 	if (role == FUNC_FILES) {
2159 	    connect_menu_adjust_signal(vwin);
2160 	}
2161     } else if (role == NATIVE_DB || role == REMOTE_DB) {
2162 	build_db_popup(vwin);
2163 	g_signal_connect(G_OBJECT(vwin->listbox), "button-press-event",
2164 			 G_CALLBACK(popup_menu_handler),
2165 			 vwin->popup);
2166     } else if (role == REMOTE_DATA_PKGS)  {
2167 	build_data_pkg_popup(vwin);
2168 	g_signal_connect(G_OBJECT(vwin->listbox), "button-press-event",
2169 			 G_CALLBACK(popup_menu_handler),
2170 			 vwin->popup);
2171     }
2172 
2173     if (REMOTE_ACTION(role)) {
2174 	GtkWidget *hbox;
2175 
2176 	hbox = gtk_hbox_new(FALSE, 0);
2177 	gtk_box_pack_start(GTK_BOX(vwin->vbox), hbox, FALSE, FALSE, 0);
2178 	if (DBNOMICS_ACTION(role)) {
2179 	    vwin->status = gtk_label_new("");
2180 	} else {
2181 	    vwin->status = gtk_label_new(_("Network status: OK"));
2182 	}
2183 	gtk_label_set_justify(GTK_LABEL(vwin->status), GTK_JUSTIFY_LEFT);
2184 	gtk_box_pack_start(GTK_BOX(hbox), vwin->status, FALSE, FALSE, 0);
2185     }
2186 
2187     /* put stuff into list box(es) */
2188     if (notebook_needed(role)) {
2189 	err = populate_notebook_filelists(vwin, filebox, role);
2190     } else if (role == FUNC_FILES) {
2191 	err = populate_filelist(vwin, NULL);
2192     } else if (role == NATIVE_DB) {
2193 	gint w, h, ndb = 0;
2194 
2195 	err = populate_filelist(vwin, &ndb);
2196 	if (!err && ndb > 12) {
2197 	    gtk_widget_get_size_request(filebox, &w, &h);
2198 	    h += 100;
2199 	    gtk_widget_set_size_request(filebox, w, h);
2200 	}
2201     } else {
2202 	err = populate_filelist(vwin, mypath);
2203     }
2204 
2205     if (err) {
2206 	gtk_widget_destroy(vwin->main);
2207     } else {
2208 	gtk_widget_show_all(vwin->main);
2209 	gtk_widget_grab_focus(vwin->listbox);
2210 	if (role == NATIVE_DB || role == FUNC_FILES) {
2211 	    set_up_viewer_drag_target(vwin);
2212 	}
2213     }
2214 
2215     if (err) {
2216 	return;
2217     }
2218 
2219     if (role != TEXTBOOK_DATA && role != PS_FILES) {
2220 	listbox_select_first(vwin);
2221     }
2222 }
2223 
display_files_code(const gchar * s)2224 static int display_files_code (const gchar *s)
2225 {
2226     if (!strcmp(s, "DisplayDataFiles"))
2227 	return TEXTBOOK_DATA;
2228     if (!strcmp(s, "DisplayScripts"))
2229 	return PS_FILES;
2230     if (!strcmp(s, "NativeDB"))
2231 	return NATIVE_DB;
2232     if (!strcmp(s, "RemoteDB"))
2233 	return REMOTE_DB;
2234     if (!strcmp(s, "LocalGfn"))
2235 	return FUNC_FILES;
2236     if (!strcmp(s, "RemoteGfn"))
2237 	return REMOTE_FUNC_FILES;
2238     if (!strcmp(s, "SFAddons"))
2239 	return REMOTE_ADDONS;
2240     if (!strcmp(s, "DBNbrowse"))
2241 	return DBNOMICS_TOP;
2242 
2243     return 0;
2244 }
2245 
2246 /* make a browser window to display a set of files: textbook
2247    data files, example (formerly: practice) scripts, databases...
2248 */
2249 
show_files(GtkAction * action,gpointer p)2250 void show_files (GtkAction *action, gpointer p)
2251 {
2252     int code = display_files_code(gtk_action_get_name(action));
2253 
2254     display_files(code, NULL);
2255 }
2256 
show_native_dbs(void)2257 void show_native_dbs (void)
2258 {
2259     display_files(NATIVE_DB, NULL);
2260 }
2261 
2262 /* functions pertaining to gfn function packages */
2263 
populate_gfn_registry_list(windata_t * vwin)2264 static int populate_gfn_registry_list (windata_t *vwin)
2265 {
2266     GtkListStore *store;
2267     GtkTreeIter iter;
2268     char *name;
2269     char *path;
2270     char *label;
2271     int modelwin;
2272     int i, n;
2273 
2274     store = GTK_LIST_STORE(gtk_tree_view_get_model
2275 			   (GTK_TREE_VIEW(vwin->listbox)));
2276     gtk_list_store_clear(store);
2277     gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
2278 
2279     n = n_registered_packages();
2280 
2281     for (i=0; i<n; i++) {
2282 	get_registered_pkg_info(i, &name, &path, &label, &modelwin);
2283 	if (name != NULL && path != NULL) {
2284 	    gchar *fullpath, *upath, *s = path;
2285 
2286 	    if (!strncmp(s, "/menubar/", 9)) {
2287 		s += 9;
2288 	    }
2289 	    upath = user_friendly_menu_path(s, modelwin);
2290 	    if (upath != NULL) {
2291 		fullpath = g_strdup_printf("%s/%s", upath, label);
2292 	    } else {
2293 		fullpath = g_strdup_printf("%s/%s", s, label);
2294 	    }
2295 	    gtk_list_store_append(store, &iter);
2296 	    gtk_list_store_set(store, &iter,
2297 			       0, name,
2298 			       1, modelwin ? "Model" : "Main",
2299 			       2, fullpath,
2300 			       -1);
2301 	    g_free(upath);
2302 	    g_free(fullpath);
2303 	}
2304     }
2305 
2306     return 0;
2307 }
2308 
get_func_info(const char * path,char ** pdesc,char ** pver,char ** pdate,char ** pauthor,int * pdfdoc)2309 static int get_func_info (const char *path, char **pdesc,
2310 			  char **pver, char **pdate,
2311 			  char **pauthor, int *pdfdoc)
2312 {
2313     int err;
2314 
2315     err = get_function_file_header(path, pdesc, pver, pdate,
2316 				   pauthor, pdfdoc);
2317     if (err) {
2318 	gui_errmsg(err);
2319     }
2320 
2321     return err;
2322 }
2323 
real_duplicate(const char * fname,const char * version,const char * dirname,GtkTreeModel * model,GtkTreeIter * iter)2324 static int real_duplicate (const char *fname,
2325 			   const char *version,
2326 			   const char *dirname,
2327 			   GtkTreeModel *model,
2328 			   GtkTreeIter *iter)
2329 {
2330     gchar *dupdir = NULL;
2331 
2332     gtk_tree_model_get(model, iter, GFN_DIRNAME_COL, &dupdir, -1);
2333 
2334     if (!strcmp(dirname, dupdir)) {
2335 	/* it's actually the same file */
2336 	return 0;
2337     }
2338 
2339     fprintf(stderr, "duplicated function package: %s %s\n",
2340 	    fname, version);
2341     fprintf(stderr, " %s [first instance found]\n %s [duplicate]\n\n",
2342 	    dupdir, dirname);
2343     g_free(dupdir);
2344 
2345     return 1;
2346 }
2347 
fn_file_is_duplicate(const char * fname,const char * version,const char * dirname,GtkListStore * store,int imax)2348 static int fn_file_is_duplicate (const char *fname,
2349 				 const char *version,
2350 				 const char *dirname,
2351 				 GtkListStore *store,
2352 				 int imax)
2353 {
2354     GtkTreeModel *model = GTK_TREE_MODEL(store);
2355     GtkTreeIter iter;
2356     int ret = 0;
2357 
2358     /* search from the top of @model to position @imax for
2359        a row that matches on package name and version
2360     */
2361 
2362     if (imax > 0 && gtk_tree_model_get_iter_first(model, &iter)) {
2363 	gchar *fname_i;
2364 	gchar *version_i;
2365 	int i, n;
2366 
2367 	n = strlen(fname) - 4;
2368 
2369 	for (i=0; i<imax; i++) {
2370 	    gtk_tree_model_get(model, &iter,
2371 			       0, &fname_i,
2372 			       1, &version_i,
2373 			       -1);
2374 	    if (strncmp(fname, fname_i, n) == 0 &&
2375 		strcmp(version, version_i) == 0 &&
2376 		real_duplicate(fname, version, dirname, model, &iter)) {
2377 		ret = 1;
2378 	    }
2379 	    g_free(fname_i);
2380 	    g_free(version_i);
2381 	    if (ret || !gtk_tree_model_iter_next(model, &iter)) {
2382 		break;
2383 	    }
2384 	}
2385     }
2386 
2387     return ret;
2388 }
2389 
is_functions_dir(const char * path)2390 static int is_functions_dir (const char *path)
2391 {
2392     int n = strlen(path) - 9;
2393 
2394     return n > 0 && !strcmp(path + n, "functions");
2395 }
2396 
2397 /* For a function package that lives in its own directory,
2398    see if it has an "examples" subdir.
2399 */
2400 
have_examples(const char * dirname)2401 static int have_examples (const char *dirname)
2402 {
2403     struct stat sbuf;
2404     gchar *test;
2405     int ret = 0;
2406 
2407     test = g_strdup_printf("%s%cexamples", dirname, SLASH);
2408 
2409     if (stat(test, &sbuf) == 0 &&
2410 	(sbuf.st_mode & S_IFDIR)) {
2411 	ret = 1;
2412     }
2413 
2414     g_free(test);
2415 
2416     return ret;
2417 }
2418 
maybe_ellipsize_string(char * s,int maxlen)2419 char *maybe_ellipsize_string (char *s, int maxlen)
2420 {
2421     size_t n = g_utf8_strlen(s, -1);
2422 
2423     if (n > maxlen) {
2424 	gretl_utf8_truncate(s, maxlen - 3);
2425 	strcat(s, "...");
2426     }
2427 
2428     return s;
2429 }
2430 
2431 /* note: @summary is not const because it may get truncated */
2432 
browser_insert_gfn_info(const char * pkgname,const char * version,const char * date,const char * author,char * summary,const char * dirname,int uses_subdir,int pdfdoc,GtkListStore * store,GtkTreeIter * iter)2433 static void browser_insert_gfn_info (const char *pkgname,
2434 				     const char *version,
2435 				     const char *date,
2436 				     const char *author,
2437 				     char *summary,
2438 				     const char *dirname,
2439 				     int uses_subdir,
2440 				     int pdfdoc,
2441 				     GtkListStore *store,
2442 				     GtkTreeIter *iter)
2443 {
2444     char *tmp = NULL;
2445     gint flags = 0;
2446 
2447     if (uses_subdir) {
2448 	if (have_examples(dirname)) {
2449 	    flags |= PKG_ATTR_RES;
2450 	}
2451 	if (pdfdoc) {
2452 	    flags |= PKG_ATTR_DOC;
2453 	}
2454     }
2455 
2456     if (g_utf8_strlen(author, -1) > 26) {
2457 	tmp = gretl_strdup(author);
2458 	maybe_ellipsize_string(tmp, 26);
2459     }
2460     maybe_ellipsize_string(summary, 68);
2461 
2462     gtk_list_store_set(store, iter,
2463 		       0, pkgname,
2464 		       1, version,
2465 		       2, date,
2466 		       3, tmp ? tmp : author,
2467 		       4, summary,
2468 		       5, dirname,
2469 		       6, flags,
2470 		       -1);
2471     free(tmp);
2472 }
2473 
check_loaded_gfn(const char * pkgname,const char * fullname)2474 static void check_loaded_gfn (const char *pkgname,
2475 			      const char *fullname)
2476 {
2477     const char *path;
2478 
2479     path = get_function_package_path_by_name(pkgname);
2480 
2481     if (path != NULL && strcmp(path, fullname)) {
2482 	/* Watch out, there's a package of the same name already
2483 	   loaded from a different location.
2484 	*/
2485 	fnpkg *pkg = get_function_package_by_filename(path, NULL);
2486 	GtkWidget *editor = function_package_get_editor(pkg);
2487 
2488 	if (editor != NULL) {
2489 	    /* warn, don't unload the package being edited */
2490 	    gchar *msg;
2491 
2492 	    msg = g_strdup_printf("%s: package in new browser window may\n"
2493 				  "conflict with package editor", pkgname);
2494 	    gtk_window_present(GTK_WINDOW(editor));
2495 	    msgbox(msg, GTK_MESSAGE_WARNING, editor);
2496 	    g_free(msg);
2497 	} else {
2498 	    function_package_unload_full_by_filename(path);
2499 	}
2500     }
2501 }
2502 
2503 /* Run various checks on @fullname: if all is OK, return 1; if
2504    something is amiss, return 0.
2505 */
2506 
ok_gfn_path(const char * fullname,const char * shortname,const char * dirname,GtkListStore * store,GtkTreeIter * iter,int imax,int subdir)2507 static int ok_gfn_path (const char *fullname,
2508 			const char *shortname,
2509 			const char *dirname,
2510 			GtkListStore *store,
2511 			GtkTreeIter *iter,
2512 			int imax,
2513 			int subdir)
2514 {
2515     char *descrip = NULL;
2516     char *version = NULL;
2517     char *date = NULL;
2518     char *author = NULL;
2519     int pdfdoc = 0;
2520     int is_dup = 0;
2521     int err, ok = 0;
2522 
2523     /* Note that even if this is a dry run with @store = NULL,
2524        it may be worth performing the next action as a sanity
2525        check on the purported gfn.
2526     */
2527     err = get_func_info(fullname, &descrip, &version, &date,
2528 			&author, &pdfdoc);
2529 
2530     if (!err && store != NULL) {
2531 	is_dup = fn_file_is_duplicate(shortname, version,
2532 				      dirname, store, imax);
2533     }
2534 
2535 #if GFN_DEBUG > 1
2536     fprintf(stderr, "%s: %s: dups_ok=%d, is_dup=%d\n", dirname,
2537 	    shortname, dups_ok, is_dup);
2538 #endif
2539 
2540     if (!err && !is_dup) {
2541 	if (store != NULL && iter != NULL) {
2542 	    /* actually enter the file into the browser */
2543 	    gchar *pkgname = g_strndup(shortname, strlen(shortname) - 4);
2544 
2545 	    if (gfn_is_loaded(shortname)) {
2546 		check_loaded_gfn(pkgname, fullname);
2547 	    }
2548 
2549 	    gtk_list_store_append(store, iter);
2550 	    browser_insert_gfn_info(pkgname,
2551 				    version,
2552 				    date,
2553 				    author,
2554 				    descrip,
2555 				    dirname,
2556 				    subdir,
2557 				    pdfdoc,
2558 				    store,
2559 				    iter);
2560 
2561 	    g_free(pkgname);
2562 	}
2563 	ok = 1;
2564     }
2565 
2566     free(descrip);
2567     free(version);
2568     free(date);
2569     free(author);
2570 
2571     return ok;
2572 }
2573 
2574 /* Read (or simply just count) the .gfn files in a given directory.
2575    The signal to count rather than read is that the @store argument
2576    is NULL.
2577 */
2578 
2579 static void
read_fn_files_in_dir(GDir * dir,const char * path,GtkListStore * store,GtkTreeIter * iter,int * nfn)2580 read_fn_files_in_dir (GDir *dir, const char *path,
2581 		      GtkListStore *store,
2582 		      GtkTreeIter *iter,
2583 		      int *nfn)
2584 {
2585     const gchar *basename;
2586     char fullname[MAXLEN];
2587     int imax = *nfn;
2588 
2589     /* Look first for a gfn file in its own subdir, as
2590        in functions/foo/foo.gfn. That way if a package
2591        has been updated to zipfile status and there's
2592        also an older "plain gfn" version lying around
2593        we should get the newer one.
2594     */
2595 
2596     if (is_functions_dir(path)) {
2597 	while ((basename = g_dir_read_name(dir)) != NULL) {
2598 	    if (!strcmp(basename, ".") ||
2599 		!strcmp(basename, "..")) {
2600 		continue;
2601 	    }
2602 	    gretl_build_path(fullname, path, basename, NULL);
2603 	    if (gretl_isdir(fullname)) {
2604 		/* construct functions/foo/foo.gfn */
2605 		gchar *realbase, *realpath;
2606 
2607 		strcat(fullname, SLASHSTR);
2608 		strcat(fullname, basename);
2609 		strcat(fullname, ".gfn");
2610 		if (gretl_file_exists(fullname)) {
2611 		    realbase = g_strdup_printf("%s.gfn", basename);
2612 		    realpath = g_strdup_printf("%s%c%s", path, SLASH, basename);
2613 		    *nfn += ok_gfn_path(fullname, realbase, realpath,
2614 					store, iter, imax, 1);
2615 		    g_free(realbase);
2616 		    g_free(realpath);
2617 		} else {
2618 		    gretl_error_clear();
2619 		}
2620 	    }
2621 	}
2622 
2623 	imax = *nfn;
2624 	g_dir_rewind(dir);
2625     }
2626 
2627     /* then look for "plain gfn" files */
2628 
2629     while ((basename = g_dir_read_name(dir)) != NULL) {
2630 	if (!strcmp(basename, ".") ||
2631 	    !strcmp(basename, "..")) {
2632 	    continue;
2633 	}
2634 	if (has_suffix(basename, ".gfn")) {
2635 	    gretl_build_path(fullname, path, basename, NULL);
2636 	    *nfn += ok_gfn_path(fullname, basename, path,
2637 				store, iter, imax, 0);
2638 	}
2639     }
2640 }
2641 
2642 #if GFN_DEBUG
2643 
show_dirs_list(char ** S,int n,const char * msg)2644 static void show_dirs_list (char **S, int n, const char *msg)
2645 {
2646     int i;
2647 
2648     fprintf(stderr, "*** dirs list: %s\n", msg);
2649     for (i=0; i<n; i++) {
2650 	fprintf(stderr, " %d: '%s'\n", i, S[i]);
2651     }
2652 }
2653 
2654 #endif
2655 
2656 /* Populate browser displaying gfn files installed on local machine:
2657    this is always called with a "clean slate": either we're showing a
2658    new browser window, or we're recreating the default listing after
2659    the user has pointed the browser at another directory (in which
2660    case the prior listing has been cleared out by the time we get
2661    here).
2662 */
2663 
populate_gfn_list(windata_t * vwin)2664 static gint populate_gfn_list (windata_t *vwin)
2665 {
2666     GtkListStore *store;
2667     GtkTreeIter iter;
2668     char **dnames = NULL;
2669     int i, n_dirs = 0;
2670     int nfn = 0;
2671     int err = 0;
2672 
2673     store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vwin->listbox)));
2674     gtk_list_store_clear(store);
2675     gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
2676 
2677     /* compile an array of names of directories to search */
2678     dnames = get_plausible_search_dirs(FUNCS_SEARCH, &n_dirs);
2679 
2680 #if GFN_DEBUG
2681     show_dirs_list(dnames, n_dirs, "FUNCS_SEARCH");
2682 #endif
2683 
2684     for (i=0; i<n_dirs; i++) {
2685 	GDir *dir = gretl_opendir(dnames[i]);
2686 
2687 	if (dir != NULL) {
2688 	    read_fn_files_in_dir(dir, dnames[i], store, &iter, &nfn);
2689 	    g_dir_close(dir);
2690 	}
2691     }
2692 
2693     /* we're done with the directory names */
2694     strings_array_free(dnames, n_dirs);
2695 
2696     if (nfn == 0) {
2697 	/* we didn't find any gfn files */
2698 	warnbox(_("No gretl function packages were found on this computer.\n"
2699 		  "Please try /File/Functions packages/On server"));
2700 	err = 1;
2701     } else {
2702 	presort_treelist(vwin);
2703     }
2704 
2705     return err;
2706 }
2707 
gfn_paths_match(const char * p0,const char * p1,const char * pkgname)2708 static int gfn_paths_match (const char *p0, const char *p1,
2709 			    const char *pkgname)
2710 {
2711     int ret = 0;
2712 
2713     if (!strcmp(p0, p1)) {
2714 	ret = 1;
2715     } else {
2716 	/* allow for the possibility that @p1 has had a
2717 	   package-specific subdir appended, relative to @p0
2718 	*/
2719 	size_t n = strlen(p0);
2720 
2721 	if (strlen(p1) > n && !strncmp(p1, p0, n) &&
2722 	    IS_SLASH(p1[n])) {
2723 	    ret = !strcmp(p1 + n + 1, pkgname);
2724 	}
2725     }
2726 
2727     return ret;
2728 }
2729 
update_gfn_browser(const char * pkgname,const char * version,const char * date,const char * author,const char * descrip,const char * fname,int uses_subdir,int pdfdoc,windata_t * vwin)2730 static void update_gfn_browser (const char *pkgname,
2731 				const char *version,
2732 				const char *date,
2733 				const char *author,
2734 				const char *descrip,
2735 				const char *fname,
2736 				int uses_subdir,
2737 				int pdfdoc,
2738 				windata_t *vwin)
2739 {
2740     GtkTreeModel *model;
2741     GtkTreeIter iter;
2742     gchar *p, *dirname;
2743     gchar *summary;
2744     gchar *oldname, *oldver, *olddir;
2745     int dirmatch = 0;
2746     int done = 0;
2747 
2748     model = gtk_tree_view_get_model(GTK_TREE_VIEW(vwin->listbox));
2749     if (!gtk_tree_model_get_iter_first(model, &iter)) {
2750 	return;
2751     }
2752 
2753     dirname = g_strdup(fname);
2754     p = strrslash(dirname);
2755     if (p != NULL) {
2756 	*p = '\0';
2757     }
2758 
2759     /* in case of truncation */
2760     summary = g_strdup(descrip);
2761 
2762     while (1) {
2763 	gtk_tree_model_get(model, &iter, 0, &oldname, 1, &oldver,
2764 			   GFN_DIRNAME_COL, &olddir, -1);
2765 	if (!strcmp(oldname, pkgname) && !strcmp(oldver, version)) {
2766 	    /* Found a match for package name and version: so update
2767 	       the browser entry and record that we're done.
2768 	    */
2769 	    fprintf(stderr, "gfn update: updating %s %s\n", pkgname, version);
2770 	    browser_insert_gfn_info(pkgname, version, date, author, summary,
2771 				    dirname, uses_subdir, pdfdoc,
2772 				    GTK_LIST_STORE(model), &iter);
2773 	    done = 1;
2774 	} else if (!dirmatch) {
2775 	    dirmatch = gfn_paths_match(olddir, dirname, pkgname);
2776 	}
2777 	g_free(oldname); g_free(oldver); g_free(olddir);
2778 	if (done || !gtk_tree_model_iter_next(model, &iter)) {
2779 	    break;
2780 	}
2781     }
2782 
2783     if (!done && dirmatch) {
2784 	/* We didn't find an entry that matched by pkgname and
2785 	   version, but we did determine that the browser was
2786 	   pointing at a directory in which the package in
2787 	   question would be found, if it were re-read. So it
2788 	   seems we should append the package (and re-sort the
2789 	   package list).
2790 	*/
2791 	fprintf(stderr, "gfn update: appending %s %s\n", pkgname, version);
2792 	gtk_list_store_append(GTK_LIST_STORE(model), &iter);
2793 	browser_insert_gfn_info(pkgname, version, date, author, summary,
2794 				dirname, uses_subdir, pdfdoc,
2795 				GTK_LIST_STORE(model), &iter);
2796 	presort_treelist(vwin);
2797     }
2798 
2799     g_free(dirname);
2800     g_free(summary);
2801 }
2802 
2803 /* Update function package status, if needed, either after
2804    a call to save a function package, or after deleting a
2805    package by CLI means; the latter case is flagged by
2806    NULL values for @version and @descrip.
2807 */
2808 
maybe_update_gfn_browser(const char * pkgname,const char * version,const char * date,const char * author,const char * descrip,const char * fname,int uses_subdir,int pdfdoc)2809 void maybe_update_gfn_browser (const char *pkgname,
2810 			       const char *version,
2811 			       const char *date,
2812 			       const char *author,
2813 			       const char *descrip,
2814 			       const char *fname,
2815 			       int uses_subdir,
2816 			       int pdfdoc)
2817 {
2818     windata_t *vwin = get_browser_for_role(FUNC_FILES, NULL);
2819     int del = (version == NULL && descrip == NULL);
2820 
2821     if (vwin != NULL && vwin->listbox != NULL) {
2822 	if (del) {
2823 	    browser_delete_row_by_content(vwin, 0, pkgname,
2824 					  GFN_DIRNAME_COL, fname);
2825 	} else {
2826 	    update_gfn_browser(pkgname, version, date, author, descrip,
2827 			       fname, uses_subdir, pdfdoc, vwin);
2828 	}
2829     }
2830 }
2831 
maybe_update_pkg_registry_window(const char * pkgname,int code)2832 void maybe_update_pkg_registry_window (const char *pkgname,
2833 				       int code)
2834 {
2835     windata_t *vwin = get_browser_for_role(PKG_REGISTRY, NULL);
2836 
2837     if (vwin != NULL && vwin->listbox != NULL) {
2838 	if (code == MENU_ADD_FN_PKG) {
2839 	    populate_gfn_registry_list(vwin);
2840 	} else if (code == DELETE_FN_PKG) {
2841 	    browser_delete_row_by_content(vwin, 0, pkgname,
2842 					  0, NULL);
2843 	}
2844     }
2845 }
2846 
populate_filelist(windata_t * vwin,gpointer p)2847 gint populate_filelist (windata_t *vwin, gpointer p)
2848 {
2849     if (vwin->role == NATIVE_DB) {
2850 	return populate_dbfilelist(vwin, p);
2851     } else if (vwin->role == REMOTE_DB) {
2852 	return populate_remote_db_list(vwin);
2853     } else if (vwin->role == DBNOMICS_TOP) {
2854 	return populate_dbnomics_provider_list(vwin);
2855     } else if (vwin->role == DBNOMICS_DB) {
2856 	return populate_dbnomics_dataset_list(vwin, p);
2857     } else if (vwin->role == DBNOMICS_SERIES) {
2858 	return populate_dbnomics_series_list(vwin, p);
2859     } else if (vwin->role == REMOTE_FUNC_FILES) {
2860 	return populate_remote_func_list(vwin, 0);
2861     } else if (vwin->role == REMOTE_DATA_PKGS) {
2862 	return populate_remote_data_pkg_list(vwin);
2863     } else if (vwin->role == FUNC_FILES) {
2864 	return populate_gfn_list(vwin);
2865     } else if (vwin->role == REMOTE_ADDONS) {
2866 	return populate_remote_addons_list(vwin);
2867     } else if (vwin->role == PKG_REGISTRY) {
2868 	return populate_gfn_registry_list(vwin);
2869     } else {
2870 	return read_file_descriptions(vwin, p);
2871     }
2872 }
2873 
files_vbox(windata_t * vwin)2874 static GtkWidget *files_vbox (windata_t *vwin)
2875 {
2876     const char *data_titles[] = {
2877 	N_("File"),
2878 	N_("Summary"),
2879 	N_("Type")
2880     };
2881     const char *remote_data_titles[] = {
2882 	N_("File"),
2883 	N_("Source"),
2884 	N_("Date")
2885     };
2886     const char *ps_titles[] = {
2887 	N_("Script"),
2888 	N_("Topic"),
2889 	N_("Data")
2890     };
2891     const char *db_titles[] = {
2892 	N_("Database"),
2893 	N_("Source")
2894     };
2895     const char *remote_db_titles[] = {
2896 	N_("Database"),
2897 	N_("Source"),
2898 	N_("Local status")
2899     };
2900     const char *dbnomics_top_titles[] = {
2901 	N_("Code"),
2902 	N_("Name")
2903     };
2904     const char *dbnomics_db_titles[] = {
2905 	N_("Code"),
2906 	N_("Content")
2907     };
2908     const char *dbnomics_series_titles[] = {
2909 	N_("Code"),
2910 	N_("Description")
2911     };
2912     const char *func_titles[] = {
2913 	N_("Package"),
2914 	N_("Version"),
2915 	N_("Date"),
2916 	N_("Author"),
2917 	N_("Summary")
2918     };
2919     const char *remote_func_titles[] = {
2920 	N_("Package"),
2921 	N_("Version"),
2922 	N_("Date"),
2923 	N_("Author"),
2924 	N_("Summary"),
2925 	N_("Local status")
2926     };
2927     const char *addons_titles[] = {
2928 	N_("Package"),
2929 	N_("Version"),
2930 	N_("Date"),
2931 	N_("Local status")
2932     };
2933     const char *registry_titles[] = {
2934 	N_("Package"),
2935 	N_("Window"),
2936 	N_("Menu")
2937     };
2938 
2939     GType types_2[] = {
2940 	G_TYPE_STRING,
2941 	G_TYPE_STRING
2942     };
2943     GType types_3[] = {
2944 	G_TYPE_STRING,
2945 	G_TYPE_STRING,
2946 	G_TYPE_STRING
2947     };
2948     GType func_types[] = {
2949 	G_TYPE_STRING,
2950 	G_TYPE_STRING,
2951 	G_TYPE_STRING,
2952 	G_TYPE_STRING,
2953 	G_TYPE_STRING,
2954 	G_TYPE_STRING,   /* hidden string: directory */
2955 	G_TYPE_INT       /* hidden flags: has examples dir? doc? */
2956     };
2957     GType remote_func_types[] = {
2958 	G_TYPE_STRING,
2959 	G_TYPE_STRING,
2960 	G_TYPE_STRING,
2961 	G_TYPE_STRING,
2962 	G_TYPE_STRING,
2963 	G_TYPE_STRING,
2964 	G_TYPE_BOOLEAN,  /* hidden boolean: zipfile? */
2965 	G_TYPE_STRING    /* hidden string: dependencies */
2966     };
2967     GType addons_types[] = {
2968 	G_TYPE_STRING,
2969 	G_TYPE_STRING,
2970 	G_TYPE_STRING,
2971 	G_TYPE_STRING,
2972 	G_TYPE_STRING  /* hidden string: description */
2973     };
2974     const char **titles = data_titles;
2975     GType *types = types_2;
2976     int full_width = 580, file_height = 300;
2977     int hidden_cols = 0;
2978     int use_tree = 0;
2979     GtkWidget *vbox;
2980     int cols = 2;
2981 
2982     switch (vwin->role) {
2983     case TEXTBOOK_DATA:
2984 	titles = data_titles;
2985 	cols = 3;
2986 	full_width = 600;
2987 	break;
2988     case NATIVE_DB:
2989 	titles = db_titles;
2990 	cols = 3;
2991 	hidden_cols = 1;
2992 	break;
2993     case REMOTE_DB:
2994 	titles = remote_db_titles;
2995 	cols = 3;
2996 	use_tree = 1;
2997 	break;
2998     case DBNOMICS_TOP:
2999 	titles = dbnomics_top_titles;
3000 	full_width = 650;
3001 	break;
3002     case DBNOMICS_DB:
3003 	titles = dbnomics_db_titles;
3004 	full_width = 650;
3005 	break;
3006     case DBNOMICS_SERIES:
3007 	titles = dbnomics_series_titles;
3008 	full_width = 600;
3009 	break;
3010     case REMOTE_DATA_PKGS:
3011 	titles = remote_data_titles;
3012 	cols = 3;
3013 	full_width = 600;
3014 	break;
3015     case PS_FILES:
3016 	titles = ps_titles;
3017 	cols = 3;
3018 	full_width = 560;
3019 	break;
3020     case FUNC_FILES:
3021 	titles = func_titles;
3022 	types = func_types;
3023 	cols = G_N_ELEMENTS(func_types);
3024 	hidden_cols = 2;
3025 	full_width = 760;
3026 	file_height = 320;
3027 	break;
3028     case REMOTE_FUNC_FILES:
3029 	titles = remote_func_titles;
3030 	types = remote_func_types;
3031 	cols = G_N_ELEMENTS(remote_func_types);
3032 	hidden_cols = 2;
3033 	full_width = 760;
3034 	file_height = 340;
3035 	break;
3036     case REMOTE_ADDONS:
3037 	titles = addons_titles;
3038 	types = addons_types;
3039 	cols = G_N_ELEMENTS(addons_types);
3040 	hidden_cols = 1;
3041 	full_width = 400;
3042 	break;
3043     case PKG_REGISTRY:
3044 	titles = registry_titles;
3045 	cols = 3;
3046 	full_width = 600;
3047 	break;
3048     default:
3049 	break;
3050     }
3051 
3052     if (cols == 3) {
3053 	types = types_3;
3054     }
3055 
3056     full_width *= gui_scale;
3057     file_height *= gui_scale;
3058 
3059     vbox = gtk_vbox_new(FALSE, 0);
3060     gtk_widget_set_size_request(vbox, full_width, file_height);
3061     /* note: the following packs and attaches vwin->listbox */
3062     vwin_add_list_box(vwin, GTK_BOX(vbox), cols, hidden_cols,
3063 		      types, titles, use_tree);
3064     gtk_widget_show(vbox);
3065 
3066     return vbox;
3067 }
3068 
switch_files_page(GtkNotebook * notebook,GtkWidget * page,guint pgnum,windata_t * vwin)3069 static void switch_files_page (GtkNotebook *notebook,
3070 			       GtkWidget *page,
3071 			       guint pgnum,
3072 			       windata_t *vwin)
3073 {
3074     GtkWidget *tab = gtk_notebook_get_nth_page(notebook, pgnum);
3075 
3076     vwin->listbox = g_object_get_data(G_OBJECT(tab), "listbox");
3077 }
3078 
3079 /* below: construct a set of notebook pages for either data file
3080    collections (Ramanathan, Wooldridge, etc.) or example scripts.
3081    The function creates the pages but does not yet fill them out.
3082 */
3083 
files_notebook(windata_t * vwin,int role)3084 static GtkWidget *files_notebook (windata_t *vwin, int role)
3085 {
3086     GList *L = NULL;
3087     file_collection *collection;
3088     GtkWidget *notebook;
3089     GtkWidget *page;
3090     GtkWidget *label;
3091     int err = 0;
3092 
3093     /* assemble the info we'll need */
3094     err = build_file_collections(role);
3095     if (err) {
3096 	fprintf(stderr, "files_notebook: build_files_collections failed\n");
3097 	return NULL;
3098     }
3099 
3100     L = collections_for_role(role);
3101     if (L == NULL) {
3102 	return NULL;
3103     }
3104 
3105     notebook = gtk_notebook_new();
3106     gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), TRUE);
3107 
3108     while (L) {
3109 	collection = L->data;
3110 	page = files_vbox(vwin);
3111 	label = gtk_label_new(collection->title);
3112 	gtk_widget_show(label);
3113 	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, label);
3114 	collection->listbox = vwin->listbox;
3115 	g_object_set_data(G_OBJECT(collection->listbox), "collection",
3116 			  collection);
3117 	g_object_set_data(G_OBJECT(page), "listbox", collection->listbox);
3118 	g_signal_connect(G_OBJECT(collection->listbox), "key-press-event",
3119 			 G_CALLBACK(enter_opens_file), vwin);
3120 	L = L->next;
3121     }
3122 
3123     g_signal_connect(G_OBJECT(notebook), "switch-page",
3124 		     G_CALLBACK(switch_files_page),
3125 		     vwin);
3126     if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook)) > 5) {
3127 	gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook));
3128     }
3129 
3130     gtk_widget_show(notebook);
3131 
3132     return notebook;
3133 }
3134 
3135 /* below: fill out a set of notebook pages (for data files
3136    or script files), entering the details into the page,
3137    then select the page to display
3138 */
3139 
populate_notebook_filelists(windata_t * vwin,GtkWidget * notebook,int role)3140 static int populate_notebook_filelists (windata_t *vwin,
3141 					GtkWidget *notebook,
3142 					int role)
3143 {
3144     file_collection *collection;
3145     file_collection *selected = NULL;
3146     const char *page = NULL;
3147     GList *L = NULL;
3148     int found = 0;
3149     int pgnum = 0;
3150 
3151     L = collections_for_role(role);
3152     if (L == NULL) {
3153 	return 1;
3154     }
3155 
3156     if (role == TEXTBOOK_DATA) {
3157 	page = get_datapage();
3158     } else if (role == PS_FILES) {
3159 	page = get_scriptpage();
3160     }
3161 
3162     selected = L->data;
3163 
3164     while (L) {
3165 	collection = L->data;
3166 	vwin->listbox = collection->listbox;
3167 	populate_filelist(vwin, collection);
3168 	if (page != NULL && !strcmp(collection->title, page)) {
3169 	    selected = collection;
3170 	    pgnum = found;
3171 	}
3172 	found++;
3173 	L = L->next;
3174     }
3175 
3176     if (found == 0) {
3177 	/* didn't find anything to show */
3178 	fprintf(stderr, "populate_notebook_filelists: found = 0!\n");
3179 	return 1;
3180     }
3181 
3182     vwin->listbox = selected->listbox;
3183     gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), pgnum);
3184     gtk_widget_grab_focus(vwin->listbox);
3185 
3186     return 0;
3187 }
3188