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 #include "gretl.h"
21 #include "version.h"
22 #include "gretl_xml.h"
23 #include "libset.h"
24 #include "dlgutils.h"
25 #include "datafiles.h"
26 #include "textbuf.h"
27 #include "fileselect.h"
28 #include "filelists.h"
29 #include "gretl_www.h"
30 #include "gretl_zip.h"
31 #include "winstack.h"
32 #include "menustate.h"
33 #include "selector.h"
34 #include "textutil.h"
35 #include "ssheet.h"
36 #include "fncall.h"
37 #include "fnsave.h"
38 
39 #include <libxml/xmlmemory.h>
40 #include <libxml/parser.h>
41 
42 #ifdef G_OS_WIN32
43 # include "gretlwin32.h"
44 #endif
45 
46 #include "gretl_func.h"
47 #include "gretl_typemap.h"
48 
49 #define PKG_DEBUG 0
50 #define N_ENTRIES 5
51 #define N_FILE_ENTRIES 4
52 #define N_DEP_ENTRIES 4
53 #define N_SPECIALS (UFUN_ROLE_MAX - 1)
54 #define HELP_HEIGHT 400
55 
56 enum {
57     NO_WINDOW,
58     MAIN_WINDOW,
59     MODEL_WINDOW
60 };
61 
62 enum {
63     APPEND_SAMPLE  = 1 << 0, /* write functions as .inp: append sample? */
64     WRITE_SAMPFILE = 1 << 1, /* write .spec file: also write sample script? */
65     WRITE_HELPFILE = 1 << 2, /* write .spec file: also write help file? */
66     WRITE_GUI_HELP = 1 << 3  /* write .spec file: also write gui help? */
67 } PkgSaveFlags;
68 
69 typedef struct function_info_ function_info;
70 typedef struct login_info_ login_info;
71 
72 /* package-editing dialog and associated info */
73 
74 struct function_info_ {
75     GtkWidget *dlg;        /* editing dialog box */
76     GtkWidget *entries[N_ENTRIES];           /* author, etc. */
77     GtkWidget *file_entries[N_FILE_ENTRIES]; /* data files */
78     GtkWidget *dep_entries[N_DEP_ENTRIES];   /* dependencies */
79     GtkWidget *prov_check; /* "provider" selected? */
80     GtkWidget *codesel;    /* code-editing selector */
81     GtkWidget *popup;      /* popup menu */
82     GtkWidget *extra;      /* extra properties child dialog */
83     GtkWidget *maintree;   /* main menu selection tree */
84     GtkWidget *modeltree;  /* model menu selection tree */
85     GtkWidget *currtree;   /* currently displayed menu treeview */
86     GtkWidget *alttree;    /* currently undisplayed menu treeview */
87     GtkWidget *treewin;    /* scrolled window to hold menu trees */
88     GtkWidget *mreq_combo; /* model requirement selector */
89     GtkWidget *data_button; /* data access request button */
90     GtkWidget *specdlg;    /* pkg spec save dialog */
91     GtkWidget *validate;   /* "Validate" gfn button */
92     GtkWidget *tagsel[2];  /* tag selector combos */
93     windata_t *samplewin;  /* window for editing sample script */
94     windata_t *helpwin;    /* window for editing regular help text */
95     windata_t *gui_helpwin; /* window for editing GUI-specific help text */
96     GtkUIManager *ui;      /* for dialog File menu */
97     GList *codewins;       /* list of windows editing function code */
98     fnpkg *pkg;            /* pointer to package being edited */
99     gchar *ininame;        /* initial name for new package (temporary) */
100     gchar *fname;          /* package filename */
101     gchar *author;         /* package author */
102     gchar *email;          /* author's email address */
103     gchar *version;        /* package version number */
104     gchar *date;           /* package last-revised date */
105     gchar *pkgdesc;        /* package description */
106     gchar *tags;           /* package tag(s) */
107     gchar *sample;         /* sample script for package */
108     gchar *help;           /* package help text */
109     gchar *gui_help;       /* GUI-specific help text */
110     gchar *sample_fname;   /* filename: sample script */
111     gchar *help_fname;     /* filename: help text */
112     gchar *gui_help_fname; /* filename: GUI-specific help */
113     gchar *pdfname;        /* name of PDF help file */
114     char **pubnames;       /* names of public functions */
115     char **privnames;      /* names of private functions */
116     char **specials;       /* names of special functions */
117     char **datafiles;      /* names of included data files */
118     char **depends;        /* names of dependencies */
119     int n_pub;             /* number of public functions */
120     int n_priv;            /* number of private functions */
121     int n_files;           /* number of included data files */
122     int n_depends;         /* number of dependencies */
123     gchar *provider;       /* name of "provider" package */
124     gboolean uses_subdir;  /* the package has its own subdir (0/1) */
125     gboolean data_access;  /* the package wants access to full data range */
126     gboolean pdfdoc;       /* the package has PDF documentation */
127     gchar *menupath;       /* path for menu attachment, if any */
128     gchar *menulabel;      /* label for menu attachment, if any */
129     int menuwin;           /* code for none/main/model window */
130     char *active;          /* name of 'active' function */
131     DataReq dreq;          /* data requirement of package */
132     GretlCmdIndex mreq;    /* model requirement of package */
133     int minver;            /* minimum gretl version, package */
134     gboolean modified;     /* anything changed in package? */
135     int save_flags;        /* see PkgSaveFlags */
136     unsigned char gui_attrs[N_SPECIALS]; /* attribute flags for special funcs */
137 };
138 
139 /* info relating to login to server for upload */
140 
141 struct login_info_ {
142     GtkWidget *dlg;
143     GtkWidget *login_entry;
144     GtkWidget *pass_entry;
145     char *login;
146     char *pass;
147     int canceled;
148 };
149 
150 static int validate_package_file (const char *fname,
151 				  int verbose);
152 static void finfo_set_menuwin (function_info *finfo);
153 static gint query_save_package (GtkWidget *w, GdkEvent *event,
154 				function_info *finfo);
155 static int finfo_save (function_info *finfo);
156 static void gfn_to_script_callback (function_info *finfo);
157 static void gfn_to_spec_callback (function_info *finfo);
158 static void do_pkg_upload (function_info *finfo);
159 static void edit_code_callback (GtkWidget *w, function_info *finfo);
160 static int check_package_filename (const char *fname,
161 				   int fullpath,
162 				   GtkWidget *parent);
163 static void regular_help_text_callback (GtkButton *b,
164 					function_info *finfo);
165 static void edit_sample_callback (GtkWidget *w, function_info *finfo);
166 static const char *finfo_pkgname (function_info *finfo);
167 
finfo_new(void)168 function_info *finfo_new (void)
169 {
170     function_info *finfo;
171 
172     finfo = mymalloc(sizeof *finfo);
173     if (finfo == NULL) {
174 	return NULL;
175     }
176 
177     finfo->specials = strings_array_new(N_SPECIALS);
178     if (finfo->specials == NULL) {
179 	free(finfo);
180 	return NULL;
181     }
182 
183     memset(finfo->gui_attrs, 0, N_SPECIALS);
184 
185     finfo->pkg = NULL;
186     finfo->fname = NULL;
187     finfo->ininame = NULL;
188     finfo->author = NULL;
189     finfo->email = NULL;
190     finfo->version = NULL;
191     finfo->date = NULL;
192     finfo->pkgdesc = NULL;
193     finfo->tags = NULL;
194     finfo->sample = NULL;
195     finfo->menupath = NULL;
196     finfo->menulabel = NULL;
197     finfo->menuwin = 0;
198 
199     finfo->currtree = NULL;
200     finfo->alttree = NULL;
201     finfo->treewin = NULL;
202     finfo->ui = NULL;
203 
204     finfo->modified = FALSE;
205     finfo->save_flags = WRITE_SAMPFILE |
206 	WRITE_HELPFILE | WRITE_GUI_HELP;
207 
208     finfo->active = NULL;
209     finfo->samplewin = NULL;
210     finfo->helpwin = NULL;
211     finfo->gui_helpwin = NULL;
212     finfo->codewins = NULL;
213     finfo->codesel = NULL;
214     finfo->popup = NULL;
215     finfo->extra = NULL;
216     finfo->specdlg = NULL;
217 
218     finfo->tagsel[0] = NULL;
219     finfo->tagsel[1] = NULL;
220 
221     finfo->help = NULL;
222     finfo->gui_help = NULL;
223 
224     finfo->sample_fname = NULL;
225     finfo->help_fname = NULL;
226     finfo->gui_help_fname = NULL;
227     finfo->pdfname = NULL;
228 
229     finfo->pubnames = NULL;
230     finfo->privnames = NULL;
231     finfo->datafiles = NULL;
232     finfo->depends = NULL;
233 
234     finfo->n_pub = 0;
235     finfo->n_priv = 0;
236     finfo->n_files = 0;
237     finfo->n_depends = 0;
238     finfo->provider = NULL;
239 
240     finfo->dreq = 0;
241     finfo->minver = 10900;
242     finfo->uses_subdir = 0;
243     finfo->data_access = 0;
244     finfo->pdfdoc = 0;
245 
246     return finfo;
247 }
248 
funname_from_filename(const char * fname)249 static const char *funname_from_filename (const char *fname)
250 {
251     const char *p = strrchr(fname, '.');
252 
253     return p + 1;
254 }
255 
filename_from_funname(char * fname,const char * funname)256 static char *filename_from_funname (char *fname,
257 				    const char *funname)
258 {
259     gretl_build_path(fname, gretl_dotdir(), "pkgedit", NULL);
260     strcat(fname, ".");
261     strcat(fname, funname);
262     return fname;
263 }
264 
destroy_code_window(windata_t * vwin,gpointer p)265 static void destroy_code_window (windata_t *vwin, gpointer p)
266 {
267     gtk_widget_destroy(vwin->main);
268 }
269 
finfo_free(function_info * finfo)270 static void finfo_free (function_info *finfo)
271 {
272     g_free(finfo->fname);
273     g_free(finfo->author);
274     g_free(finfo->email);
275     g_free(finfo->version);
276     g_free(finfo->date);
277     g_free(finfo->pkgdesc);
278     g_free(finfo->tags);
279     g_free(finfo->sample);
280     g_free(finfo->help);
281     g_free(finfo->gui_help);
282 
283     g_free(finfo->ininame);
284     g_free(finfo->sample_fname);
285     g_free(finfo->help_fname);
286     g_free(finfo->gui_help_fname);
287     g_free(finfo->pdfname);
288 
289     g_free(finfo->menupath);
290     g_free(finfo->menulabel);
291 
292     if (finfo->pubnames != NULL) {
293 	strings_array_free(finfo->pubnames, finfo->n_pub);
294     }
295 
296     if (finfo->privnames != NULL) {
297 	strings_array_free(finfo->privnames, finfo->n_priv);
298     }
299 
300     if (finfo->specials != NULL) {
301 	strings_array_free(finfo->specials, N_SPECIALS);
302     }
303 
304     if (finfo->datafiles != NULL) {
305 	strings_array_free(finfo->datafiles, finfo->n_files);
306     }
307 
308     if (finfo->depends != NULL) {
309 	strings_array_free(finfo->depends, finfo->n_depends);
310     }
311 
312     if (finfo->provider != NULL) {
313 	g_free(finfo->provider);
314     }
315 
316     if (finfo->samplewin != NULL) {
317 	gtk_widget_destroy(finfo->samplewin->main);
318     }
319 
320     if (finfo->helpwin != NULL) {
321 	gtk_widget_destroy(finfo->helpwin->main);
322     }
323 
324     if (finfo->gui_helpwin != NULL) {
325 	gtk_widget_destroy(finfo->gui_helpwin->main);
326     }
327 
328     if (finfo->codewins != NULL) {
329 	g_list_foreach(finfo->codewins, (GFunc) destroy_code_window, NULL);
330 	g_list_free(finfo->codewins);
331     }
332 
333     if (finfo->ui != NULL) {
334 	g_object_unref(finfo->ui);
335     }
336 
337     if (finfo->popup != NULL) {
338 	gtk_widget_destroy(finfo->popup);
339     }
340 
341     free(finfo);
342 }
343 
pkg_save_action(GtkAction * action,function_info * finfo)344 static void pkg_save_action (GtkAction *action, function_info *finfo)
345 {
346     const gchar *s = gtk_action_get_name(action);
347 
348     if (!strcmp(s, "Save")) {
349 	finfo_save(finfo);
350     } else if (!strcmp(s, "SaveZip")) {
351 	file_selector_with_parent(SAVE_GFN_ZIP, FSEL_DATA_MISC,
352 				  finfo, finfo->dlg);
353     } else if (!strcmp(s, "WriteInp")) {
354 	gfn_to_script_callback(finfo);
355     } else if (!strcmp(s, "WriteSpec")) {
356 	gfn_to_spec_callback(finfo);
357     } else if (!strcmp(s, "Upload")) {
358 	do_pkg_upload(finfo);
359     }
360 }
361 
362 const gchar *pkgsave_ui =
363     "<ui>"
364     "  <popup>"
365     "    <menuitem action='Save'/>"
366     "    <menuitem action='SaveZip'/>"
367     "    <menuitem action='WriteInp'/>"
368     "    <menuitem action='WriteSpec'/>"
369     "    <menuitem action='Upload'/>"
370     "  </popup>"
371     "</ui>";
372 
373 static GtkActionEntry pkgsave_items[] = {
374     { "Save", NULL, N_("_Save gfn"), NULL, NULL, G_CALLBACK(pkg_save_action) },
375     { "SaveZip", NULL, N_("Save _zip file..."), NULL, NULL, G_CALLBACK(pkg_save_action) },
376     { "WriteInp", NULL, N_("Save as _script..."), NULL, NULL, G_CALLBACK(pkg_save_action) },
377     { "WriteSpec", NULL, N_("_Write spec file..."), NULL, NULL, G_CALLBACK(pkg_save_action) },
378     { "Upload", NULL, N_("_Upload to server..."), NULL, NULL, G_CALLBACK(pkg_save_action) },
379 };
380 
save_popup_pos(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer data)381 static void save_popup_pos (GtkMenu *menu,
382 			    gint *x,
383 			    gint *y,
384 			    gboolean *push_in,
385 			    gpointer data)
386 {
387     GtkWidget *button = data;
388     gint wx, wy, tx, ty;
389 
390     gdk_window_get_origin(gtk_widget_get_window(button), &wx, &wy);
391     gtk_widget_translate_coordinates(button, gtk_widget_get_toplevel(button),
392 				     0, 0, &tx, &ty);
393     *x = wx + tx - 80;
394     *y = wy + ty - 128;
395     *push_in = TRUE;
396 }
397 
pkg_save_popup(GtkWidget * button,function_info * finfo)398 static void pkg_save_popup (GtkWidget *button,
399 			    function_info *finfo)
400 {
401     GtkWidget *menu;
402     gboolean cond;
403 
404     if (finfo->ui == NULL) {
405 	GtkActionGroup *actions;
406 
407 	finfo->ui = gtk_ui_manager_new();
408 	actions = gtk_action_group_new("PkgActions");
409 	gtk_action_group_set_translation_domain(actions, "gretl");
410 	gtk_action_group_add_actions(actions, pkgsave_items,
411 				     G_N_ELEMENTS(pkgsave_items),
412 				     finfo);
413 	gtk_ui_manager_add_ui_from_string(finfo->ui, pkgsave_ui, -1, NULL);
414 	gtk_ui_manager_insert_action_group(finfo->ui, actions, 0);
415 	g_object_unref(actions);
416     }
417 
418     /* set menu item sensitivities */
419     flip(finfo->ui, "/popup/Save", finfo->modified);
420     cond = finfo->fname != NULL;
421     flip(finfo->ui, "/popup/Upload", cond);
422     cond = finfo->pdfdoc || finfo->datafiles != NULL;
423     flip(finfo->ui, "/popup/SaveZip", cond && !finfo->modified);
424 
425     menu = gtk_ui_manager_get_widget(finfo->ui, "/popup");
426 
427     gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
428 		   save_popup_pos,
429 		   button, 0,
430 		   gtk_get_current_event_time());
431 }
432 
finfo_set_modified(function_info * finfo,gboolean s)433 static void finfo_set_modified (function_info *finfo, gboolean s)
434 {
435     if (s != finfo->modified) {
436 	gchar *tmp;
437 
438 	finfo->modified = s;
439 	if (s) {
440 	    tmp = g_strdup_printf("gretl: %s *", finfo_pkgname(finfo));
441 	} else {
442 	    tmp = g_strdup_printf("gretl: %s", finfo_pkgname(finfo));
443 	}
444 	gtk_window_set_title(GTK_WINDOW(finfo->dlg), tmp);
445 	g_free(tmp);
446     }
447 }
448 
login_init_or_free(login_info * linfo,int freeit)449 static void login_init_or_free (login_info *linfo, int freeit)
450 {
451     static gchar *login;
452     static gchar *pass;
453 
454     if (freeit) {
455 	if (!linfo->canceled) {
456 	    g_free(login);
457 	    g_free(pass);
458 	    login = g_strdup(linfo->login);
459 	    pass = g_strdup(linfo->pass);
460 	}
461 	g_free(linfo->login);
462 	g_free(linfo->pass);
463     } else {
464 	linfo->login = (login == NULL)? NULL : g_strdup(login);
465 	linfo->pass = (pass == NULL)? NULL : g_strdup(pass);
466 	linfo->canceled = 1;
467     }
468 }
469 
login_init(login_info * linfo)470 static void login_init (login_info *linfo)
471 {
472     login_init_or_free(linfo, 0);
473 }
474 
linfo_free(login_info * linfo)475 static void linfo_free (login_info *linfo)
476 {
477     login_init_or_free(linfo, 1);
478 }
479 
login_finalize(GtkWidget * w,login_info * linfo)480 static void login_finalize (GtkWidget *w, login_info *linfo)
481 {
482     linfo->login = entry_box_get_trimmed_text(linfo->login_entry);
483     if (linfo->login == NULL) {
484 	gtk_widget_grab_focus(linfo->login_entry);
485 	return;
486     }
487 
488     linfo->pass = entry_box_get_trimmed_text(linfo->pass_entry);
489     if (linfo->pass == NULL) {
490 	gtk_widget_grab_focus(linfo->pass_entry);
491 	g_free(linfo->login);
492 	return;
493     }
494 
495     gtk_widget_destroy(linfo->dlg);
496 }
497 
finfo_pkgname(function_info * finfo)498 static const char *finfo_pkgname (function_info *finfo)
499 {
500     if (finfo->pkg != NULL) {
501 	return function_package_get_name(finfo->pkg);
502     } else if (finfo->ininame != NULL) {
503 	return finfo->ininame;
504     } else {
505 	/* "can't happen" */
506 	return "untitled";
507     }
508 }
509 
510 /* Used by the File, Save dialog when saving a package,
511    or saving packaged functions as a script, or writing
512    a .spec file based on a package.
513 */
514 
get_default_package_name(char * fname,gpointer p,int mode)515 void get_default_package_name (char *fname, gpointer p, int mode)
516 {
517     function_info *finfo = (function_info *) p;
518     const char *pkgname = finfo_pkgname(finfo);
519 
520     *fname = '\0';
521 
522     if (mode == SELECT_PDF) {
523 	if (finfo->pdfname != NULL) {
524 	    strcpy(fname, finfo->pdfname);
525 	} else if (finfo->fname != NULL) {
526 	    switch_ext(fname, finfo->fname, "pdf");
527 	}
528 	if (*fname != '\0') {
529 	    /* should be an existing file, or scrub it */
530 	    if (!gretl_file_exists(fname)) {
531 		*fname = '\0';
532 	    }
533 	}
534     } else {
535 	strcpy(fname, pkgname);
536  	if (mode == SAVE_FUNCTIONS_AS) {
537 	    strcat(fname, ".inp");
538 	} else if (mode == SAVE_GFN_SPEC) {
539 	    strcat(fname, ".spec");
540 	} else if (mode == SAVE_GFN_ZIP) {
541 	    strcat(fname, ".zip");
542 	} else {
543 	    strcat(fname, ".gfn");
544 	}
545     }
546 }
547 
548 /* fairly minimal check here! */
549 
check_email_string(const char * s)550 static int check_email_string (const char *s)
551 {
552     int err = 0;
553 
554     if (strchr(s, ' ') != NULL) {
555 	/* no spaces allowed */
556 	err = 1;
557     } else if (strchr(s, '@') == NULL) {
558 	/* must include "at"-sign */
559 	err = 1;
560     }
561 
562     return err;
563 }
564 
565 /* Check the user-supplied version string for the package: should be
566    something like "1" or "1.02"
567 */
568 
check_version_string(const char * s)569 static int check_version_string (const char *s)
570 {
571     int dotcount = 0;
572     int err = 0;
573 
574     /* must start and end with a digit */
575     if (!isdigit(*s) || (*s && !isdigit(s[strlen(s) - 1]))) {
576 	err = 1;
577     }
578 
579     while (*s && !err) {
580 	if (!isdigit(*s) && *s != '.') {
581 	    /* only dots and digits allowed */
582 	    err = 1;
583 	} else if (*s == '.' && ++dotcount > 1) {
584 	    /* max of one dot exceeded */
585 	    err = 1;
586 	}
587 	s++;
588     }
589 
590     return err;
591 }
592 
pkg_path_is_toplevel(function_info * finfo,const char * pkgname)593 static int pkg_path_is_toplevel (function_info *finfo,
594 				 const char *pkgname)
595 {
596     gchar *test;
597     int ret;
598 
599     /* look for pattern "functions/mypkg.gfn" */
600     test = g_strdup_printf("functions%c%s.gfn", SLASH, pkgname);
601     ret = strstr(finfo->fname, test) != NULL;
602     g_free(test);
603 
604     return ret;
605 }
606 
set_gfn_save_opt(GtkWidget * w,int * opt)607 static void set_gfn_save_opt (GtkWidget *w, int *opt)
608 {
609     *opt = widget_get_int(w, "action");
610 }
611 
save_gfn_ok(GtkButton * button,GtkWidget * dialog)612 static void save_gfn_ok (GtkButton *button, GtkWidget *dialog)
613 {
614     gtk_widget_destroy(dialog);
615 }
616 
save_gfn_cancel(GtkButton * button,int * retval)617 static void save_gfn_cancel (GtkButton *button, int *retval)
618 {
619     GtkWidget *dialog;
620 
621     dialog = g_object_get_data(G_OBJECT(button), "dialog");
622     *retval = GRETL_CANCEL;
623     gtk_widget_destroy(dialog);
624 }
625 
save_gfn_delete(GtkWidget * w,GdkEvent * event,int * retval)626 static void save_gfn_delete (GtkWidget *w, GdkEvent *event, int *retval)
627 {
628     *retval = GRETL_CANCEL;
629 }
630 
save_gfn_dialog(function_info * finfo)631 static int save_gfn_dialog (function_info *finfo)
632 {
633     const char *opts[] = {
634 	N_("Save the file to its standard \"installed\" location"),
635 	N_("Save it to a location of your own choosing")
636     };
637     GtkWidget *dialog;
638     GtkWidget *vbox, *hbox, *label;
639     GtkWidget *button = NULL;
640     GSList *group = NULL;
641     int i, ret = 0;
642 
643     if (maybe_raise_dialog()) {
644 	return ret;
645     }
646 
647     dialog = gretl_dialog_new(NULL, finfo->dlg, GRETL_DLG_BLOCK);
648     g_signal_connect(G_OBJECT(dialog), "delete-event",
649 		     G_CALLBACK(save_gfn_delete), &ret);
650     vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
651 
652     hbox = gtk_hbox_new(FALSE, 5);
653     gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
654     gtk_widget_show(hbox);
655     label = gtk_label_new(_("Save gfn file"));
656     gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 5);
657 
658     for (i=0; i<2; i++) {
659 	button = gtk_radio_button_new_with_label(group, _(opts[i]));
660 	gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, TRUE, 0);
661 	g_object_set_data(G_OBJECT(button), "action", GINT_TO_POINTER(i));
662 	g_signal_connect(G_OBJECT(button), "clicked",
663 			 G_CALLBACK(set_gfn_save_opt), &ret);
664 	if (i == 0) {
665 	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (button), TRUE);
666 	}
667 	group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button));
668     }
669 
670     hbox = gtk_dialog_get_action_area(GTK_DIALOG(dialog));
671 
672     /* "Cancel" */
673     button = cancel_button(hbox);
674     gtk_widget_set_can_default(button, FALSE);
675     g_object_set_data(G_OBJECT(button), "dialog", dialog);
676     g_signal_connect(G_OBJECT(button), "clicked",
677 		     G_CALLBACK(save_gfn_cancel), &ret);
678 
679     /* "OK" */
680     button = ok_button(hbox);
681     g_signal_connect(G_OBJECT(button), "clicked",
682 		     G_CALLBACK(save_gfn_ok), dialog);
683     gtk_widget_grab_default(button);
684 
685     gtk_widget_show_all(dialog);
686 
687     return ret;
688 }
689 
overwrite_gfn_check(const char * fname,GtkWidget * parent,int * notified)690 static int overwrite_gfn_check (const char *fname,
691 				GtkWidget *parent,
692 				int *notified)
693 {
694     int resp = GRETL_YES;
695 
696     if (gretl_file_exists(fname)) {
697 	gchar *msg;
698 
699 	msg = g_strdup_printf("%s\n\n%s\n%s", fname,
700 			      _("A file of this name already exists."),
701 			      _("OK to overwrite it?"));
702 	resp = yes_no_dialog(NULL, msg, parent);
703 	g_free(msg);
704 	if (notified != NULL) {
705 	    *notified = 1;
706 	}
707     }
708 
709     return resp;
710 }
711 
712 /* In saving a new package, the user has chosen to write to
713    the "install" location. We need to figure out the correct
714    path (possibly creating a package-specific directory) then
715    save the gfn file. We should give the user some feedback
716    whether or not this is successful.
717 */
718 
install_gfn(function_info * finfo)719 static int install_gfn (function_info *finfo)
720 {
721     char savepath[FILENAME_MAX];
722     gchar *msg;
723     int notified = 0;
724     int err = 0;
725 
726     get_default_dir_for_action(savepath, SAVE_FUNCTIONS);
727 
728     if (finfo->uses_subdir) {
729 	strcat(savepath, finfo_pkgname(finfo));
730 	err = gretl_mkdir(savepath);
731 	if (err) {
732 	    gui_errmsg(err);
733 	} else {
734 	    slash_terminate(savepath);
735 	}
736     }
737 
738     if (!err) {
739 	int resp;
740 
741 	strcat(savepath, finfo_pkgname(finfo));
742 	strcat(savepath, ".gfn");
743 	resp = overwrite_gfn_check(savepath, finfo->dlg,
744 				   &notified);
745 	if (resp != GRETL_YES) {
746 	    return 0;
747 	}
748 	err = save_function_package(savepath, finfo);
749     }
750 
751     if (!err && !notified) {
752 	msg = g_strdup_printf(_("Wrote gfn file as\n%s"), savepath);
753 	msgbox(msg, GTK_MESSAGE_INFO, finfo->dlg);
754 	g_free(msg);
755     }
756 
757     return err;
758 }
759 
760 /* Callback from "Save", when editing a function package. We first
761    assemble and check the relevant info then if the package is new and
762    has not been saved yet (which is flagged by finfo->fname being
763    NULL) we offer a file selector, otherwise we go ahead and save
764    using the package's recorded filename.
765 */
766 
finfo_save(function_info * finfo)767 static int finfo_save (function_info *finfo)
768 {
769     const char *missing = "???";
770     char **fields[] = {
771 	&finfo->author,
772 	&finfo->email,
773 	&finfo->version,
774 	&finfo->date,
775 	&finfo->pkgdesc
776     };
777     int i, err = 0;
778 
779     for (i=0; i<N_ENTRIES && !err; i++) {
780 	g_free(*fields[i]);
781 	*fields[i] = entry_box_get_trimmed_text(finfo->entries[i]);
782 	if (*fields[i] == NULL || !strcmp(*fields[i], missing)) {
783 	    warnbox(_("Please complete all fields"));
784 	    gtk_entry_set_text(GTK_ENTRY(finfo->entries[i]), missing);
785 	    gtk_editable_select_region(GTK_EDITABLE(finfo->entries[i]), 0, -1);
786 	    gtk_widget_grab_focus(finfo->entries[i]);
787 	    return 1;
788 	}
789     }
790 
791     if (!finfo->pdfdoc) {
792 	int fixit = 0;
793 
794 	if (finfo->help == NULL || *finfo->help == '\0') {
795 	    warnbox(_("Please add some help text for this package"));
796 	    fixit = 1;
797 	} else if (strstr(finfo->help, "pdfdoc:") != NULL) {
798 	    warnbox(_("Please delete the \"pdfdoc:\" line from the help text"));
799 	    fixit = 1;
800 	}
801 	if (fixit) {
802 	    regular_help_text_callback(NULL, finfo);
803 	    return 1;
804 	}
805     }
806 
807     if (finfo->sample == NULL) {
808 	warnbox(_("Please add a sample script for this package"));
809 	edit_sample_callback(NULL, finfo);
810 	return 1;
811     }
812 
813     if (check_email_string(finfo->email)) {
814 	errbox(_("Please supply a valid email address"));
815 	return 1;
816     } else {
817 	set_author_mail(finfo->email);
818     }
819 
820     if (check_version_string(finfo->version)) {
821 	errbox(_("Invalid version string: use numbers and '.' only"));
822 	return 1;
823     }
824 
825     if (finfo->tags == NULL) {
826 	warnbox(_("Please select a tag (or two) for this package"));
827 	gtk_widget_grab_focus(finfo->tagsel[0]);
828 	return 1;
829     }
830 
831     if (!finfo->uses_subdir) {
832 	if (finfo->n_files > 0 || finfo->pdfdoc) {
833 	    finfo->uses_subdir = 1;
834 	}
835     } else if (finfo->n_files == 0 && !finfo->pdfdoc) {
836 	finfo->uses_subdir = 0;
837     }
838 
839     if (finfo->fname == NULL) {
840 	/* a new save */
841 	int resp = save_gfn_dialog(finfo);
842 
843 	if (resp == 0) {
844 	    /* "install" the gfn file */
845 	    err = install_gfn(finfo);
846 	}
847 	if (resp < 1) {
848 	    /* cancel or "install" */
849 	    return err;
850 	}
851     }
852 
853     if (finfo->fname == NULL) {
854 	/* note: the callback from the file selector is
855 	   save_function_package()
856 	*/
857 	file_selector_with_parent(SAVE_FUNCTIONS, FSEL_DATA_MISC,
858 				  finfo, finfo->dlg);
859     } else {
860 	err = save_function_package(finfo->fname, finfo);
861     }
862 
863     return err;
864 }
865 
finfo_destroy(GtkWidget * w,function_info * finfo)866 static void finfo_destroy (GtkWidget *w, function_info *finfo)
867 {
868     if (finfo != NULL && finfo->pkg != NULL) {
869 	function_package_set_editor(finfo->pkg, NULL);
870     }
871 
872     finfo_free(finfo);
873 }
874 
update_active_func(GtkComboBox * menu,function_info * finfo)875 static gboolean update_active_func (GtkComboBox *menu,
876 				    function_info *finfo)
877 {
878     int i = 0;
879 
880     if (menu != NULL) {
881 	i = gtk_combo_box_get_active(menu);
882 	if (i < 0) {
883 	    i = 0;
884 	}
885     }
886 
887     if (i < finfo->n_pub) {
888 	finfo->active = finfo->pubnames[i];
889     } else {
890 	finfo->active = finfo->privnames[i-finfo->n_pub];
891     }
892 
893     return FALSE;
894 }
895 
896 /* Given a line "function ..." get the function name, with
897    some error checking.  The @s we are given here is at an
898    offset of 9 bytes into the line, skipping "function ".
899 */
900 
extract_funcname(const char * s,const char * origname)901 static int extract_funcname (const char *s, const char *origname)
902 {
903     char newname[FN_NAMELEN];
904     char word[FN_NAMELEN];
905     int n, type, err = 0;
906 
907     s += strspn(s, " ");
908     n = strcspn(s, " (");
909 
910     if (n == 0 || n > FN_NAMELEN - 1) {
911 	return E_DATA;
912     }
913 
914     *newname = *word = '\0';
915     strncat(word, s, n);
916 
917     if (!strcmp(word, "void")) {
918 	type = GRETL_TYPE_VOID;
919     } else {
920 	type = gretl_type_from_string(word);
921     }
922 
923     if (!ok_function_return_type(type)) {
924 	gretl_errmsg_sprintf("%s: bad or missing return type", origname);
925 	err = E_DATA;
926     } else {
927 	s += n;
928 	s += strspn(s, " ");
929 	n = strcspn(s, " (");
930 	if (n == 0 || n > FN_NAMELEN - 1) {
931 	    err = E_DATA;
932 	} else {
933 	    strncat(newname, s, n);
934 	    if (strcmp(newname, origname)) {
935 		gretl_errmsg_set(_("You can't change the name of a function here"));
936 		err = E_DATA;
937 	    }
938 	}
939     }
940 
941     return err;
942 }
943 
pretest_funcname(char * buf,const char * origname)944 static int pretest_funcname (char *buf, const char *origname)
945 {
946     char *s, line[MAXLINE];
947     int err = 0;
948 
949     bufgets_init(buf);
950 
951     while (bufgets(line, sizeof line, buf) && !err) {
952 	s = line + strspn(line, " \t");
953 	if (!strncmp(s, "function ", 9)) {
954 	    err = extract_funcname(s + 9, origname);
955 	    break;
956 	}
957     }
958 
959     bufgets_finalize(buf);
960 
961     return err;
962 }
963 
964 /* callback used when editing a function in the context of the package
965    editor: save window-content to file and pass this to gretl_func to
966    revise the function definition.
967 */
968 
update_func_code(windata_t * vwin)969 int update_func_code (windata_t *vwin)
970 {
971     gchar *text = textview_get_text(vwin->text);
972     function_info *finfo = vwin->data;
973     const char *funname;
974     int err;
975 
976     funname = funname_from_filename(vwin->fname);
977     err = pretest_funcname(text, funname);
978 
979     if (!err) {
980 	int save_batch = gretl_in_batch_mode();
981 
982 	set_current_function_package(finfo->pkg);
983 	err = execute_script(NULL, text, NULL, INCLUDE_EXEC, NULL);
984 	set_current_function_package(NULL);
985 	gretl_set_batch_mode(save_batch);
986     }
987 
988     g_free(text);
989 
990     if (err) {
991 	gui_errmsg(err);
992     } else {
993 	mark_vwin_content_saved(vwin);
994 	finfo_set_modified(finfo, TRUE);
995     }
996 
997     return err;
998 }
999 
finfo_remove_codewin(GtkWidget * w,function_info * finfo)1000 static void finfo_remove_codewin (GtkWidget *w, function_info *finfo)
1001 {
1002     gpointer p = g_object_get_data(G_OBJECT(w), "vwin");
1003 
1004     finfo->codewins = g_list_remove(finfo->codewins, p);
1005 }
1006 
funcname_limit(gunichar c,gpointer p)1007 static gboolean funcname_limit (gunichar c, gpointer p)
1008 {
1009     return (!isalpha(c) && c != '_');
1010 }
1011 
catch_codewin_key(GtkWidget * w,GdkEventKey * event,function_info * finfo)1012 static gint catch_codewin_key (GtkWidget *w, GdkEventKey *event,
1013 			       function_info *finfo)
1014 {
1015     if (finfo->n_pub + finfo->n_priv < 2) {
1016 	/* we don't have multiple functions */
1017 	return FALSE;
1018     }
1019 
1020     /* implement Alt-dot in a given function editing window to
1021        traverse to another window editing a different function
1022     */
1023 
1024     if ((event->state & GDK_MOD1_MASK) && event->keyval == GDK_period) {
1025 	/* Alt + dot */
1026 	windata_t *vwin = g_object_get_data(G_OBJECT(w), "vwin");
1027 	GtkTextView *view = GTK_TEXT_VIEW(vwin->text);
1028 	GtkTextBuffer *buf = gtk_text_view_get_buffer(view);
1029 	GtkTextMark *mark = gtk_text_buffer_get_insert(buf);
1030 	GtkTextIter iter, istart, iend;
1031 	gchar *word = NULL;
1032 
1033 	gtk_text_buffer_get_iter_at_mark(buf, &iter, mark);
1034 	istart = iend = iter;
1035 	if (gtk_text_iter_backward_find_char(&istart, funcname_limit, NULL, NULL) &&
1036 	    gtk_text_iter_forward_char(&istart) &&
1037 	    gtk_text_iter_forward_find_char(&iend, funcname_limit, NULL, NULL)) {
1038 	    word = gtk_text_buffer_get_text(buf, &istart, &iend, FALSE);
1039 	}
1040 
1041 	if (word != NULL) {
1042 	    /* we got a "word": is it the name of a function in
1043 	       this package? */
1044 	    char *active = NULL;
1045 	    int i;
1046 
1047 	    for (i=0; i<finfo->n_pub && !active; i++) {
1048 		if (!strcmp(word, finfo->pubnames[i])) {
1049 		    active = finfo->pubnames[i];
1050 		}
1051 	    }
1052 	    for (i=0; i<finfo->n_priv && !active; i++) {
1053 		if (!strcmp(word, finfo->privnames[i])) {
1054 		    active = finfo->privnames[i];
1055 		}
1056 	    }
1057 	    if (active != NULL) {
1058 		finfo->active = active;
1059 		edit_code_callback(NULL, finfo);
1060 	    }
1061 	    g_free(word);
1062 	}
1063 
1064 	return TRUE;
1065     }
1066 
1067     return FALSE;
1068 }
1069 
finfo_add_codewin(function_info * finfo,windata_t * vwin)1070 static void finfo_add_codewin (function_info *finfo, windata_t *vwin)
1071 {
1072     finfo->codewins = g_list_append(finfo->codewins, vwin);
1073 
1074     g_object_set_data(G_OBJECT(vwin->main), "vwin", vwin);
1075     g_signal_connect(G_OBJECT(vwin->main), "key-press-event",
1076 		     G_CALLBACK(catch_codewin_key), finfo);
1077     g_signal_connect(G_OBJECT(vwin->main), "destroy",
1078 		     G_CALLBACK(finfo_remove_codewin),
1079 		     finfo);
1080 }
1081 
get_codewin_by_filename(const char * fname,function_info * finfo)1082 static windata_t *get_codewin_by_filename (const char *fname,
1083 					   function_info *finfo)
1084 {
1085     GList *list = finfo->codewins;
1086     windata_t *vwin;
1087 
1088     while (list) {
1089 	vwin = list->data;
1090 	if (vwin != NULL && !strcmp(fname, vwin->fname)) {
1091 	    return vwin;
1092 	}
1093 	list = g_list_next(list);
1094     }
1095 
1096     return NULL;
1097 }
1098 
1099 /* editing a public interface or private function belonging
1100    to a package: callback from "Edit function code" button.
1101 */
1102 
edit_code_callback(GtkWidget * w,function_info * finfo)1103 static void edit_code_callback (GtkWidget *w, function_info *finfo)
1104 {
1105     char *funname = finfo->active;
1106     char fname[FILENAME_MAX];
1107     ufunc *fun;
1108     windata_t *vwin;
1109     PRN *prn = NULL;
1110 
1111     if (funname == NULL) {
1112 	return;
1113     }
1114 
1115     filename_from_funname(fname, funname);
1116 
1117     vwin = get_codewin_by_filename(fname, finfo);
1118     if (vwin != NULL) {
1119 	gtk_window_present(GTK_WINDOW(vwin->main));
1120 	return;
1121     }
1122 
1123     fun = get_function_from_package(funname, finfo->pkg);
1124     if (fun == NULL) {
1125 	/* the package may not be saved yet */
1126 	fun = get_user_function_by_name(funname);
1127 	if (fun == NULL) {
1128 	    errbox_printf(_("Can't find the function '%s'"), funname);
1129 	}
1130     }
1131 
1132     if (bufopen(&prn)) {
1133 	return;
1134     }
1135 
1136     gretl_function_print_code(fun, tabwidth, prn);
1137 
1138     vwin = view_buffer(prn, SCRIPT_WIDTH, SCRIPT_HEIGHT,
1139 		       finfo->active, EDIT_PKG_CODE, finfo);
1140 
1141     if (vwin != NULL) {
1142 	strcpy(vwin->fname, fname);
1143 	finfo_add_codewin(finfo, vwin);
1144 	set_window_delete_filename(vwin);
1145     }
1146 }
1147 
1148 /* used by callback from Exec in sample script editor window */
1149 
package_sample_get_script(windata_t * vwin)1150 gchar *package_sample_get_script (windata_t *vwin)
1151 {
1152     function_info *finfo = vwin->data;
1153     gchar *buf = textview_get_text(vwin->text);
1154     const char *pkgname;
1155     gchar *ret;
1156     char *p, line[MAXLINE];
1157     gsize retsize;
1158     int n, done = 0;
1159 
1160     if (buf == NULL || *buf == '\0' || finfo->pkg == NULL) {
1161 	return buf;
1162     }
1163 
1164     pkgname = function_package_get_name(finfo->pkg);
1165 
1166     /* allow for adding "# ", and possibly appending a newline */
1167     retsize = strlen(buf) + 5;
1168     ret = g_malloc(retsize);
1169     *ret = '\0';
1170 
1171     /* We need to comment out "include <self>.gfn" if such a line is
1172        included in the sample script: the package is already in memory
1173        and re-loading it now may be disruptive.
1174     */
1175 
1176     bufgets_init(buf);
1177 
1178     while (bufgets(line, sizeof line, buf)) {
1179 	if (!done) {
1180 	    p = line + strspn(line, " ");
1181 	    if (!strncmp(p, "include ", 8)) {
1182 		p += 8;
1183 		p += strspn(p, " ");
1184 		n = gretl_namechar_spn(p);
1185 		if (!strncmp(p, pkgname, n)) {
1186 		    g_strlcat(ret, "# ", retsize);
1187 		    done = 1;
1188 		}
1189 	    }
1190 	}
1191 	g_strlcat(ret, line, retsize);
1192     }
1193 
1194     bufgets_finalize(buf);
1195     g_free(buf);
1196 
1197     n = strlen(ret);
1198     if (ret[n-1] != '\n') {
1199 	g_strlcat(ret, "\n", retsize);
1200     }
1201 
1202     return ret;
1203 }
1204 
1205 /* callback from Save in sample script editor window */
1206 
update_sample_script(windata_t * vwin)1207 void update_sample_script (windata_t *vwin)
1208 {
1209     function_info *finfo;
1210 
1211     finfo = g_object_get_data(G_OBJECT(vwin->main), "finfo");
1212 
1213     if (finfo != NULL) {
1214 	gchar *text = textview_get_text(vwin->text);
1215 
1216 	free(finfo->sample);
1217 	if (text == NULL || string_is_blank(text)) {
1218 	    finfo->sample = NULL;
1219 	} else {
1220 	    finfo->sample = gretl_strdup(text);
1221 	}
1222 	g_free(text);
1223 	mark_vwin_content_saved(vwin);
1224 	finfo_set_modified(finfo, TRUE);
1225     }
1226 }
1227 
1228 /* callback from Save in help text editor window */
1229 
update_gfn_help_text(windata_t * vwin)1230 void update_gfn_help_text (windata_t *vwin)
1231 {
1232     function_info *finfo;
1233 
1234     finfo = g_object_get_data(G_OBJECT(vwin->main), "finfo");
1235 
1236     if (finfo != NULL) {
1237 	gchar *text = textview_get_wrapped_text(vwin->text);
1238 
1239 	if (vwin->role == EDIT_PKG_GHLP) {
1240 	    g_free(finfo->gui_help);
1241 	    if (text == NULL || string_is_blank(text)) {
1242 		finfo->gui_help = NULL;
1243 	    } else {
1244 		finfo->gui_help = text;
1245 		text = NULL;
1246 	    }
1247 	} else {
1248 	    g_free(finfo->help);
1249 	    if (text == NULL || string_is_blank(text)) {
1250 		finfo->help = NULL;
1251 	    } else {
1252 		finfo->help = text;
1253 		text = NULL;
1254 	    }
1255 	}
1256 
1257 	g_free(text);
1258 	mark_vwin_content_saved(vwin);
1259 	finfo_set_modified(finfo, TRUE);
1260     }
1261 }
1262 
nullify_sample_window(GtkWidget * w,function_info * finfo)1263 static void nullify_sample_window (GtkWidget *w, function_info *finfo)
1264 {
1265     finfo->samplewin = NULL;
1266 }
1267 
nullify_helpwin(GtkWidget * w,function_info * finfo)1268 static void nullify_helpwin (GtkWidget *w, function_info *finfo)
1269 {
1270     finfo->helpwin = NULL;
1271 }
1272 
nullify_gui_helpwin(GtkWidget * w,function_info * finfo)1273 static void nullify_gui_helpwin (GtkWidget *w, function_info *finfo)
1274 {
1275     finfo->gui_helpwin = NULL;
1276 }
1277 
1278 /* edit the sample script for a package: callback from
1279    "Edit sample script" button in packager
1280 */
1281 
edit_sample_callback(GtkWidget * w,function_info * finfo)1282 static void edit_sample_callback (GtkWidget *w, function_info *finfo)
1283 {
1284     const char *pkgname = finfo_pkgname(finfo);
1285     gchar *title;
1286     PRN *prn = NULL;
1287 
1288     if (finfo->samplewin != NULL) {
1289 	gtk_window_present(GTK_WINDOW(finfo->samplewin->main));
1290 	return;
1291     }
1292 
1293     if (bufopen(&prn)) {
1294 	return;
1295     }
1296 
1297     title = g_strdup_printf("%s-sample", pkgname);
1298 
1299     if (finfo->sample == NULL) {
1300 	pprintf(prn, "include %s.gfn\n", pkgname);
1301     } else {
1302 	pputs(prn, finfo->sample);
1303 	pputc(prn, '\n');
1304     }
1305 
1306     finfo->samplewin = view_buffer(prn, 78, 350, title,
1307 				   EDIT_PKG_SAMPLE, finfo);
1308     if (finfo->sample == NULL) {
1309 	cursor_to_end(finfo->samplewin);
1310     }
1311 
1312     g_object_set_data(G_OBJECT(finfo->samplewin->main), "finfo",
1313 		      finfo);
1314     g_signal_connect(G_OBJECT(finfo->samplewin->main), "destroy",
1315 		     G_CALLBACK(nullify_sample_window), finfo);
1316 
1317     g_free(title);
1318 }
1319 
1320 /* Callback to launch dialog for adding or removing functions.
1321    We need to be careful here: if the package's "extra
1322    properties" dialog is open, its content is liable to be
1323    out-dated by changes in the public and/or private
1324    function lists. Since it would be very complicated and
1325    error-prone to adjust this content on the fly, we'll
1326    insist that the user closes the extra props dialog
1327    first.
1328 */
1329 
add_remove_callback(GtkWidget * w,function_info * finfo)1330 static void add_remove_callback (GtkWidget *w, function_info *finfo)
1331 {
1332     if (finfo->extra != NULL) {
1333 	const char *msg = N_("Before adding or removing functions, please close\n"
1334 			     "the \"extra properties\" dialog (after applying any\n"
1335 			     "changes you wish to keep).");
1336 
1337 	gtk_window_present(GTK_WINDOW(finfo->extra));
1338 	msgbox(_(msg), GTK_MESSAGE_INFO, finfo->extra);
1339 	return;
1340     }
1341 
1342     add_remove_functions_dialog(finfo->pubnames, finfo->n_pub,
1343 				finfo->privnames, finfo->n_priv,
1344 				finfo->pkg, finfo);
1345 }
1346 
gfn_to_script_callback(function_info * finfo)1347 static void gfn_to_script_callback (function_info *finfo)
1348 {
1349     gint resp;
1350 
1351     if (finfo->n_pub + finfo->n_priv == 0) {
1352 	warnbox("No code to save");
1353 	return;
1354     }
1355 
1356     if (finfo->sample != NULL) {
1357 	resp = yes_no_cancel_dialog("gretl",
1358 				    _("Saving packaged functions as script:\n"
1359 				      "include the sample script?"),
1360 				    finfo->dlg);
1361 
1362 	if (canceled(resp)) {
1363 	    return;
1364 	}
1365 
1366 	if (resp == GRETL_YES) {
1367 	    finfo->save_flags |= APPEND_SAMPLE;
1368 	} else {
1369 	    finfo->save_flags &= ~APPEND_SAMPLE;
1370 	}
1371     }
1372 
1373     file_selector_with_parent(SAVE_FUNCTIONS_AS, FSEL_DATA_MISC,
1374 			      finfo, finfo->dlg);
1375 }
1376 
1377 struct spec_info {
1378     GtkWidget *dialog;
1379     GtkWidget *checks[3];
1380     GtkWidget *entries[3];
1381     int *flags;
1382     function_info *finfo;
1383     int retval;
1384 };
1385 
reset_finfo_filename(function_info * finfo,int i,gchar * src)1386 static void reset_finfo_filename (function_info *finfo, int i, gchar *src)
1387 {
1388     if (i == 0) {
1389 	g_free(finfo->sample_fname);
1390 	finfo->sample_fname = src;
1391     } else if (i == 1) {
1392 	g_free(finfo->help_fname);
1393 	finfo->help_fname = src;
1394     } else {
1395 	g_free(finfo->gui_help_fname);
1396 	finfo->gui_help_fname = src;
1397     }
1398 }
1399 
spec_save_ok(GtkWidget * button,gpointer data)1400 static void spec_save_ok (GtkWidget *button, gpointer data)
1401 {
1402     struct spec_info *sinfo = data;
1403     function_info *finfo = sinfo->finfo;
1404     gchar *fname;
1405     int i, flag;
1406 
1407     for (i=0; i<3; i++) {
1408 	if (sinfo->checks[i] != NULL) {
1409 	    flag = sinfo->flags[i];
1410 	    finfo->save_flags &= ~flag;
1411 	    if (button_is_active(sinfo->checks[i])) {
1412 		fname = entry_box_get_trimmed_text(sinfo->entries[i]);
1413 		if (fname != NULL) {
1414 		    finfo->save_flags |= flag;
1415 		    reset_finfo_filename(finfo, i, fname);
1416 		}
1417 	    }
1418 	}
1419     }
1420 
1421     sinfo->retval = 0;
1422     gtk_widget_destroy(sinfo->dialog);
1423 }
1424 
get_pkg_text_filename(function_info * finfo,const char * pkgname,const char ** ids,int i)1425 static gchar *get_pkg_text_filename (function_info *finfo,
1426 				     const char *pkgname,
1427 				     const char **ids,
1428 				     int i)
1429 {
1430     const char *s;
1431     gchar *fname = NULL;
1432 
1433     s = function_package_get_string(finfo->pkg, ids[i]);
1434 
1435     if (s != NULL) {
1436 	fname = g_strdup(s);
1437     } else if (i == 0) {
1438 	fname = g_strdup_printf("%s_sample.inp", pkgname);
1439     } else if (i == 1) {
1440 	fname = g_strdup_printf("%s_help.txt", pkgname);
1441     } else {
1442 	fname = g_strdup_printf("%s_gui_help.txt", pkgname);
1443     }
1444 
1445     return fname;
1446 }
1447 
sensitize_auxname_entry(GtkToggleButton * button,GtkWidget * w)1448 static void sensitize_auxname_entry (GtkToggleButton *button,
1449 				     GtkWidget *w)
1450 {
1451     gboolean s = gtk_toggle_button_get_active(button);
1452 
1453     gtk_widget_set_sensitive(w, s);
1454 }
1455 
nullify_spec_dialog(GtkWidget * w,function_info * finfo)1456 static void nullify_spec_dialog (GtkWidget *w, function_info *finfo)
1457 {
1458     finfo->specdlg = NULL;
1459 }
1460 
gfn_spec_save_dialog(function_info * finfo,const char ** texts)1461 static int gfn_spec_save_dialog (function_info *finfo,
1462 				 const char **texts)
1463 {
1464     const gchar *msgs[] = {
1465 	N_("Save sample script as"),
1466 	N_("Save help text as"),
1467 	N_("Save GUI help as")
1468     };
1469     const char *ids[] = {
1470 	"sample-fname",
1471 	"help-fname",
1472 	"gui-help-fname"
1473     };
1474     int flags[] = {
1475 	WRITE_SAMPFILE,
1476 	WRITE_HELPFILE,
1477 	WRITE_GUI_HELP
1478     };
1479     struct spec_info sinfo;
1480     GtkWidget *dialog, *entry;
1481     GtkWidget *vbox, *hbox, *w;
1482     GtkWidget *table;
1483     const char *pkgname;
1484     gchar *tmp;
1485     int i, j, n = 0;
1486 
1487     sinfo.retval = GRETL_CANCEL;
1488     sinfo.finfo = finfo;
1489     sinfo.flags = flags;
1490 
1491     finfo->specdlg = sinfo.dialog = dialog =
1492 	gretl_dialog_new(NULL, finfo->dlg, GRETL_DLG_BLOCK);
1493     g_signal_connect(G_OBJECT(dialog), "destroy",
1494 		     G_CALLBACK(nullify_spec_dialog), finfo);
1495 
1496     vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1497 
1498     pkgname = finfo_pkgname(finfo);
1499 
1500     hbox = gtk_hbox_new(FALSE, 5);
1501     gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
1502     tmp = g_strdup_printf(_("Saving %s.spec: also save ancillary file(s)?"),
1503 			  pkgname);
1504     w = gtk_label_new(tmp);
1505     gtk_box_pack_start(GTK_BOX(hbox), w, TRUE, TRUE, 5);
1506 
1507     for (i=0; i<3; i++) {
1508 	n += (texts[i] != NULL);
1509     }
1510 
1511     table = gtk_table_new(n, 2, FALSE);
1512     gtk_table_set_row_spacings(GTK_TABLE(table), 5);
1513     gtk_table_set_col_spacings(GTK_TABLE(table), 5);
1514     gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 5);
1515 
1516     j = 0;
1517     for (i=0; i<3; i++) {
1518 	if (texts[i] == NULL) {
1519 	    sinfo.checks[i] = sinfo.entries[i] = NULL;
1520 	    continue;
1521 	}
1522 	tmp = get_pkg_text_filename(finfo, pkgname, ids, i);
1523 	w = sinfo.checks[i] = gtk_check_button_new_with_label(_(msgs[i]));
1524 	if (finfo->save_flags & flags[i]) {
1525 	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), TRUE);
1526 	}
1527 	gtk_table_attach_defaults(GTK_TABLE(table), w, 0, 1, j, j+1);
1528 	entry = sinfo.entries[i] = gtk_entry_new();
1529 	gtk_entry_set_max_length(GTK_ENTRY(entry), 64);
1530 	gtk_entry_set_width_chars(GTK_ENTRY(entry), 28);
1531 	gtk_entry_set_text(GTK_ENTRY(entry), tmp);
1532 	g_signal_connect(G_OBJECT(w), "toggled",
1533 			 G_CALLBACK(sensitize_auxname_entry),
1534 			 entry);
1535 	gtk_table_attach_defaults(GTK_TABLE(table), entry, 1, 2, j, j+1);
1536 	g_free(tmp);
1537 	j++;
1538     }
1539 
1540     hbox = gtk_dialog_get_action_area(GTK_DIALOG(dialog));
1541 
1542     cancel_delete_button(hbox, dialog);
1543     w = ok_button(hbox);
1544     g_signal_connect(G_OBJECT(w), "clicked",
1545 		     G_CALLBACK(spec_save_ok), &sinfo);
1546     gtk_widget_grab_default(w);
1547 
1548     gtk_widget_show_all(dialog);
1549 
1550     return sinfo.retval;
1551 }
1552 
1553 /* callback from file selector on saving package spec or
1554    zip file: the default location should match that of the
1555    gfn file
1556 */
1557 
get_gfn_dir(char * dirname,gpointer p)1558 void get_gfn_dir (char *dirname, gpointer p)
1559 {
1560     function_info *finfo = (function_info *) p;
1561     char *s = NULL;
1562 
1563     *dirname = '\0';
1564 
1565     if (finfo->fname != NULL) {
1566 	strcpy(dirname, finfo->fname);
1567 	s = strrslash(dirname);
1568 	if (s != NULL) {
1569 	    *s = '\0';
1570 	} else {
1571 	    *dirname = '\0';
1572 	}
1573     }
1574 }
1575 
gfn_to_spec_callback(function_info * finfo)1576 static void gfn_to_spec_callback (function_info *finfo)
1577 {
1578     const char *texts[] = {
1579 	finfo->sample,
1580 	finfo->help,
1581 	finfo->gui_help
1582     };
1583     int resp = 0;
1584 
1585     if (finfo->specdlg != NULL) {
1586 	gtk_window_present(GTK_WINDOW(finfo->specdlg));
1587 	return;
1588     }
1589 
1590     if (finfo->pkg == NULL) {
1591 	warnbox(_("Please save your package first"));
1592 	return;
1593     }
1594 
1595     if (texts[0] == NULL) {
1596 	texts[0] = function_package_get_string(finfo->pkg, "sample-script");
1597     }
1598     if (texts[1] != NULL && finfo->pdfdoc) {
1599 	texts[1] = NULL;
1600     }
1601     if (texts[2] == NULL) {
1602 	texts[2] = function_package_get_string(finfo->pkg, "gui-help");
1603     }
1604 
1605     if (texts[0] != NULL || texts[1] != NULL || texts[2] != NULL) {
1606 	resp = gfn_spec_save_dialog(finfo, texts);
1607     }
1608 
1609     if (!canceled(resp)) {
1610 	file_selector_with_parent(SAVE_GFN_SPEC, FSEL_DATA_MISC,
1611 				  finfo, finfo->dlg);
1612     }
1613 }
1614 
label_hbox(GtkWidget * w,const char * txt)1615 static GtkWidget *label_hbox (GtkWidget *w, const char *txt)
1616 {
1617     GtkWidget *hbox, *label;
1618 
1619     hbox = gtk_hbox_new(FALSE, 5);
1620     gtk_box_pack_start(GTK_BOX(w), hbox, FALSE, FALSE, 10);
1621 
1622     label = gtk_label_new(txt);
1623     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10);
1624     gtk_widget_show(label);
1625 
1626     return hbox;
1627 }
1628 
1629 enum {
1630     REGULAR_BUTTON,
1631     CHECK_BUTTON
1632 };
1633 
get_function_names(const int * list,int * err)1634 static char **get_function_names (const int *list, int *err)
1635 {
1636     char **names = NULL;
1637     const char *funname;
1638     int i, n = 0;
1639 
1640     for (i=1; i<=list[0] && !*err; i++) {
1641 	funname = user_function_name_by_index(list[i]);
1642 	if (funname == NULL) {
1643 	    *err = E_DATA;
1644 	} else {
1645 	    *err = strings_array_add(&names, &n, funname);
1646 	}
1647     }
1648 
1649     if (*err) {
1650 	strings_array_free(names, n);
1651 	names = NULL;
1652     }
1653 
1654     return names;
1655 }
1656 
finfo_reset_function_names(function_info * finfo,char ** pubnames,int npub,char ** privnames,int npriv,int * changed)1657 static int finfo_reset_function_names (function_info *finfo,
1658 				       char **pubnames, int npub,
1659 				       char **privnames, int npriv,
1660 				       int *changed)
1661 {
1662     *changed = 0;
1663 
1664     if (npub != finfo->n_pub || npriv != finfo->n_priv) {
1665 	/* we know that something has changed */
1666 	*changed = 1;
1667     } else {
1668 	/* we'll have to check the arrays for any changes */
1669 	*changed = strings_array_cmp(pubnames, finfo->pubnames, npub);
1670 	if (*changed == 0 && npriv > 0) {
1671 	    *changed = strings_array_cmp(privnames, finfo->privnames,
1672 					 npriv);
1673 	}
1674     }
1675 
1676     if (*changed == 0) {
1677 	/* trash the new function-name arrays */
1678 	strings_array_free(pubnames, npub);
1679 	strings_array_free(privnames, npriv);
1680     } else {
1681 	/* replace the old function-name arrays */
1682 	strings_array_free(finfo->pubnames, finfo->n_pub);
1683 	finfo->pubnames = pubnames;
1684 	finfo->n_pub = npub;
1685 	strings_array_free(finfo->privnames, finfo->n_priv);
1686 	finfo->privnames = privnames;
1687 	finfo->n_priv = npriv;
1688 	finfo->active = finfo->pubnames[0];
1689     }
1690 
1691     return 0;
1692 }
1693 
finfo_set_function_names(function_info * finfo,const int * publist,const int * privlist)1694 static int finfo_set_function_names (function_info *finfo,
1695 				     const int *publist,
1696 				     const int *privlist)
1697 {
1698     int npriv = (privlist == NULL)? 0 : privlist[0];
1699     int err = 0;
1700 
1701     finfo->pubnames = get_function_names(publist, &err);
1702     if (!err) {
1703 	finfo->n_pub = publist[0];
1704     }
1705 
1706     if (!err && npriv > 0) {
1707 	finfo->privnames = get_function_names(privlist, &err);
1708 	if (!err) {
1709 	    finfo->n_priv = npriv;
1710 	}
1711     }
1712 
1713     return err;
1714 }
1715 
func_selector_set_strings(function_info * finfo,GtkWidget * ifmenu)1716 static void func_selector_set_strings (function_info *finfo,
1717 				       GtkWidget *ifmenu)
1718 {
1719     int i, n = 0;
1720 
1721     for (i=0; i<finfo->n_pub; i++) {
1722 	combo_box_append_text(ifmenu, finfo->pubnames[i]);
1723 	n++;
1724     }
1725 
1726     for (i=0; i<finfo->n_priv; i++) {
1727 	gchar *s = g_strdup_printf("%s (%s)", finfo->privnames[i], _("private"));
1728 
1729 	combo_box_append_text(ifmenu, s);
1730 	g_free(s);
1731 	n++;
1732     }
1733 
1734     gtk_combo_box_set_active(GTK_COMBO_BOX(ifmenu), 0);
1735     gtk_widget_set_sensitive(ifmenu, n > 1);
1736 }
1737 
active_func_selector(function_info * finfo)1738 static GtkWidget *active_func_selector (function_info *finfo)
1739 {
1740     GtkWidget *ifmenu = gtk_combo_box_text_new();
1741 
1742     func_selector_set_strings(finfo, ifmenu);
1743     gtk_widget_show_all(ifmenu);
1744 
1745     return ifmenu;
1746 }
1747 
dreq_select(GtkComboBox * menu,function_info * finfo)1748 static void dreq_select (GtkComboBox *menu, function_info *finfo)
1749 {
1750     finfo->dreq = gtk_combo_box_get_active(menu);
1751     finfo_set_modified(finfo, TRUE);
1752 }
1753 
add_data_requirement_menu(GtkWidget * tbl,int i,function_info * finfo)1754 static void add_data_requirement_menu (GtkWidget *tbl, int i,
1755 				       function_info *finfo)
1756 {
1757     const char *datareq[] = {
1758 	N_("No special requirement"),
1759 	N_("Time-series data"),
1760 	N_("Quarterly or monthly data"),
1761 	N_("Panel data"),
1762 	N_("No dataset needed")
1763     };
1764     GtkWidget *datamenu, *tmp;
1765     int j;
1766 
1767     tmp = gtk_label_new(_("Data requirement"));
1768     gtk_misc_set_alignment(GTK_MISC(tmp), 1.0, 0.5);
1769     gtk_table_attach_defaults(GTK_TABLE(tbl), tmp, 0, 1, i, i+1);
1770     gtk_widget_show(tmp);
1771 
1772     datamenu = gtk_combo_box_text_new();
1773     for (j=0; j<=FN_NODATA_OK; j++) {
1774 	combo_box_append_text(datamenu, _(datareq[j]));
1775     }
1776     gtk_combo_box_set_active(GTK_COMBO_BOX(datamenu), finfo->dreq);
1777 
1778     tmp = gtk_hbox_new(FALSE, 0);
1779     gtk_box_pack_start(GTK_BOX(tmp), datamenu, FALSE, FALSE, 0);
1780     gtk_table_attach_defaults(GTK_TABLE(tbl), tmp, 1, 2, i, i+1);
1781     gtk_widget_show_all(tmp);
1782 
1783     g_signal_connect(G_OBJECT(datamenu), "changed",
1784 		     G_CALLBACK(dreq_select), finfo);
1785 }
1786 
pdf_toggled_callback(GtkToggleButton * button,function_info * finfo)1787 static void pdf_toggled_callback (GtkToggleButton *button,
1788 				  function_info *finfo)
1789 {
1790     int prev = finfo->pdfdoc;
1791 
1792     finfo->pdfdoc = button_is_active(button);
1793     if (finfo->pdfdoc != prev) {
1794 	finfo_set_modified(finfo, TRUE);
1795     }
1796 
1797     if (!finfo->pdfdoc) {
1798 	g_free(finfo->pdfname);
1799 	finfo->pdfname = NULL;
1800     }
1801 }
1802 
1803 /* We have the name of the PDF file that the user selected
1804    when switching to PDF help via the GUI, recorded in
1805    finfo->pdfname. Now the user is trying to build a
1806    zipfile, so we need to determine if the PDF is already
1807    in place or has to be copied from somewhere else,
1808    then do the copying if need be.
1809 
1810    "Already in place" means that the basename of the PDF is
1811    the same as the package name, and the file is either in
1812    the same directory as the gfn or in a subdirectory named
1813    "doc".
1814 */
1815 
maybe_copy_pdf_file(function_info * finfo)1816 static int maybe_copy_pdf_file (function_info *finfo)
1817 {
1818     char *p, targ[FILENAME_MAX];
1819     int copy = 1;
1820     int err = 0;
1821 
1822     switch_ext(targ, finfo->fname, "pdf");
1823 
1824     if (!strcmp(targ, finfo->pdfname)) {
1825 	copy = 0;
1826     } else if ((p = strrslash(targ)) != NULL) {
1827 	gchar *tmp = g_strdup(p + 1);
1828 
1829 	p++;
1830 	*p = '\0';
1831 	strcat(p, "doc");
1832 	strcat(p, SLASHSTR);
1833 	strcat(p, tmp);
1834 	g_free(tmp);
1835 	if (!strcmp(targ, finfo->pdfname)) {
1836 	    copy = 0;
1837 	}
1838     } else {
1839 	sprintf(targ, "doc%c%s", SLASH, finfo->fname);
1840 	switch_ext(targ, targ, "pdf");
1841 	if (!strcmp(targ, finfo->pdfname)) {
1842 	    copy = 0;
1843 	}
1844     }
1845 
1846     if (copy) {
1847 	switch_ext(targ, finfo->fname, "pdf");
1848 	err = gretl_copy_file(finfo->pdfname, targ);
1849 	if (!err) {
1850 	    /* this variable has done its work */
1851 	    g_free(finfo->pdfname);
1852 	    finfo->pdfname = NULL;
1853 	}
1854     }
1855 
1856     return err;
1857 }
1858 
pdf_press_callback(GtkWidget * button,GdkEvent * event,function_info * finfo)1859 static gboolean pdf_press_callback (GtkWidget *button,
1860 				    GdkEvent  *event,
1861 				    function_info *finfo)
1862 {
1863     int resp = GRETL_YES;
1864     gboolean ret = TRUE; /* block */
1865 
1866     if (finfo->help != NULL && strlen(finfo->help) > 128) {
1867 	/* Seems like we may have a usable plain text help
1868 	   buffer in place -- so warn the user.
1869 	*/
1870 	const gchar *msg1, *msg2, *query;
1871 	gchar *text;
1872 
1873 	msg1 = N_("Switching to PDF help means that you must supply\n"
1874 		  "a PDF file containing help text for your package.\n\n");
1875 
1876 	msg2 = N_("It also means that any existing plain text help\n"
1877 		  "will be lost when the package is saved.\n\n");
1878 
1879 	query = N_("Switch to PDF help now?");
1880 
1881 	text = g_strconcat(_(msg1), _(msg2), _(query), NULL);
1882 	resp = yes_no_dialog(NULL, text, finfo->dlg);
1883 	g_free(text);
1884     }
1885 
1886     if (resp == GRETL_YES) {
1887 	g_free(finfo->pdfname);
1888 	finfo->pdfname = NULL;
1889 	file_selector_with_parent(SELECT_PDF, FSEL_DATA_MISC,
1890 				  finfo, finfo->dlg);
1891 	if (finfo->pdfname != NULL) {
1892 	    /* otherwise the user canceled */
1893 	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
1894 	    ret = FALSE;
1895 	}
1896     }
1897 
1898     return ret;
1899 }
1900 
get_gfn_pdf_dir(char * dirname,gpointer p)1901 void get_gfn_pdf_dir (char *dirname, gpointer p)
1902 {
1903     function_info *finfo = (function_info *) p;
1904     char *s = NULL;
1905 
1906     *dirname = '\0';
1907 
1908     if (finfo->pdfname != NULL) {
1909 	strcpy(dirname, finfo->pdfname);
1910 	s = strrslash(dirname);
1911 	if (s != NULL) {
1912 	    *s = '\0';
1913 	} else {
1914 	    *dirname = '\0';
1915 	}
1916     } else if (finfo->fname != NULL) {
1917 	strcpy(dirname, finfo->fname);
1918 	s = strrslash(dirname);
1919 	if (s != NULL) {
1920 	    *s = '\0';
1921 	} else {
1922 	    *dirname = '\0';
1923 	}
1924     }
1925 }
1926 
1927 /* We get here only if the package already has PDF doc selected
1928    (otherwise the button whose callback this is is disabled).
1929    If finfo->pdfname is non-NULL that means that we haven't
1930    yet built a zipfile, so we should probably preserve that
1931    filename (or at least, directory) as the default when we
1932    open the file selector. But if finfo->pdfname is NULL we'll
1933    show the gfn directory by default. See above, get_gfn_pdf_dir.
1934 */
1935 
select_pdf_callback(GtkButton * b,function_info * finfo)1936 static void select_pdf_callback (GtkButton *b, function_info *finfo)
1937 {
1938     file_selector_with_parent(SELECT_PDF, FSEL_DATA_MISC,
1939 			      finfo, finfo->dlg);
1940 }
1941 
add_help_radios(GtkWidget * tbl,int i,function_info * finfo)1942 static void add_help_radios (GtkWidget *tbl, int i,
1943 			     function_info *finfo)
1944 {
1945     GtkWidget *w, *rb, *htab;
1946     GSList *group = NULL;
1947 
1948     w = gtk_label_new(_("Help text"));
1949     gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.5);
1950     gtk_table_attach_defaults(GTK_TABLE(tbl), w, i, i+1, 0, 1);
1951     gtk_widget_show_all(w);
1952 
1953     htab = gtk_table_new(2, 2, TRUE);
1954     gtk_table_set_row_spacings(GTK_TABLE(htab), 4);
1955     gtk_table_set_col_spacings(GTK_TABLE(htab), 2);
1956 
1957     rb = gtk_radio_button_new_with_label(group, _("Plain text"));
1958     gtk_table_attach_defaults(GTK_TABLE(htab), rb, 0, 1, 0, 1);
1959     w = gtk_button_new_from_stock(GTK_STOCK_EDIT);
1960     g_signal_connect(G_OBJECT(w), "clicked",
1961 		     G_CALLBACK(regular_help_text_callback), finfo);
1962     gtk_table_attach_defaults(GTK_TABLE(htab), w, 1, 2, 0, 1);
1963     sensitize_conditional_on(w, rb);
1964 
1965     group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rb));
1966     rb = gtk_radio_button_new_with_label(group, _("PDF file"));
1967     gtk_table_attach_defaults(GTK_TABLE(htab), rb, 0, 1, 1, 2);
1968     g_signal_connect(G_OBJECT(rb), "button-press-event",
1969 		     G_CALLBACK(pdf_press_callback), finfo);
1970     g_signal_connect(G_OBJECT(rb), "toggled",
1971 		     G_CALLBACK(pdf_toggled_callback), finfo);
1972     w = gtk_button_new_with_label(_("Select"));
1973     g_signal_connect(G_OBJECT(w), "clicked",
1974 		     G_CALLBACK(select_pdf_callback), finfo);
1975     gtk_table_attach_defaults(GTK_TABLE(htab), w, 1, 2, 1, 2);
1976     sensitize_conditional_on(w, rb);
1977 
1978     if (finfo->pdfdoc) {
1979 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), TRUE);
1980     }
1981 
1982     gtk_table_attach_defaults(GTK_TABLE(tbl), htab, i, i+1, 1, 3);
1983     gtk_widget_show_all(htab);
1984 }
1985 
1986 enum {
1987     OLD_TO_NEW,
1988     NEW_TO_OLD,
1989     FOR_DISPLAY
1990 };
1991 
translate_program_version(int v,int trans)1992 static int translate_program_version (int v, int trans)
1993 {
1994     int vtrans[17][2] = {
1995 	{10904, 20110},
1996 	{10905, 20111},
1997 	{10906, 20112},
1998 	{10907, 20113},
1999 	{10908, 20120},
2000 	{10909, 20121},
2001 	{10910, 20122},
2002 	{10911, 20123},
2003 	{10912, 20130},
2004 	{10913, 20131},
2005 	{10914, 20132},
2006 	{10990, 20140},
2007 	{10991, 20141},
2008 	{10992, 20142},
2009 	{11000, 20150},
2010 	{11001, 20151},
2011 	{11002, 20152}
2012     };
2013     int i;
2014 
2015     if (trans == OLD_TO_NEW) {
2016 	for (i=0; i<17; i++) {
2017 	    if (v == vtrans[i][0]) {
2018 		return vtrans[i][1];
2019 	    }
2020 	}
2021 	if (v < vtrans[0][0]) {
2022 	    return vtrans[0][1];
2023 	}
2024     } else {
2025 	/* new to old, or "for display" */
2026 	for (i=0; i<17; i++) {
2027 	    if (v == vtrans[i][1]) {
2028 		return vtrans[i][0];
2029 	    } else if (i < 16 && v < vtrans[i+1][1]) {
2030 		return vtrans[i][0];
2031 	    }
2032 	}
2033 	if (trans == NEW_TO_OLD && v < vtrans[0][1]) {
2034 	    return vtrans[0][0];
2035 	}
2036     }
2037 
2038     return trans == FOR_DISPLAY ? 0 : 20151;
2039 }
2040 
set_oldver_label(GtkWidget * label,int minver)2041 static void set_oldver_label (GtkWidget *label, int minver)
2042 {
2043     int oldv = translate_program_version(minver, FOR_DISPLAY);
2044 
2045     if (oldv == 0) {
2046 	gtk_label_set_text(GTK_LABEL(label), "");
2047     } else {
2048 	char vstr[12];
2049 
2050 	vstr[0] = '(';
2051 	gretl_version_string(vstr + 1, oldv);
2052 	strcat(vstr, ")");
2053 	gtk_label_set_text(GTK_LABEL(label), vstr);
2054     }
2055 }
2056 
adjust_minver(GtkSpinButton * b,function_info * finfo)2057 static void adjust_minver (GtkSpinButton *b, function_info *finfo)
2058 {
2059     GtkWidget *label;
2060 
2061     finfo->minver = gtk_spin_button_get_value_as_int(b);
2062     finfo_set_modified(finfo, TRUE);
2063 
2064     label = g_object_get_data(G_OBJECT(b), "old-label");
2065     if (label != NULL) {
2066 	set_oldver_label(label, finfo->minver);
2067     }
2068 }
2069 
letter_to_int(char c)2070 static int letter_to_int (char c)
2071 {
2072     const char *s = "abcdefghij";
2073     int i = 0;
2074 
2075     while (*s) {
2076 	if (c == *s) {
2077 	    return i;
2078 	}
2079 	s++;
2080 	i++;
2081     }
2082 
2083     return 0;
2084 }
2085 
int_to_letter(int i)2086 static char int_to_letter (int i)
2087 {
2088     const char *s = "abcdefghij";
2089 
2090     if (i >= 0 && i < 10) {
2091 	return s[i];
2092     }
2093 
2094     return 'a';
2095 }
2096 
version_input(GtkSpinButton * spin,gdouble * new_val,gpointer p)2097 static gint version_input (GtkSpinButton *spin,
2098 			   gdouble *new_val,
2099 			   gpointer p)
2100 {
2101     const gchar *s = gtk_entry_get_text(GTK_ENTRY(spin));
2102 
2103     *new_val = 10 * atoi(s) + letter_to_int(s[4]);
2104 
2105     return TRUE;
2106 }
2107 
version_output(GtkSpinButton * spin,gpointer p)2108 static gboolean version_output (GtkSpinButton *spin, gpointer p)
2109 {
2110     int n = gtk_spin_button_get_value_as_int(spin);
2111     int r = n - 10*(n/10);
2112     char buf[6] = {0};
2113 
2114     sprintf(buf, "%d", n);
2115     buf[4] = int_to_letter(r);
2116     gtk_entry_set_text(GTK_ENTRY(spin), buf);
2117 
2118     return TRUE;
2119 }
2120 
add_minver_selector(GtkWidget * tbl,int i,function_info * finfo)2121 static void add_minver_selector (GtkWidget *tbl, int i,
2122 				 function_info *finfo)
2123 {
2124     GtkWidget *label, *spin, *hbox;
2125     int minminver = 20110; /* gretl 1.9.4, new-style */
2126     int maxminver;
2127     int lwidth;
2128 
2129     /* max version requirement: the highest possible release
2130        in the build year */
2131     maxminver = 10 * atoi(GRETL_VERSION) + 9;
2132 
2133     if (finfo->minver < 20000) {
2134 	/* update an old-style "minver" value */
2135 	finfo->minver =
2136 	    translate_program_version(finfo->minver, OLD_TO_NEW);
2137     }
2138 
2139     /* fix out-of-bounds minver */
2140     if (finfo->minver < minminver) {
2141 	finfo->minver = minminver;
2142     } else if (finfo->minver > maxminver) {
2143 	finfo->minver = maxminver;
2144     }
2145 
2146     label = gtk_label_new(_("Minimum gretl version"));
2147     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
2148     gtk_table_attach_defaults(GTK_TABLE(tbl), label, i, i+1, 0, 1);
2149     gtk_widget_show(label);
2150 
2151     /* to align things below */
2152     hbox = gtk_hbox_new(FALSE, 0);
2153 
2154     /* new-style version spinner */
2155     spin = gtk_spin_button_new_with_range(minminver, maxminver, 1);
2156     gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), finfo->minver);
2157     gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), 5);
2158     gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(spin), FALSE);
2159     g_signal_connect(G_OBJECT(spin), "value-changed",
2160    		     G_CALLBACK(adjust_minver), finfo);
2161     g_signal_connect(G_OBJECT(spin), "input",
2162 		     G_CALLBACK(version_input), NULL);
2163     g_signal_connect(G_OBJECT(spin), "output",
2164 		     G_CALLBACK(version_output), NULL);
2165     gtk_entry_set_width_chars(GTK_ENTRY(spin), 5);
2166 #if GTK_MAJOR_VERSION == 3
2167     /* remedy required for gtk3 */
2168     gtk_entry_set_max_width_chars(GTK_ENTRY(spin), 5);
2169 #endif
2170     gtk_box_pack_start(GTK_BOX(hbox), spin, FALSE, FALSE, 2);
2171 
2172     /* translation to old-style version? */
2173     label = gtk_label_new(NULL);
2174     lwidth = get_string_width(" (1.9.12) ");
2175     gtk_widget_set_size_request(label, lwidth, -1);
2176     g_object_set_data(G_OBJECT(spin), "old-label", label);
2177     set_oldver_label(label, finfo->minver);
2178     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
2179 
2180     gtk_table_attach_defaults(GTK_TABLE(tbl), hbox, i, i+1, 1, 2);
2181     gtk_widget_show_all(hbox);
2182 
2183     /* placeholder to prevent the above from slipping down */
2184     label = gtk_label_new("");
2185     gtk_table_attach_defaults(GTK_TABLE(tbl), label, i, i+1, 2, 3);
2186     gtk_widget_show_all(label);
2187 }
2188 
2189 struct jel_lookup {
2190     int code;
2191     const char *label;
2192 };
2193 
2194 struct jel_lookup tag_lookups[] = {
2195     { 10, "Econometric and Statistical Methods: General" },
2196     { 11, "Bayesian Analysis: General" },
2197     { 12, "Hypothesis Testing: General" },
2198     { 13, "Estimation: General" },
2199     { 14, "Semiparametric and Nonparametric Methods" },
2200     { 15, "Statistical Simulation Methods: General" },
2201     { 20, "Single Equation Models: General" },
2202     { 21, "Cross-Sectional Models" },
2203     { 22, "Univariate Time-Series Models" },
2204     { 23, "Univariate Panel Data Models" },
2205     { 24, "Truncated, Censored and Threshold Models" },
2206     { 25, "Discrete and Qualitative Choice Models" },
2207     { 26, "Instrumental Variables (IV) Estimation" },
2208     { 30, "Multivariate Models: General" },
2209     { 31, "Multivariate Cross-sectional Models" },
2210     { 32, "Multivariate Time-Series Models" },
2211     { 33, "Multivariate Panel Data Models" },
2212     { 34, "Multivariate: Truncated and Censored" },
2213     { 35, "Multivariate: Discrete and Qualitative" },
2214     { 36, "Multivariate: IV Estimation" },
2215     { 38, "Classification Methods" },
2216     { 40, "Econometric Methods: Special Topics" },
2217     { 41, "Duration Models" },
2218     { 51, "Model Construction and Estimation" },
2219     { 52, "Model Evaluation, Validation, and Selection" },
2220     { 53, "Forecasting, Prediction and Simulation Methods" },
2221     { 54, "Quantitative Policy Modeling" },
2222     { 58, "Financial Econometrics" },
2223     { 81, "Data Access" },
2224     { 88, "Other Computer Software" },
2225     {  0, NULL }
2226 };
2227 
2228 /* As a fallback if we couldn't get the canonical listing of tags
2229    from the server, use the inline info above to construct the
2230    listing: we hope it's in sync with that on the server!
2231 */
2232 
make_local_tags_buf(void)2233 static char *make_local_tags_buf (void)
2234 {
2235     char *s = NULL;
2236     size_t len = 0;
2237     int i;
2238 
2239     for (i=0; tag_lookups[i].code > 0; i++) {
2240 	len += strlen(tag_lookups[i].label) + 8;
2241     }
2242 
2243     s = calloc(len, 1);
2244 
2245     if (s != NULL) {
2246 	char s0[6];
2247 
2248 	for (i=0; tag_lookups[i].code > 0; i++) {
2249 	    sprintf(s0, "C%02d: ", tag_lookups[i].code);
2250 	    strcat(s, s0);
2251 	    strcat(s, tag_lookups[i].label);
2252 	    strcat(s, "\n");
2253 	}
2254     }
2255 
2256     return s;
2257 }
2258 
tagsel_callback(GtkComboBox * combo,function_info * finfo)2259 static void tagsel_callback (GtkComboBox *combo,
2260 			     function_info *finfo)
2261 {
2262     char code[6], newtags[32];
2263     int t1, t2;
2264     gchar *s;
2265 
2266     if (GTK_WIDGET(combo) == finfo->tagsel[0]) {
2267 	t1 = gtk_combo_box_get_active(combo);
2268 	t2 = gtk_combo_box_get_active(GTK_COMBO_BOX(finfo->tagsel[1]));
2269     } else {
2270 	t1 = gtk_combo_box_get_active(GTK_COMBO_BOX(finfo->tagsel[0]));
2271 	t2 = gtk_combo_box_get_active(combo);
2272     }
2273 
2274     /* make Tag 2 selectable only if we have a Tag 1 */
2275     gtk_widget_set_sensitive(finfo->tagsel[1], t1 > 0);
2276 
2277     if (t1 == 0 || (t2 > 0 && t2 == t1)) {
2278 	/* interdict setting the same tag twice */
2279 	gtk_combo_box_set_active(GTK_COMBO_BOX(finfo->tagsel[1]), 0);
2280     }
2281 
2282     *code = *newtags = '\0';
2283 
2284     if (t1 > 0) {
2285 	s = combo_box_get_active_text(finfo->tagsel[0]);
2286 	sscanf(s, "%4[^: ]", code);
2287 	strcat(newtags, code);
2288 	g_free(s);
2289     }
2290     if (t2 > 0) {
2291 	s = combo_box_get_active_text(finfo->tagsel[1]);
2292 	sscanf(s, "%4[^: ]", code);
2293 	if (*newtags != '\0') {
2294 	    strcat(newtags, " ");
2295 	}
2296 	strcat(newtags, code);
2297 	g_free(s);
2298     }
2299 
2300     if (*newtags == '\0') {
2301 	/* no tags selected in dialog */
2302 	if (finfo->tags != NULL) {
2303 	    g_free(finfo->tags);
2304 	    finfo->tags = NULL;
2305 	    finfo_set_modified(finfo, TRUE);
2306 	}
2307     } else if (finfo->tags == NULL || strcmp(newtags, finfo->tags)) {
2308 	/* update from non-empty @newtags */
2309 	g_free(finfo->tags);
2310 	finfo->tags = g_strdup(newtags);
2311 	finfo_set_modified(finfo, TRUE);
2312     }
2313 }
2314 
add_tag_selectors(GtkWidget * tbl,int i,function_info * finfo)2315 static void add_tag_selectors (GtkWidget *tbl, int i,
2316 			       function_info *finfo)
2317 {
2318     GtkWidget *tmp, *hbox, *combo;
2319     char line[128];
2320     char *getbuf = NULL;
2321     char **S = NULL;
2322     int n_tags = 0;
2323     int j, err;
2324 
2325     err = list_remote_function_categories(&getbuf, OPT_A);
2326 
2327     if (err || getbuf == NULL || *getbuf != 'C') {
2328 	free(getbuf);
2329 	getbuf = NULL;
2330     }
2331 
2332     if (getbuf == NULL) {
2333 	fprintf(stderr, "add_tag_selectors: couldn't get tags list from server\n");
2334 	getbuf = make_local_tags_buf();
2335 	if (getbuf == NULL) {
2336 	    return;
2337 	}
2338     }
2339 
2340     if (finfo->tags != NULL) {
2341 	S = gretl_string_split(finfo->tags, &n_tags, NULL);
2342     }
2343 
2344     bufgets_init(getbuf);
2345 
2346     for (j=0; j<2; j++) {
2347 	int active = 0;
2348 	int k = 0;
2349 
2350 	if (j == 0) {
2351 	    tmp = gtk_label_new(_("Tag"));
2352 	} else {
2353 	    tmp = gtk_label_new(_("Tag 2 (optional)"));
2354 	}
2355 	gtk_misc_set_alignment(GTK_MISC(tmp), 1.0, 0.5);
2356 	gtk_table_attach_defaults(GTK_TABLE(tbl), tmp, 0, 1, i, i+1);
2357 	gtk_widget_show(tmp);
2358 
2359 	finfo->tagsel[j] = combo = gtk_combo_box_text_new();
2360 	combo_box_append_text(combo, _("none"));
2361 	while (bufgets(line, sizeof line, getbuf)) {
2362 	    k++;
2363 	    combo_box_append_text(combo, tailstrip(line));
2364 	    if (n_tags > j && !strncmp(line, S[j], strlen(S[j]))) {
2365 		/* this code is pre-selected */
2366 		active = k;
2367 	    }
2368 	}
2369 	gtk_combo_box_set_active(GTK_COMBO_BOX(combo), active);
2370 	g_signal_connect(G_OBJECT(combo), "changed",
2371 			 G_CALLBACK(tagsel_callback), finfo);
2372 	if (j > 0 && n_tags == 0) {
2373 	    gtk_widget_set_sensitive(combo, FALSE);
2374 	}
2375 
2376 	hbox = gtk_hbox_new(FALSE, 0);
2377 	gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 2);
2378 	gtk_table_attach_defaults(GTK_TABLE(tbl), hbox, 1, 2, i, i+1);
2379 	gtk_widget_show_all(hbox);
2380 
2381 	if (j == 0) {
2382 	    buf_rewind(getbuf);
2383 	    i++;
2384 	}
2385     }
2386 
2387     bufgets_finalize(getbuf);
2388 
2389     if (S != NULL) {
2390 	strings_array_free(S, n_tags);
2391     }
2392 }
2393 
pkg_changed(gpointer p,function_info * finfo)2394 static void pkg_changed (gpointer p, function_info *finfo)
2395 {
2396     finfo_set_modified(finfo, TRUE);
2397 }
2398 
get_user_string(void)2399 static const gchar *get_user_string (void)
2400 {
2401     const gchar *name;
2402 
2403     name = g_get_real_name();
2404     if (name == NULL) {
2405 	name = g_get_user_name();
2406     }
2407 
2408     return name;
2409 }
2410 
insert_today(GtkWidget * w,GtkWidget * entry)2411 static void insert_today (GtkWidget *w, GtkWidget *entry)
2412 {
2413     gtk_entry_set_text(GTK_ENTRY(entry), print_today());
2414 }
2415 
today_popup(GtkWidget * entry,GdkEventButton * event,GtkWidget ** popup)2416 static gint today_popup (GtkWidget *entry, GdkEventButton *event,
2417 			 GtkWidget **popup)
2418 {
2419     if (*popup == NULL) {
2420 	GtkWidget *menu = gtk_menu_new();
2421 	GtkWidget *item;
2422 
2423 	item = gtk_menu_item_new_with_label(_("Insert today's date"));
2424 	g_signal_connect(G_OBJECT(item), "activate",
2425 			 G_CALLBACK(insert_today), entry);
2426 	gtk_widget_show(item);
2427 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
2428 	*popup = menu;
2429     }
2430 
2431     gtk_menu_popup(GTK_MENU(*popup), NULL, NULL, NULL, NULL,
2432 		   event->button, event->time);
2433 
2434     return TRUE;
2435 }
2436 
query_save_package(GtkWidget * w,GdkEvent * event,function_info * finfo)2437 static gint query_save_package (GtkWidget *w, GdkEvent *event,
2438 				function_info *finfo)
2439 {
2440     if (finfo->modified) {
2441 	int resp =
2442 	    yes_no_cancel_dialog("gretl", _("Save changes?"), w);
2443 
2444 	if (resp == GRETL_CANCEL) {
2445 	    return TRUE;
2446 	} else if (resp == GRETL_YES) {
2447 	    return finfo_save(finfo);
2448 	}
2449     }
2450 
2451     return FALSE;
2452 }
2453 
check_pkg_callback(GtkWidget * widget,function_info * finfo)2454 static void check_pkg_callback (GtkWidget *widget, function_info *finfo)
2455 {
2456     validate_package_file(finfo->fname, 1);
2457 }
2458 
make_menu_attachment_tree(function_info * finfo,GtkTreePath ** ppath,int modelwin)2459 static GtkTreeStore *make_menu_attachment_tree (function_info *finfo,
2460 						GtkTreePath **ppath,
2461 						int modelwin)
2462 {
2463     const char *main_items =
2464 	"0 Tools\n"
2465 	"0 Data\n"
2466 	"0 View\n"
2467 	"1 GraphVars\n"
2468 	"2 MultiPlots\n"
2469 	"0 Add\n"
2470 	"0 Sample\n"
2471 	"0 Variable\n"
2472 	"1 URTests\n"
2473 	"1 Filter\n"
2474 	"0 Model\n"
2475 	"1 ivreg\n"
2476 	"1 LinearModels\n"
2477 	"1 LimdepModels\n"
2478 	"2 logit\n"
2479 	"2 probit\n"
2480 	"1 TSModels\n"
2481 	"2 AR-GLS\n"
2482 	"1 TSMulti\n"
2483 	"1 PanelModels\n"
2484 	"1 RobustModels\n";
2485     const char *model_items =
2486 	"0 Edit\n"
2487 	"0 Tests\n"
2488 	"0 Save\n"
2489 	"0 Graphs\n"
2490 	"0 Analysis\n";
2491     const char *leaders[] = {
2492 	"MAINWIN",
2493 	"MODELWIN"
2494     };
2495     const char *leader;
2496     GtkTreeStore *store;
2497     GtkTreeIter iter;
2498     GtkTreeIter parents[2];
2499     GtkTreeIter *iterp;
2500     gchar *path, *ustr;
2501     const char *s;
2502     char words[3][16];
2503     char *word;
2504     int level;
2505 
2506     store = gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
2507 
2508     leader = modelwin ? leaders[1] : leaders[0];
2509     s = modelwin ? model_items : main_items;
2510 
2511     while (*s) {
2512 	level = atoi(s);
2513 	word = words[level];
2514 	s += 2;
2515 	sscanf(s, "%s", word);
2516 	ustr = user_friendly_menu_path(word, modelwin);
2517 	if (level == 0) {
2518 	    path = g_strdup_printf("%s/%s", leader, words[0]);
2519 	    gtk_tree_store_append(store, &parents[0], NULL);
2520 	    iterp = &parents[0];
2521 	} else if (level == 1) {
2522 	    path = g_strdup_printf("%s/%s/%s", leader, words[0],
2523 				   words[1]);
2524 	    gtk_tree_store_append(store, &parents[1], &parents[0]);
2525 	    iterp = &parents[1];
2526 	} else {
2527 	    path = g_strdup_printf("%s/%s/%s/%s", leader, words[0],
2528 				   words[1], words[2]);
2529 	    gtk_tree_store_append(store, &iter, &parents[1]);
2530 	    iterp = &iter;
2531 	}
2532 	gtk_tree_store_set(store, iterp, 0, ustr, 1, path, -1);
2533 	if (finfo->menupath != NULL && !strcmp(path, finfo->menupath)) {
2534 	    /* record the path of pre-selected menu entry */
2535 	    *ppath = gtk_tree_model_get_path(GTK_TREE_MODEL(store), iterp);
2536 	}
2537 	g_free(path);
2538 	g_free(ustr);
2539 	s += strlen(word) + 1;
2540     }
2541 
2542     return store;
2543 }
2544 
add_menu_navigator(GtkWidget * holder,function_info * finfo,int modelwin)2545 static GtkWidget *add_menu_navigator (GtkWidget *holder,
2546 				      function_info *finfo,
2547 				      int modelwin)
2548 {
2549     GtkTreeStore *store;
2550     GtkWidget *view;
2551     GtkCellRenderer *renderer;
2552     GtkTreeViewColumn *column;
2553     GtkTreeSelection *select;
2554     GtkTreePath *path = NULL;
2555 
2556     store = make_menu_attachment_tree(finfo, &path, modelwin);
2557     if (store == NULL) {
2558 	return NULL;
2559     }
2560 
2561     view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2562     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
2563     g_object_set(view, "enable-tree-lines", TRUE, NULL);
2564     g_object_ref(G_OBJECT(view));
2565 
2566     renderer = gtk_cell_renderer_text_new();
2567     column = gtk_tree_view_column_new_with_attributes("",
2568 						      renderer,
2569 						      "text", 0,
2570 						      NULL);
2571     gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
2572 
2573     select = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
2574     gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
2575 
2576     if (path != NULL) {
2577 	gtk_tree_view_expand_to_path(GTK_TREE_VIEW(view), path);
2578 	gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(view), path,
2579 				     NULL, FALSE, 0, 0);
2580 	gtk_tree_selection_select_path(select, path);
2581 	gtk_tree_path_free(path);
2582     }
2583 
2584     if (finfo->treewin == NULL) {
2585 	GtkWidget *sw;
2586 
2587 	sw = finfo->treewin = gtk_scrolled_window_new(NULL, NULL);
2588 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
2589 				       GTK_POLICY_AUTOMATIC,
2590 				       GTK_POLICY_AUTOMATIC);
2591 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
2592 					    GTK_SHADOW_IN);
2593 	gtk_container_add(GTK_CONTAINER(holder), sw);
2594 	gtk_widget_set_size_request(sw, 150, 200);
2595     }
2596 
2597     if ((modelwin == 0 && finfo->menuwin != MODEL_WINDOW) ||
2598 	(modelwin == 1 && finfo->menuwin == MODEL_WINDOW)) {
2599 	gtk_container_add(GTK_CONTAINER(finfo->treewin), view);
2600 	finfo->currtree = view;
2601     } else {
2602 	finfo->alttree = view;
2603     }
2604 
2605     gtk_tree_view_columns_autosize(GTK_TREE_VIEW(view));
2606 
2607     return view;
2608 }
2609 
2610 static GtkWidget *
model_requirement_selector(GtkWidget * holder,function_info * finfo)2611 model_requirement_selector (GtkWidget *holder,
2612 			    function_info *finfo)
2613 {
2614     GtkWidget *hbox, *label;
2615     GtkWidget *combo;
2616     GretlCmdIndex ci;
2617     int deflt = 0;
2618     int j = 0;
2619 
2620     hbox = gtk_hbox_new(FALSE, 5);
2621     label = gtk_label_new(_("Model requirement"));
2622     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
2623 
2624     combo = gtk_combo_box_text_new();
2625     g_object_set_data(G_OBJECT(combo), "label", label);
2626     combo_box_append_text(combo, _("Any model"));
2627 
2628     for (ci=1; ci<NC; ci++) {
2629 	if (MODEL_COMMAND(ci) || EQN_SYSTEM_COMMAND(ci)) {
2630 	    j++;
2631 	    combo_box_append_text(combo, gretl_command_word(ci));
2632 	    if (finfo->mreq == ci) {
2633 		deflt = j;
2634 	    }
2635 	}
2636     }
2637 
2638     gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 5);
2639     gtk_combo_box_set_active(GTK_COMBO_BOX(combo), deflt);
2640     gtk_box_pack_start(GTK_BOX(holder), hbox, FALSE, FALSE, 5);
2641 
2642     gtk_widget_set_sensitive(label, finfo->menuwin == MODEL_WINDOW);
2643     gtk_widget_set_sensitive(combo, finfo->menuwin == MODEL_WINDOW);
2644 
2645     return combo;
2646 }
2647 
2648 static GtkWidget *
access_request_button(GtkWidget * holder,function_info * finfo)2649 access_request_button (GtkWidget *holder,
2650 		       function_info *finfo)
2651 {
2652     GtkWidget *hbox, *button;
2653 
2654     hbox = gtk_hbox_new(FALSE, 5);
2655     button = gtk_check_button_new_with_label(_("request access to out-of-sample data"));
2656     gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
2657     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), finfo->data_access);
2658     // g_signal_connect(button, "toggled", access_button_callback, finfo);
2659     gtk_box_pack_start(GTK_BOX(holder), hbox, FALSE, FALSE, 5);
2660 
2661     return button;
2662 }
2663 
switch_menu_view(GtkComboBox * combo,function_info * finfo)2664 static void switch_menu_view (GtkComboBox *combo,
2665 			      function_info *finfo)
2666 {
2667     int w = gtk_combo_box_get_active(combo);
2668     GtkWidget *sw = finfo->treewin;
2669 
2670     if (w == NO_WINDOW) {
2671 	if (finfo->currtree != NULL) {
2672 	    gtk_widget_set_sensitive(finfo->currtree, FALSE);
2673 	}
2674     } else if (w == MAIN_WINDOW) {
2675 	if (finfo->currtree == finfo->modeltree) {
2676 	    gtk_container_remove(GTK_CONTAINER(sw), finfo->modeltree);
2677 	    finfo->alttree = finfo->modeltree;
2678 	    gtk_widget_show(finfo->maintree);
2679 	    gtk_container_add(GTK_CONTAINER(sw), finfo->maintree);
2680 	    finfo->currtree = finfo->maintree;
2681 	}
2682 	gtk_widget_set_sensitive(finfo->currtree, TRUE);
2683     } else if (w == MODEL_WINDOW) {
2684 	if (finfo->currtree == finfo->maintree) {
2685 	    gtk_container_remove(GTK_CONTAINER(sw), finfo->maintree);
2686 	    finfo->alttree = finfo->maintree;
2687 	    gtk_widget_show(finfo->modeltree);
2688 	    gtk_container_add(GTK_CONTAINER(sw), finfo->modeltree);
2689 	    finfo->currtree = finfo->modeltree;
2690 	}
2691 	gtk_widget_set_sensitive(finfo->currtree, TRUE);
2692     }
2693 
2694     if (finfo->mreq_combo != NULL) {
2695 	GtkWidget *l = g_object_get_data(G_OBJECT(finfo->mreq_combo),
2696 					 "label");
2697 
2698 	gtk_widget_set_sensitive(finfo->mreq_combo,
2699 				 w == MODEL_WINDOW);
2700 	gtk_widget_set_sensitive(l, w == MODEL_WINDOW);
2701     }
2702 }
2703 
add_data_files_entries(GtkWidget * holder,function_info * finfo)2704 static void add_data_files_entries (GtkWidget *holder,
2705 				    function_info *finfo)
2706 {
2707     const char *msg = N_("You may add or delete names of data"
2708 			 "files to be included in the package.");
2709     GtkWidget *w, *hbox, *entry;
2710     int i;
2711 
2712     w = gtk_label_new(_(msg));
2713     gtk_label_set_line_wrap(GTK_LABEL(w), TRUE);
2714     hbox = gtk_hbox_new(FALSE, 5);
2715     gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 5);
2716     gtk_box_pack_start(GTK_BOX(holder), hbox, FALSE, FALSE, 5);
2717 
2718     for (i=0; i<N_FILE_ENTRIES; i++) {
2719 	hbox = gtk_hbox_new(FALSE, 5);
2720 	finfo->file_entries[i] = entry = gtk_entry_new();
2721 	gtk_entry_set_width_chars(GTK_ENTRY(entry), 32);
2722 	if (i < finfo->n_files) {
2723 	    gtk_entry_set_text(GTK_ENTRY(entry), finfo->datafiles[i]);
2724 	}
2725 	gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2726 	gtk_box_pack_start(GTK_BOX(holder), hbox, FALSE, FALSE, 5);
2727     }
2728 }
2729 
set_prov_check_state(GtkWidget * b,function_info * finfo)2730 static void set_prov_check_state (GtkWidget *b, function_info *finfo)
2731 {
2732     gboolean s = FALSE;
2733 
2734     if (finfo->provider != NULL && finfo->n_depends > 0) {
2735 	if (!strcmp(finfo->provider, finfo->depends[0])) {
2736 	    s = TRUE;
2737 	}
2738     } else if (finfo->n_depends == 0) {
2739 	gtk_widget_set_sensitive(b, FALSE);
2740     }
2741 
2742     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b), s);
2743 }
2744 
adjust_prov_check(GtkEditable * w,GtkWidget * b)2745 static void adjust_prov_check (GtkEditable *w, GtkWidget *b)
2746 {
2747     const gchar *s = gtk_entry_get_text(GTK_ENTRY(w));
2748 
2749     if (s == NULL || string_is_blank(s)) {
2750 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b), FALSE);
2751 	gtk_widget_set_sensitive(b, FALSE);
2752     } else {
2753 	gtk_widget_set_sensitive(b, TRUE);
2754     }
2755 }
2756 
add_dependency_entries(GtkWidget * holder,function_info * finfo)2757 static void add_dependency_entries (GtkWidget *holder,
2758 				    function_info *finfo)
2759 {
2760     const char *msg = N_("You may add or delete names of packages "
2761 			 "to be recorded as dependencies.\nLeave off the "
2762 			 ".gfn or .zip suffix.");
2763     GtkWidget *w, *hbox, *entry;
2764     int i;
2765 
2766     w = gtk_label_new(_(msg));
2767     gtk_label_set_line_wrap(GTK_LABEL(w), TRUE);
2768     hbox = gtk_hbox_new(FALSE, 5);
2769     gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 5);
2770     gtk_box_pack_start(GTK_BOX(holder), hbox, FALSE, FALSE, 5);
2771 
2772     for (i=0; i<N_DEP_ENTRIES; i++) {
2773 	hbox = gtk_hbox_new(FALSE, 5);
2774 	finfo->dep_entries[i] = entry = gtk_entry_new();
2775 	gtk_entry_set_width_chars(GTK_ENTRY(entry), 32);
2776 	if (i < finfo->n_depends) {
2777 	    gtk_entry_set_text(GTK_ENTRY(entry), finfo->depends[i]);
2778 	}
2779 	gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2780 	if (i == 0) {
2781 	    finfo->prov_check = gtk_check_button_new_with_label(_("provider?"));
2782 	    set_prov_check_state(finfo->prov_check, finfo);
2783 	    gtk_box_pack_start(GTK_BOX(hbox), finfo->prov_check, FALSE, FALSE, 5);
2784 	    g_signal_connect(G_OBJECT(entry), "changed",
2785 			     G_CALLBACK(adjust_prov_check), finfo->prov_check);
2786 	}
2787 	gtk_box_pack_start(GTK_BOX(holder), hbox, FALSE, FALSE, 5);
2788     }
2789 }
2790 
gui_help_text_callback(GtkButton * b,function_info * finfo)2791 static void gui_help_text_callback (GtkButton *b, function_info *finfo)
2792 {
2793     const char *pkgname;
2794     gchar *title;
2795     PRN *prn = NULL;
2796 
2797     if (finfo->gui_helpwin != NULL) {
2798 	gtk_window_present(GTK_WINDOW(finfo->gui_helpwin->main));
2799 	return;
2800     }
2801 
2802     if (finfo->gui_help == NULL) {
2803 	const char *msg =
2804 	    N_("This package has no GUI-specific help text at present.\n"
2805 	       "Would you like to add some?");
2806 
2807 	if (yes_no_dialog(NULL, _(msg), finfo->extra) != GRETL_YES) {
2808 	    return;
2809 	}
2810     }
2811 
2812     if (bufopen(&prn)) {
2813 	return;
2814     }
2815 
2816     pkgname = finfo_pkgname(finfo);
2817     title = g_strdup_printf("%s gui-help", pkgname);
2818 
2819     if (finfo->gui_help != NULL) {
2820 	pputs(prn, finfo->gui_help);
2821 	pputc(prn, '\n');
2822     }
2823 
2824     finfo->gui_helpwin = view_buffer(prn, HELP_WIDTH, HELP_HEIGHT, title,
2825 				     EDIT_PKG_GHLP, finfo);
2826     g_object_set_data(G_OBJECT(finfo->gui_helpwin->main), "finfo",
2827 		      finfo);
2828     g_signal_connect(G_OBJECT(finfo->gui_helpwin->main), "destroy",
2829 		     G_CALLBACK(nullify_gui_helpwin), finfo);
2830     g_free(title);
2831 }
2832 
2833 /* callback for editing plain-text package help */
2834 
regular_help_text_callback(GtkButton * b,function_info * finfo)2835 static void regular_help_text_callback (GtkButton *b, function_info *finfo)
2836 {
2837     const char *pkgname = finfo_pkgname(finfo);
2838     gchar *title;
2839     PRN *prn = NULL;
2840 
2841     if (finfo->helpwin != NULL) {
2842 	gtk_window_present(GTK_WINDOW(finfo->helpwin->main));
2843 	return;
2844     }
2845 
2846     if (bufopen(&prn)) {
2847 	return;
2848     }
2849 
2850     title = g_strdup_printf("%s help", pkgname);
2851 
2852     if (finfo->help != NULL) {
2853 	pputs(prn, finfo->help);
2854 	pputc(prn, '\n');
2855     }
2856 
2857     finfo->helpwin = view_buffer(prn, HELP_WIDTH, HELP_HEIGHT, title,
2858 				 EDIT_PKG_HELP, finfo);
2859     g_object_set_data(G_OBJECT(finfo->helpwin->main), "finfo",
2860 		      finfo);
2861     g_signal_connect(G_OBJECT(finfo->helpwin->main), "destroy",
2862 		     G_CALLBACK(nullify_helpwin), finfo);
2863     g_free(title);
2864 }
2865 
add_menu_attach_top(GtkWidget * holder,function_info * finfo)2866 static void add_menu_attach_top (GtkWidget *holder,
2867 				 function_info *finfo)
2868 {
2869     GtkWidget *w, *hbox, *entry;
2870     GtkWidget *combo;
2871 
2872     /* menu label entry */
2873     hbox = gtk_hbox_new(FALSE, 5);
2874     w = gtk_label_new(_("Label"));
2875     gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 5);
2876     entry = gtk_entry_new();
2877     gtk_entry_set_max_length(GTK_ENTRY(entry), 36);
2878     gtk_entry_set_width_chars(GTK_ENTRY(entry), 32);
2879     if (finfo->menulabel != NULL) {
2880 	gtk_entry_set_text(GTK_ENTRY(entry), finfo->menulabel);
2881     }
2882     gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
2883     g_object_set_data(G_OBJECT(finfo->extra), "label-entry", entry);
2884     gtk_box_pack_start(GTK_BOX(holder), hbox, FALSE, FALSE, 5);
2885 
2886     /* menu attachment combo */
2887     hbox = gtk_hbox_new(FALSE, 5);
2888     w = gtk_label_new(_("Window"));
2889     gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 5);
2890     combo = gtk_combo_box_text_new();
2891     combo_box_append_text(combo, _("none"));
2892     combo_box_append_text(combo, _("main window"));
2893     combo_box_append_text(combo, _("model window"));
2894     gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 5);
2895     gtk_combo_box_set_active(GTK_COMBO_BOX(combo), finfo->menuwin);
2896     g_signal_connect(G_OBJECT(combo), "changed",
2897 		     G_CALLBACK(switch_menu_view), finfo);
2898 
2899     /* gui-help button */
2900     w = gtk_button_new_with_label(_("GUI help text"));
2901     g_signal_connect(G_OBJECT(w), "clicked",
2902 		     G_CALLBACK(gui_help_text_callback), finfo);
2903     gtk_box_pack_end(GTK_BOX(hbox), w, FALSE, FALSE, 5);
2904 
2905     /* complete the packing */
2906     gtk_box_pack_start(GTK_BOX(holder), hbox, FALSE, FALSE, 5);
2907 }
2908 
get_model_req_ci(function_info * finfo)2909 static GretlCmdIndex get_model_req_ci (function_info *finfo)
2910 {
2911     GtkWidget *combo = finfo->mreq_combo;
2912     GretlCmdIndex ci = 0;
2913 
2914     if (combo != NULL && gtk_widget_is_sensitive(combo)) {
2915 	gchar *s = combo_box_get_active_text(combo);
2916 
2917 	ci = gretl_command_number(s);
2918 	g_free(s);
2919     }
2920 
2921     return ci;
2922 }
2923 
2924 /* pertaining to the "extra properties" dialog: check for
2925    any changes in relation to menu attachment
2926 */
2927 
process_menu_attachment(function_info * finfo,gboolean make_changes,int * focus_label)2928 static int process_menu_attachment (function_info *finfo,
2929 				    gboolean make_changes,
2930 				    int *focus_label)
2931 {
2932     GtkTreeSelection *selection;
2933     GtkTreeModel *model;
2934     GtkTreeIter iter;
2935     GtkWidget *view, *entry;
2936     gchar *label;
2937     int changed = 0;
2938 
2939     view = finfo->currtree;
2940 
2941     entry = g_object_get_data(G_OBJECT(finfo->extra), "label-entry");
2942     label = entry_box_get_trimmed_text(entry);
2943 
2944     if (label == NULL || *label == '\0') {
2945 	if (finfo->menulabel != NULL) {
2946 	    if (make_changes) {
2947 		g_free(finfo->menulabel);
2948 		finfo->menulabel = NULL;
2949 	    }
2950 	    changed = 1;
2951 	}
2952     } else if (finfo->menulabel == NULL || strcmp(finfo->menulabel, label)) {
2953 	if (make_changes) {
2954 	    g_free(finfo->menulabel);
2955 	    finfo->menulabel = label;
2956 	    label = NULL;
2957 	}
2958 	changed = 1;
2959     }
2960 
2961     g_free(label);
2962 
2963     if (!gtk_widget_is_sensitive(view)) {
2964 	/* no menu attachment at present */
2965 	if (finfo->menupath != NULL) {
2966 	    if (make_changes) {
2967 		g_free(finfo->menupath);
2968 		finfo->menupath = NULL;
2969 	    }
2970 	    changed = 1;
2971 	}
2972 	finfo->menuwin = NO_WINDOW;
2973 	goto finish;
2974     }
2975 
2976     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
2977 
2978     if (!gtk_tree_selection_get_selected(selection, &model, &iter)) {
2979 	if (finfo->menupath != NULL) {
2980 	    if (make_changes) {
2981 		g_free(finfo->menupath);
2982 		finfo->menupath = NULL;
2983 	    }
2984 	    changed = 1;
2985 	}
2986     } else {
2987 	gchar *newpath = NULL;
2988 
2989 	gtk_tree_model_get(model, &iter, 1, &newpath, -1);
2990 	if (finfo->menupath == NULL || strcmp(finfo->menupath, newpath)) {
2991 	    if (make_changes) {
2992 		g_free(finfo->menupath);
2993 		finfo->menupath = newpath;
2994 		newpath = NULL;
2995 	    }
2996 	    changed = 1;
2997 	}
2998 	g_free(newpath);
2999     }
3000 
3001     finfo_set_menuwin(finfo);
3002 
3003     if (finfo->menuwin == MODEL_WINDOW) {
3004 	/* model-requirement is relevant */
3005 	GretlCmdIndex ci = get_model_req_ci(finfo);
3006 
3007 	if (ci != finfo->mreq) {
3008 	    if (make_changes) {
3009 		finfo->mreq = ci;
3010 	    }
3011 	    changed = 1;
3012 	}
3013     } else {
3014 	/* model-requirement is otiose */
3015 	if (finfo->mreq > 0) {
3016 	    if (make_changes) {
3017 		finfo->mreq = 0;
3018 	    }
3019 	    changed = 1;
3020 	}
3021     }
3022 
3023     if (finfo->data_button != NULL) {
3024 	gboolean req = button_is_active(finfo->data_button);
3025 
3026 	if (req != finfo->data_access) {
3027 	    if (make_changes) {
3028 		finfo->data_access = req;
3029 	    }
3030 	    changed = 1;
3031 	}
3032     }
3033 
3034  finish:
3035 
3036     if (make_changes) {
3037 	if (finfo->menupath != NULL && *finfo->menupath != '\0' &&
3038 	    (finfo->menulabel == NULL || *finfo->menulabel == '\0')) {
3039 	    warnbox(_("To create a menu attachment, you must supply a label."));
3040 	    *focus_label = 1;
3041 	}
3042     }
3043 
3044     return changed;
3045 }
3046 
want_no_print_toggle(int role)3047 static int want_no_print_toggle (int role)
3048 {
3049     return role != UFUN_GUI_PRECHECK &&
3050 	role != UFUN_BUNDLE_PRINT;
3051 }
3052 
3053 /* pertaining to the "extra properties" dialog: check for
3054    any changes in relation to the special functions table
3055 */
3056 
process_special_functions(function_info * finfo,gboolean make_changes)3057 static int process_special_functions (function_info *finfo,
3058 				      gboolean make_changes)
3059 {
3060     GtkWidget **c_array;
3061     const char *oldfun;
3062     gchar *newfun;
3063     int n_changed = 0;
3064     int i, err = 0;
3065 
3066     c_array = g_object_get_data(G_OBJECT(finfo->extra), "combo-array");
3067 
3068     /* For each special function slot, check to see if the
3069        currently selected function differs from what was
3070        present originally, and if so update the record in
3071        @finfo->specials. The changes are not yet saved to
3072        the function package itself.
3073     */
3074 
3075     for (i=0; i<N_SPECIALS && !err; i++) {
3076 	int role = i + 1;
3077 
3078 	if (gtk_widget_is_sensitive(c_array[i])) {
3079 	    int fn_changed = 0, attr_changed = 0;
3080 	    int newnull = 0, oldnull = 0;
3081 	    unsigned char attr = 0;
3082 	    GtkWidget *cb;
3083 
3084 	    /* retrieve and check the selected name */
3085 	    newfun = combo_box_get_active_text(GTK_COMBO_BOX(c_array[i]));
3086 	    newnull = (newfun == NULL || *newfun == '\0' ||
3087 		       !strcmp(newfun, "none"));
3088 
3089 	    /* retrieve and check what was there before */
3090 	    oldfun = finfo->specials[i];
3091 	    oldnull = (oldfun == NULL || *oldfun == '\0' ||
3092 		       !strcmp(oldfun, "none"));
3093 
3094 	    if (want_no_print_toggle(role)) {
3095 		/* retrieve the no-print attribute? */
3096 		cb = g_object_get_data(G_OBJECT(c_array[i]), "np-toggle");
3097 		if (cb != NULL && button_is_active(cb)) {
3098 		    attr |= UFUN_NOPRINT;
3099 		}
3100 	    }
3101 
3102 	    if (role == UFUN_GUI_MAIN) {
3103 		/* retrieve the menu-only attribute? */
3104 		cb = g_object_get_data(G_OBJECT(c_array[i]), "mo-toggle");
3105 		if (cb != NULL && button_is_active(cb)) {
3106 		    attr |= UFUN_MENU_ONLY;
3107 		}
3108 	    }
3109 
3110 	    if (oldnull && !newnull) {
3111 		fn_changed = 1;
3112 	    } else if (!oldnull && newnull) {
3113 		fn_changed = 1;
3114 	    } else if (!oldnull && !newnull) {
3115 		fn_changed = strcmp(newfun, oldfun);
3116 	    }
3117 
3118 	    if (attr != finfo->gui_attrs[i]) {
3119 		attr_changed = 1;
3120 	    }
3121 
3122 	    if (make_changes) {
3123 		if (fn_changed) {
3124 		    free(finfo->specials[i]);
3125 		    finfo->specials[i] = gretl_strdup(newfun);
3126 		}
3127 		if (attr_changed) {
3128 		    finfo->gui_attrs[i] = attr;
3129 		}
3130 	    }
3131 
3132 	    if (fn_changed || attr_changed) {
3133 		n_changed++;
3134 	    }
3135 
3136 	    g_free(newfun);
3137 	}
3138     }
3139 
3140     return n_changed;
3141 }
3142 
3143 #define must_be_private(r) (r == UFUN_GUI_PRECHECK)
3144 #define must_be_public(r) (r != UFUN_GUI_PRECHECK && r != UFUN_LIST_MAKER)
3145 
3146 /* After adding or deleting functions, check that any
3147    selected "specials" are still valid: the selected
3148    funtion has not been removed from the package, nor
3149    has its public/private status been changed such as
3150    to disqualify it from playing the given role. If a
3151    selection has been invalidated, null it out.
3152 */
3153 
verify_selected_specials(function_info * finfo)3154 static void verify_selected_specials (function_info *finfo)
3155 {
3156     const char *seek;
3157     int i, j, found, role;
3158 
3159     for (i=0; i<N_SPECIALS; i++) {
3160 	role = i + 1;
3161 	if (finfo->specials[i] != NULL) {
3162 	    /* a selection was made */
3163 	    seek = finfo->specials[i];
3164 	    found = 0;
3165 	    if (!must_be_private(role)) {
3166 		/* try the public interface list */
3167 		for (j=0; j<finfo->n_pub && !found; j++) {
3168 		    if (!strcmp(seek, finfo->pubnames[j])) {
3169 			found = 1;
3170 		    }
3171 		}
3172 	    }
3173 	    if (!found && !must_be_public(role)) {
3174 		/* try the private interface list */
3175 		for (j=0; j<finfo->n_priv && !found; j++) {
3176 		    if (!strcmp(seek, finfo->privnames[j])) {
3177 			found = 1;
3178 		    }
3179 		}
3180 	    }
3181 	    if (!found) {
3182 		/* gone bad */
3183 		free(finfo->specials[i]);
3184 		finfo->specials[i] = NULL;
3185 	    }
3186 	}
3187     }
3188 }
3189 
data_file_check_existence(function_info * finfo,const char * fname)3190 static int data_file_check_existence (function_info *finfo,
3191 				      const char *fname)
3192 {
3193     char *p, test[FILENAME_MAX];
3194 
3195     strcpy(test, finfo->fname);
3196     p = strrslash(test);
3197     if (p != NULL) {
3198 	*p = '\0';
3199 	strcat(p, fname);
3200     } else {
3201 	strcpy(test, fname);
3202     }
3203 
3204     if (!gretl_file_exists(test)) {
3205 	gchar *msg;
3206 
3207 	msg = g_strdup_printf(_("Couldn't find %s"), test);
3208 	msgbox(msg, GTK_MESSAGE_WARNING, finfo->extra);
3209 	g_free(msg);
3210 	return 1;
3211     }
3212 
3213     return 0;
3214 }
3215 
3216 /* pertaining to the "extra properties" dialog: check for
3217    any changes in relation to included data files
3218 */
3219 
process_data_file_names(function_info * finfo,gboolean make_changes)3220 static int process_data_file_names (function_info *finfo,
3221 				    gboolean make_changes)
3222 {
3223     gchar *fname;
3224     int i, nf = 0;
3225     int changed = 0;
3226 
3227     for (i=0; i<N_FILE_ENTRIES; i++) {
3228 	fname = entry_box_get_trimmed_text(finfo->file_entries[i]);
3229 	if (fname != NULL) {
3230 	    nf++;
3231 	    if (i < finfo->n_files &&
3232 		strcmp(fname, finfo->datafiles[i])) {
3233 		changed = 1;
3234 	    }
3235 	}
3236 	g_free(fname);
3237     }
3238 
3239     if (!changed && nf != finfo->n_files) {
3240 	/* added or deleted */
3241 	changed = 1;
3242     }
3243 
3244     if (changed && make_changes) {
3245 	strings_array_free(finfo->datafiles, finfo->n_files);
3246 	if (nf == 0) {
3247 	    finfo->datafiles = NULL;
3248 	    finfo->n_files = 0;
3249 	} else {
3250 	    finfo->datafiles = strings_array_new(nf);
3251 	    if (finfo->datafiles != NULL) {
3252 		finfo->n_files = nf;
3253 	    }
3254 	}
3255 
3256 	if (finfo->datafiles != NULL) {
3257 	    int j = 0, err = 0;
3258 
3259 	    for (i=0; i<N_FILE_ENTRIES; i++) {
3260 		fname = entry_box_get_trimmed_text(finfo->file_entries[i]);
3261 		if (fname != NULL) {
3262 		    if (make_changes && err == 0) {
3263 			err = data_file_check_existence(finfo, fname);
3264 		    }
3265 		    finfo->datafiles[j++] = gretl_strdup(fname);
3266 		}
3267 		g_free(fname);
3268 	    }
3269 	}
3270     }
3271 
3272     return changed;
3273 }
3274 
3275 /* pertaining to the "extra properties" dialog: check for
3276    any changes in relation to dependencies
3277 */
3278 
process_dependency_names(function_info * finfo,gboolean make_changes)3279 static int process_dependency_names (function_info *finfo,
3280 				     gboolean make_changes)
3281 {
3282     gchar *dname;
3283     int i, nd = 0;
3284     int changed = 0;
3285 
3286     for (i=0; i<N_DEP_ENTRIES; i++) {
3287 	dname = entry_box_get_trimmed_text(finfo->dep_entries[i]);
3288 	if (dname != NULL) {
3289 	    nd++;
3290 	    if (i < finfo->n_depends &&
3291 		strcmp(dname, finfo->depends[i])) {
3292 		changed = 1;
3293 	    }
3294 	}
3295 	g_free(dname);
3296     }
3297 
3298     if (!changed && nd != finfo->n_depends) {
3299 	/* added or deleted */
3300 	changed = 1;
3301     }
3302 
3303     if (changed && make_changes) {
3304 	strings_array_free(finfo->depends, finfo->n_depends);
3305 	if (nd == 0) {
3306 	    finfo->depends = NULL;
3307 	    finfo->n_depends = 0;
3308 	} else {
3309 	    finfo->depends = strings_array_new(nd);
3310 	    if (finfo->depends != NULL) {
3311 		finfo->n_depends = nd;
3312 	    }
3313 	}
3314 
3315 	if (finfo->depends != NULL) {
3316 	    int j = 0;
3317 
3318 	    for (i=0; i<N_DEP_ENTRIES; i++) {
3319 		dname = entry_box_get_trimmed_text(finfo->dep_entries[i]);
3320 		if (dname != NULL) {
3321 		    finfo->depends[j++] = gretl_strdup(dname);
3322 		}
3323 		g_free(dname);
3324 	    }
3325 	}
3326     }
3327 
3328     return changed;
3329 }
3330 
process_provider_name(function_info * finfo,gboolean make_changes)3331 static int process_provider_name (function_info *finfo,
3332 				  gboolean make_changes)
3333 {
3334     gchar *sname = NULL;
3335     gboolean checked;
3336     int changed = 0;
3337 
3338     checked =
3339 	gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(finfo->prov_check));
3340 
3341     if (checked) {
3342 	sname = entry_box_get_trimmed_text(finfo->dep_entries[0]);
3343     }
3344 
3345     if (sname != NULL && *sname != '\0') {
3346 	if (finfo->provider == NULL) {
3347 	    /* no prior provider */
3348 	    changed = 1;
3349 	} else if (strcmp(sname, finfo->provider)) {
3350 	    /* different prior provider */
3351 	    changed = 1;
3352 	}
3353     } else if (finfo->provider != NULL) {
3354 	/* prior provider was removed */
3355 	changed = 1;
3356     }
3357 
3358     if (changed && make_changes) {
3359 	g_free(finfo->provider);
3360 	if (sname != NULL && *sname != '\0') {
3361 	    finfo->provider = g_strdup(sname);
3362 	} else {
3363 	    finfo->provider = NULL;
3364 	}
3365     }
3366 
3367     return changed;
3368 }
3369 
process_extra_properties(function_info * finfo,gboolean make_changes)3370 static int process_extra_properties (function_info *finfo,
3371 				     gboolean make_changes)
3372 {
3373     int focus_label = 0;
3374     int changed = 0;
3375 
3376     changed += process_special_functions(finfo, make_changes);
3377 
3378     changed += process_menu_attachment(finfo, make_changes, &focus_label);
3379     if (focus_label) {
3380 	GtkWidget *w = g_object_get_data(G_OBJECT(finfo->extra), "label-entry");
3381 
3382 	gtk_widget_grab_focus(w);
3383     }
3384 
3385     changed += process_data_file_names(finfo, make_changes);
3386     changed += process_dependency_names(finfo, make_changes);
3387     changed += process_provider_name(finfo, make_changes);
3388 
3389     if (changed && make_changes) {
3390 	finfo_set_modified(finfo, TRUE);
3391     }
3392 
3393     return changed;
3394 }
3395 
extra_properties_apply(GtkWidget * w,function_info * finfo)3396 static void extra_properties_apply (GtkWidget *w, function_info *finfo)
3397 {
3398     process_extra_properties(finfo, TRUE);
3399 }
3400 
extra_properties_close(GtkWidget * w,function_info * finfo)3401 static void extra_properties_close (GtkWidget *w, function_info *finfo)
3402 {
3403     int changed = process_extra_properties(finfo, FALSE);
3404 
3405     if (changed) {
3406 	int resp = yes_no_cancel_dialog(NULL, _("Apply changes?"),
3407 					finfo->extra);
3408 
3409 	if (resp == GRETL_CANCEL) {
3410 	    return;
3411 	} else if (resp == GRETL_YES) {
3412 	    process_extra_properties(finfo, TRUE);
3413 	}
3414     }
3415 
3416     gtk_widget_destroy(finfo->extra);
3417 }
3418 
query_save_extra_props(GtkWidget * w,GdkEvent * event,function_info * finfo)3419 static gint query_save_extra_props (GtkWidget *w, GdkEvent *event,
3420 				    function_info *finfo)
3421 {
3422     int changed = process_extra_properties(finfo, FALSE);
3423 
3424     if (changed) {
3425 	int resp = yes_no_cancel_dialog(NULL, _("Apply changes?"),
3426 					finfo->extra);
3427 
3428 	if (resp == GRETL_CANCEL) {
3429 	    return TRUE;
3430 	} else if (resp == GRETL_YES) {
3431 	    process_extra_properties(finfo, TRUE);
3432 	}
3433     }
3434 
3435     return FALSE;
3436 }
3437 
sensitize_attr_toggles(GObject * obj,gboolean s)3438 static void sensitize_attr_toggles (GObject *obj, gboolean s)
3439 {
3440     GtkWidget *cb;
3441 
3442     cb = g_object_get_data(obj, "np-toggle");
3443     if (cb != NULL) {
3444 	gtk_widget_set_sensitive(cb, s);
3445     }
3446 
3447     cb = g_object_get_data(obj, "mo-toggle");
3448     if (cb != NULL) {
3449 	gtk_widget_set_sensitive(cb, s);
3450     }
3451 }
3452 
3453 /* Prevent the user from assigning a given function to more
3454    then one special role: when a selection is changed, if
3455    the given function is already selected for a different
3456    role, deselect it in that role.
3457 */
3458 
special_changed_callback(GtkComboBox * this,function_info * finfo)3459 static void special_changed_callback (GtkComboBox *this,
3460 				      function_info *finfo)
3461 {
3462     GtkWidget **c_array;
3463     GtkWidget *other;
3464     gchar *s0, *si;
3465     int i, dup = 0;
3466 
3467     if (gtk_combo_box_get_active(this) == 0) {
3468 	/* selected "none" */
3469 	sensitize_attr_toggles(G_OBJECT(this), FALSE);
3470 	return;
3471     }
3472 
3473     sensitize_attr_toggles(G_OBJECT(this), TRUE);
3474     s0 = combo_box_get_active_text(this);
3475     c_array = g_object_get_data(G_OBJECT(finfo->extra), "combo-array");
3476 
3477     for (i=0; i<N_SPECIALS && !dup; i++) {
3478 	other = c_array[i];
3479 	if (other != GTK_WIDGET(this)) {
3480 	    si = combo_box_get_active_text(GTK_COMBO_BOX(other));
3481 	    if (!strcmp(si, s0)) {
3482 		/* switch to "none" */
3483 		gtk_combo_box_set_active(GTK_COMBO_BOX(other), 0);
3484 		sensitize_attr_toggles(G_OBJECT(other), FALSE);
3485 		dup = 1;
3486 	    }
3487 	    g_free(si);
3488 	}
3489     }
3490 
3491     g_free(s0);
3492 }
3493 
finfo_extra_help(GtkWidget * w,function_info * finfo)3494 static void finfo_extra_help (GtkWidget *w, function_info *finfo)
3495 {
3496     GtkWidget *notebook;
3497     gint page;
3498 
3499     notebook = g_object_get_data(G_OBJECT(finfo->extra), "book");
3500     page = gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook));
3501 
3502     if (page == 0) {
3503 	show_gui_help(GUI_FUNCS);
3504     } else if (page == 1) {
3505 	show_gui_help(MENU_ATTACH);
3506     } else if (page == 2) {
3507 	show_gui_help(PKG_FILES);
3508     } else {
3509 	show_gui_help(PKG_DEPS);
3510     }
3511 }
3512 
unref_trees(GtkWidget * w,function_info * finfo)3513 static void unref_trees (GtkWidget *w, function_info *finfo)
3514 {
3515     g_object_unref(G_OBJECT(finfo->maintree));
3516     g_object_unref(G_OBJECT(finfo->modeltree));
3517 }
3518 
3519 /* The following function supports three "notebook" tabs. The first
3520    allows the user to select functions in the package for the various
3521    "special" package roles (e.g. gui-main, bundle-print). The second
3522    allows for selection of a menu attachment point and GUI label. The
3523    third allows for specification of additional data to be included in
3524    the package.
3525 */
3526 
extra_properties_dialog(GtkWidget * w,function_info * finfo)3527 static void extra_properties_dialog (GtkWidget *w, function_info *finfo)
3528 {
3529     GtkWidget *dlg, *combo, *table;
3530     GtkWidget *tmp, *vbox, *hbox;
3531     GtkWidget **combo_array;
3532     GtkWidget *notebook;
3533     const char *key;
3534     const char *special;
3535     int tabcols = 4;
3536     int nfuns, i, j;
3537 
3538     if (finfo->extra != NULL) {
3539 	gtk_window_present(GTK_WINDOW(finfo->extra));
3540 	return;
3541     }
3542 
3543     if (finfo->pkg == NULL) {
3544 	warnbox(_("Please save your package first"));
3545 	return;
3546     }
3547 
3548     finfo->maintree = finfo->modeltree = NULL;
3549     finfo->currtree = finfo->alttree = NULL;
3550     finfo->treewin = NULL;
3551     finfo->mreq_combo = NULL;
3552     finfo->data_button = NULL;
3553 
3554     dlg = gretl_dialog_new(_("gretl: extra properties"), finfo->dlg,
3555 			   GRETL_DLG_BLOCK | GRETL_DLG_RESIZE);
3556     finfo->extra = dlg;
3557     g_signal_connect(G_OBJECT(dlg), "delete-event",
3558 		     G_CALLBACK(query_save_extra_props), finfo);
3559     g_signal_connect(G_OBJECT(dlg), "destroy",
3560 		     G_CALLBACK(gtk_widget_destroyed), &finfo->extra);
3561     g_signal_connect(G_OBJECT(dlg), "destroy",
3562 		     G_CALLBACK(unref_trees), finfo);
3563     vbox = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
3564 
3565     notebook = gtk_notebook_new();
3566     gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0);
3567     g_object_set_data(G_OBJECT(dlg), "book", notebook);
3568 
3569     vbox = gtk_vbox_new(FALSE, 0);
3570     gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
3571     tmp = gtk_label_new(_("Special functions"));
3572     gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, tmp);
3573 
3574     table = gtk_table_new(N_SPECIALS, tabcols, FALSE);
3575     gtk_table_set_row_spacings(GTK_TABLE(table), 5);
3576     gtk_table_set_col_spacings(GTK_TABLE(table), 5);
3577 
3578     nfuns = finfo->n_priv + finfo->n_pub;
3579     combo_array = g_malloc(N_SPECIALS * sizeof *combo_array);
3580     g_object_set_data_full(G_OBJECT(dlg), "combo-array",
3581 			   combo_array, g_free);
3582 
3583     /* For each "special" function role, test the functions
3584        in finfo->pkg to see if they qualify as candidates for
3585        that role; if so, add them to the combo selector.
3586     */
3587 
3588     for (i=0; i<N_SPECIALS; i++) {
3589 	const char *funname = NULL;
3590 	int n_cands = 0;
3591 	int selected = 0;
3592 	int role = i + 1;
3593 
3594 	key = package_role_get_key(role);
3595 	special = finfo->specials[i];
3596 	combo = gtk_combo_box_text_new();
3597 	combo_box_append_text(combo, "none");
3598 
3599 	for (j=0; j<nfuns; j++) {
3600 	    if (j < finfo->n_priv && !must_be_public(role)) {
3601 		funname = finfo->privnames[j];
3602 	    } else if (j >= finfo->n_priv && !must_be_private(role)) {
3603 		funname = finfo->pubnames[j - finfo->n_priv];
3604 	    } else {
3605 		continue;
3606 	    }
3607 	    if (function_ok_for_package_role(funname, role)) {
3608 		combo_box_append_text(combo, funname);
3609 		if (special != NULL && !selected && !strcmp(special, funname)) {
3610 		    selected = n_cands + 1;
3611 		}
3612 		n_cands++;
3613 	    }
3614 	}
3615 
3616 	tmp = gtk_label_new(key);
3617 	gtk_misc_set_alignment(GTK_MISC(tmp), 1, 0.5);
3618 	gtk_table_attach_defaults(GTK_TABLE(table), tmp, 0, 1, i, i+1);
3619 	gtk_table_attach_defaults(GTK_TABLE(table), combo, 1, 2, i, i+1);
3620 	gtk_combo_box_set_active(GTK_COMBO_BOX(combo), selected);
3621 	if (n_cands == 0) {
3622 	    gtk_widget_set_sensitive(combo, FALSE);
3623 	} else {
3624 	    g_signal_connect(G_OBJECT(combo), "changed",
3625 			     G_CALLBACK(special_changed_callback),
3626 			     finfo);
3627 	}
3628 	combo_array[i] = combo;
3629 
3630 	if (want_no_print_toggle(role)) {
3631 	    GtkWidget *cb = gtk_check_button_new_with_label("no-print");
3632 
3633 	    gtk_table_attach_defaults(GTK_TABLE(table), cb, 2, 3, i, i+1);
3634 	    g_object_set_data(G_OBJECT(combo), "np-toggle", cb);
3635 	    gtk_widget_set_sensitive(cb, selected > 0);
3636 	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb),
3637 					 finfo->gui_attrs[i] & UFUN_NOPRINT);
3638 	}
3639 
3640 	if (role == UFUN_GUI_MAIN) {
3641 	    GtkWidget *cb = gtk_check_button_new_with_label("menu-only");
3642 
3643 	    gtk_table_attach_defaults(GTK_TABLE(table), cb, 3, 4, i, i+1);
3644 	    g_object_set_data(G_OBJECT(combo), "mo-toggle", cb);
3645 	    gtk_widget_set_sensitive(cb, selected > 0);
3646 	    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb),
3647 					 finfo->gui_attrs[i] & UFUN_MENU_ONLY);
3648 	}
3649     }
3650 
3651     hbox = gtk_hbox_new(FALSE, 5);
3652     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
3653     gtk_box_pack_start(GTK_BOX(hbox), table, FALSE, FALSE, 5);
3654 
3655     /* the menu attachment page */
3656 
3657     vbox = gtk_vbox_new(FALSE, 0);
3658     gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
3659     tmp = gtk_label_new(_("Menu attachment"));
3660     gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, tmp);
3661 
3662     add_menu_attach_top(vbox, finfo);
3663     finfo->maintree = add_menu_navigator(vbox, finfo, 0);
3664     finfo->modeltree = add_menu_navigator(vbox, finfo, 1);
3665 
3666     finfo->mreq_combo = model_requirement_selector(vbox, finfo);
3667     finfo->data_button = access_request_button(vbox, finfo);
3668 
3669     gtk_widget_set_sensitive(finfo->currtree, finfo->menuwin != NO_WINDOW);
3670 
3671     /* the data files page */
3672 
3673     vbox = gtk_vbox_new(FALSE, 0);
3674     gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
3675     tmp = gtk_label_new(_("Data files"));
3676     gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, tmp);
3677     add_data_files_entries(vbox, finfo);
3678 
3679     /* the dependencies page */
3680 
3681     vbox = gtk_vbox_new(FALSE, 0);
3682     gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
3683     tmp = gtk_label_new(_("Dependencies"));
3684     gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, tmp);
3685     add_dependency_entries(vbox, finfo);
3686 
3687     /* the common buttons area */
3688 
3689     hbox = gtk_dialog_get_action_area(GTK_DIALOG(dlg));
3690 
3691     /* Apply button */
3692     tmp = apply_button(hbox);
3693     g_signal_connect(G_OBJECT(tmp), "clicked",
3694 		     G_CALLBACK(extra_properties_apply), finfo);
3695     gtk_widget_grab_default(tmp);
3696 
3697     /* Close button */
3698     tmp = close_button(hbox);
3699     g_signal_connect(G_OBJECT(tmp), "clicked",
3700                      G_CALLBACK(extra_properties_close), finfo);
3701 
3702     /* Help button */
3703     tmp = gtk_button_new_from_stock(GTK_STOCK_HELP);
3704     gtk_container_add(GTK_CONTAINER(hbox), tmp);
3705     gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(hbox),
3706 				       tmp, TRUE);
3707     g_signal_connect(G_OBJECT(tmp), "clicked",
3708 		     G_CALLBACK(finfo_extra_help),
3709 		     finfo);
3710 
3711     gtk_widget_show_all(dlg);
3712 }
3713 
package_editor_exit_check(GtkWidget * w)3714 int package_editor_exit_check (GtkWidget *w)
3715 {
3716     function_info *finfo;
3717 
3718     finfo = g_object_get_data(G_OBJECT(w), "finfo");
3719 
3720     if (finfo != NULL && finfo->modified) {
3721 	gtk_window_present(GTK_WINDOW(w));
3722 	return query_save_package(w, NULL, finfo);
3723     }
3724 
3725     return FALSE;
3726 }
3727 
3728 /* return non-zero if @w is the window of an editor working
3729    on @pkgname
3730 */
3731 
query_package_editor(GtkWidget * w,const char * pkgname)3732 int query_package_editor (GtkWidget *w, const char *pkgname)
3733 {
3734     function_info *finfo;
3735 
3736     finfo = g_object_get_data(G_OBJECT(w), "finfo");
3737 
3738     if (finfo != NULL && finfo->pkg != NULL) {
3739 	const char *myname = function_package_get_name(finfo->pkg);
3740 
3741 	return strcmp(pkgname, myname) == 0;
3742     }
3743 
3744     return FALSE;
3745 }
3746 
package_editor_get_pkg(GtkWidget * w)3747 void *package_editor_get_pkg (GtkWidget *w)
3748 {
3749     function_info *finfo;
3750 
3751     finfo = g_object_get_data(G_OBJECT(w), "finfo");
3752 
3753     if (finfo != NULL && finfo->pkg != NULL) {
3754 	return (void *) finfo->pkg;
3755     }
3756 
3757     return NULL;
3758 }
3759 
delete_dlg_callback(GtkWidget * button,function_info * finfo)3760 static void delete_dlg_callback (GtkWidget *button, function_info *finfo)
3761 {
3762     gint resp = 0;
3763 
3764     if (finfo->modified) {
3765 	resp = query_save_package(finfo->dlg, NULL, finfo);
3766     }
3767 
3768     if (!resp) {
3769 	gtk_widget_destroy(finfo->dlg);
3770     }
3771 }
3772 
3773 /* Dialog for editing a function package.  The user can get here
3774    in either of two ways: after selecting functions to put into a
3775    newly created package, or upon selecting an existing package
3776    for editing.
3777 */
3778 
finfo_dialog(function_info * finfo)3779 static void finfo_dialog (function_info *finfo)
3780 {
3781     GtkWidget *button, *label;
3782     GtkWidget *tbl, *vbox, *hbox;
3783     const char *entry_labels[] = {
3784 	N_("Author"),
3785 	N_("Email"),
3786 	N_("Version"),
3787 	N_("Date (YYYY-MM-DD)"),
3788 	N_("Package description")
3789     };
3790     char *entry_texts[] = {
3791 	finfo->author,
3792 	finfo->email,
3793 	finfo->version,
3794 	finfo->date,
3795 	finfo->pkgdesc
3796     };
3797     gchar *tmp, *title;
3798     int focused = 0;
3799     int rows = N_ENTRIES + 2;
3800     int i;
3801 
3802     finfo->dlg = gretl_gtk_window();
3803     gtk_window_set_default_size(GTK_WINDOW(finfo->dlg), 600, -1);
3804 
3805     title = g_strdup_printf("gretl: %s", finfo_pkgname(finfo));
3806     gtk_window_set_title(GTK_WINDOW(finfo->dlg), title);
3807     g_free(title);
3808 
3809     if (finfo->pkg != NULL) {
3810 	function_package_set_editor(finfo->pkg, finfo->dlg);
3811     }
3812 
3813     g_object_set_data(G_OBJECT(finfo->dlg), "finfo", finfo);
3814     gtk_widget_set_name(finfo->dlg, "pkg-editor");
3815     g_signal_connect(G_OBJECT(finfo->dlg), "delete-event",
3816 		     G_CALLBACK(query_save_package), finfo);
3817     g_signal_connect(G_OBJECT(finfo->dlg), "destroy",
3818 		     G_CALLBACK(finfo_destroy), finfo);
3819 
3820     vbox = gtk_vbox_new(FALSE, 5);
3821     gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
3822     gtk_container_add(GTK_CONTAINER(finfo->dlg), vbox);
3823 
3824     tbl = gtk_table_new(rows, 2, FALSE);
3825     gtk_table_set_col_spacings(GTK_TABLE(tbl), 5);
3826     gtk_table_set_row_spacings(GTK_TABLE(tbl), 4);
3827     gtk_box_pack_start(GTK_BOX(vbox), tbl, FALSE, FALSE, 5);
3828 
3829     for (i=0; i<N_ENTRIES; i++) {
3830 	GtkWidget *entry;
3831 
3832 	label = gtk_label_new(_(entry_labels[i]));
3833 	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
3834 	gtk_table_attach_defaults(GTK_TABLE(tbl), label, 0, 1, i, i+1);
3835 
3836 	entry = gtk_entry_new();
3837 	gtk_entry_set_width_chars(GTK_ENTRY(entry), 40);
3838 	gtk_table_attach_defaults(GTK_TABLE(tbl), entry, 1, 2, i, i+1);
3839 
3840 	finfo->entries[i] = entry;
3841 
3842 	if (entry_texts[i] != NULL) {
3843 	    gtk_entry_set_text(GTK_ENTRY(entry), entry_texts[i]);
3844 	    if (i == 3) {
3845 		g_signal_connect(G_OBJECT(entry), "button-press-event",
3846 				 G_CALLBACK(today_popup), &finfo->popup);
3847 	    }
3848 	} else if (i == 0) {
3849 	    /* author */
3850 	    const gchar *s = get_user_string();
3851 
3852 	    if (s != NULL) {
3853 		gtk_entry_set_text(GTK_ENTRY(entry), s);
3854 	    }
3855 	} else if (i == 1) {
3856 	    /* email */
3857 	    gtk_entry_set_text(GTK_ENTRY(entry), get_author_mail());
3858 	} else if (i == 2) {
3859 	    /* version */
3860 	    gtk_entry_set_text(GTK_ENTRY(entry), "1.0");
3861 	} else if (i == 3) {
3862 	    /* date */
3863 	    gtk_entry_set_text(GTK_ENTRY(entry), print_today());
3864 	}
3865 
3866 	if (i == 0 && entry_texts[i] == NULL) {
3867 	    /* no author's name */
3868 	    gtk_widget_grab_focus(entry);
3869 	    focused = 1;
3870 	} else if (i == 1 && !focused &&
3871 		   (entry_texts[i] == NULL || *entry_texts[i] == '\0')) {
3872 	    /* no email address */
3873 	    gtk_widget_grab_focus(entry);
3874 	    focused = 1;
3875 	} else if (i == 2 && !focused) {
3876 	    /* version number */
3877 	    gtk_widget_grab_focus(entry);
3878 	}
3879 
3880 	g_signal_connect(GTK_EDITABLE(entry), "changed",
3881 			 G_CALLBACK(pkg_changed), finfo);
3882     }
3883 
3884     add_tag_selectors(tbl, i, finfo);
3885     i += 2;
3886     add_data_requirement_menu(tbl, i, finfo);
3887 
3888     /* table for min version and help doc controls */
3889     hbox = gtk_hbox_new(FALSE, 0);
3890     tbl = gtk_table_new(3, 2, FALSE);
3891     gtk_table_set_row_spacings(GTK_TABLE(tbl), 4);
3892     gtk_table_set_col_spacings(GTK_TABLE(tbl), 64);
3893     gtk_box_pack_start(GTK_BOX(hbox), tbl, TRUE, FALSE, 0);
3894     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 8);
3895 
3896     add_minver_selector(tbl, 0, finfo);
3897     add_help_radios(tbl, 1, finfo);
3898 
3899     /* table for buttons arrayed at foot of window */
3900     hbox = gtk_hbox_new(FALSE, 0);
3901     tbl = gtk_table_new(2, 4, FALSE);
3902     gtk_table_set_row_spacings(GTK_TABLE(tbl), 4);
3903     gtk_table_set_col_spacings(GTK_TABLE(tbl), 4);
3904     gtk_table_set_col_spacing(GTK_TABLE(tbl), 2, 32);
3905     gtk_box_pack_start(GTK_BOX(hbox), tbl, TRUE, FALSE, 0);
3906     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 2);
3907 
3908     /* first button row */
3909 
3910     /* 1: edit code button */
3911     button = gtk_button_new_with_label(_("Edit function code"));
3912     gtk_table_attach_defaults(GTK_TABLE(tbl), button, 0, 1, 0, 1);
3913     g_signal_connect(G_OBJECT(button), "clicked",
3914 		     G_CALLBACK(edit_code_callback), finfo);
3915 
3916     /* 2: interface selector */
3917     finfo->codesel = active_func_selector(finfo);
3918     gtk_table_attach_defaults(GTK_TABLE(tbl), finfo->codesel, 1, 2, 0, 1);
3919     g_signal_connect(G_OBJECT(finfo->codesel), "changed",
3920 		     G_CALLBACK(update_active_func), finfo);
3921 
3922     update_active_func(NULL, finfo);
3923 
3924     /* 3: extra package properties button */
3925     button = gtk_button_new_with_label(_("Extra properties"));
3926     gtk_table_attach_defaults(GTK_TABLE(tbl), button, 2, 3, 0, 1);
3927     g_signal_connect(G_OBJECT(button), "clicked",
3928 		     G_CALLBACK(extra_properties_dialog), finfo);
3929 
3930     /* 4: save-menu button */
3931     tmp = g_strdup_printf(" %s ", _("Save..."));
3932     button = gtk_button_new_with_label(tmp);
3933     g_free(tmp);
3934     gtk_table_attach_defaults(GTK_TABLE(tbl), button, 3, 4, 0, 1);
3935     g_signal_connect(G_OBJECT(button), "clicked",
3936 		     G_CALLBACK(pkg_save_popup), finfo);
3937 
3938     /* second button row */
3939 
3940     /* 1: edit sample script button */
3941     button = gtk_button_new_with_label(_("Edit sample script"));
3942     gtk_table_attach_defaults(GTK_TABLE(tbl), button, 0, 1, 1, 2);
3943     g_signal_connect(G_OBJECT(button), "clicked",
3944 		     G_CALLBACK(edit_sample_callback), finfo);
3945 
3946     /* 2: add/remove functions button */
3947     button = gtk_button_new_with_label(_("Add/remove functions"));
3948     gtk_table_attach_defaults(GTK_TABLE(tbl), button, 1, 2, 1, 2);
3949     g_signal_connect(G_OBJECT(button), "clicked",
3950 		     G_CALLBACK(add_remove_callback), finfo);
3951 
3952     /* 3: validate button */
3953     button = gtk_button_new_with_label(_("Validate"));
3954     gtk_table_attach_defaults(GTK_TABLE(tbl), button, 2, 3, 1, 2);
3955     g_signal_connect(G_OBJECT(button), "clicked",
3956 		     G_CALLBACK(check_pkg_callback), finfo);
3957     gtk_widget_set_sensitive(button, finfo->fname != NULL);
3958     finfo->validate = button;
3959 
3960     /* 4: close button */
3961     button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
3962     gtk_table_attach_defaults(GTK_TABLE(tbl), button, 3, 4, 1, 2);
3963     g_signal_connect(G_OBJECT(button), "clicked",
3964 		     G_CALLBACK(delete_dlg_callback), finfo);
3965 
3966     finfo_set_modified(finfo, finfo->fname == NULL);
3967 
3968     window_list_add(finfo->dlg, SAVE_FUNCTIONS);
3969     gtk_widget_show_all(finfo->dlg);
3970 }
3971 
web_get_login(GtkWidget * w,gpointer p)3972 static void web_get_login (GtkWidget *w, gpointer p)
3973 {
3974     browser_open("http://gretl.ecn.wfu.edu/cgi-bin/apply/");
3975 }
3976 
login_dialog(login_info * linfo,GtkWidget * parent)3977 static void login_dialog (login_info *linfo, GtkWidget *parent)
3978 {
3979     const gchar *msg = N_("Upload package: This means that the package will\n"
3980 			  "be uploaded to the gretl server for approval.\n"
3981 			  "You should do this only if you are the author of\n"
3982 			  "this package and either the package is not already\n"
3983 			  "on the server or you have made changes since the\n"
3984 			  "last upload.");
3985     GtkWidget *button, *label;
3986     GtkWidget *tbl, *vbox, *hbox;
3987     int i;
3988 
3989     login_init(linfo);
3990 
3991     linfo->dlg = gretl_dialog_new(_("gretl: upload"), parent, GRETL_DLG_BLOCK);
3992     vbox = gtk_dialog_get_content_area(GTK_DIALOG(linfo->dlg));
3993 
3994     label_hbox(vbox, _(msg));
3995 
3996     hbox = gtk_hbox_new(FALSE, 5);
3997     tbl = gtk_table_new(2, 2, FALSE);
3998     gtk_box_pack_start(GTK_BOX(hbox), tbl, FALSE, FALSE, 5);
3999     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 10);
4000 
4001     for (i=0; i<2; i++) {
4002 	char *src = (i == 0)? linfo->login : linfo->pass;
4003 	GtkWidget *entry;
4004 
4005 	label = gtk_label_new((i == 0)? _("Login") : _("Password"));
4006 	gtk_table_attach(GTK_TABLE(tbl), label, 0, 1, i, i+1,
4007 			 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
4008 			 5, 5);
4009 
4010 	entry = gtk_entry_new();
4011 	gtk_entry_set_width_chars(GTK_ENTRY(entry), 34);
4012 	gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
4013 	gtk_table_attach_defaults(GTK_TABLE(tbl), entry, 1, 2, i, i+1);
4014 	if (src != NULL) {
4015 	    gtk_entry_set_text(GTK_ENTRY(entry), src);
4016 	}
4017 
4018 	if (i == 0) {
4019 	    linfo->login_entry = entry;
4020 	} else {
4021 	    gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
4022 	    linfo->pass_entry = entry;
4023 	}
4024     }
4025 
4026     label_hbox(vbox,
4027 	       _("If you don't have a login to the gretl server\n"
4028 		 "please see http://gretl.ecn.wfu.edu/cgi-bin/apply/.\n"
4029 		 "The 'Website' button below should open this page\n"
4030 		 "in your web browser."));
4031 
4032     /* control button area */
4033     hbox = gtk_dialog_get_action_area(GTK_DIALOG(linfo->dlg));
4034 
4035     /* Cancel */
4036     button = cancel_button(hbox);
4037     g_signal_connect(G_OBJECT(button), "clicked",
4038 		     G_CALLBACK(delete_widget), linfo->dlg);
4039 
4040     /* OK */
4041     button = ok_validate_button(hbox, &linfo->canceled, NULL);
4042     g_signal_connect(G_OBJECT(button), "clicked",
4043 		     G_CALLBACK(login_finalize), linfo);
4044     gtk_widget_grab_default(button);
4045 
4046     /* Website */
4047     button = gtk_button_new_with_label("Website");
4048     gtk_widget_set_can_default(button, TRUE);
4049     gtk_container_add(GTK_CONTAINER(hbox), button);
4050     gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(hbox),
4051 				       button, TRUE);
4052     g_signal_connect(G_OBJECT(button), "clicked",
4053 		     G_CALLBACK(web_get_login), NULL);
4054 
4055     gtk_widget_show_all(linfo->dlg);
4056 }
4057 
4058 /* Check the package file against gretlfunc.dtd. We call
4059    this automatically before uploading a package file to
4060    the server.  The user can also choose to run the test by
4061    clicking the "Validate" button in the package editor;
4062    in that case we set the @verbose flag.
4063 */
4064 
validate_package_file(const char * fname,int verbose)4065 static int validate_package_file (const char *fname, int verbose)
4066 {
4067     const char *gretldir = gretl_home();
4068     char dtdname[FILENAME_MAX];
4069     xmlDocPtr doc;
4070     xmlDtdPtr dtd;
4071     int err = 0;
4072 
4073     err = gretl_xml_open_doc_root(fname, NULL, &doc, NULL);
4074     if (err) {
4075 	gui_errmsg(err);
4076 	return 1;
4077     }
4078 
4079     sprintf(dtdname, "%sfunctions%cgretlfunc.dtd", gretldir, SLASH);
4080     dtd = xmlParseDTD(NULL, (const xmlChar *) dtdname);
4081 
4082     if (dtd == NULL) {
4083 	if (verbose) {
4084 	    errbox("Couldn't open DTD to check package");
4085 	} else {
4086 	    fprintf(stderr, "Couldn't open DTD to check package\n");
4087 	}
4088     } else {
4089 	const char *pkgname = path_last_element(fname);
4090 	xmlValidCtxtPtr cvp = xmlNewValidCtxt();
4091 	PRN *prn = NULL;
4092 	int xerr = 0;
4093 
4094 	if (cvp == NULL) {
4095 	    xerr = 1;
4096 	    if (verbose) nomem();
4097 	} else {
4098 	    xerr = bufopen(&prn);
4099 	}
4100 
4101 	if (xerr) {
4102 	    xmlFreeDtd(dtd);
4103 	    xmlFreeDoc(doc);
4104 	    return 0;
4105 	}
4106 
4107 	cvp->userData = (void *) prn;
4108 	cvp->error    = (xmlValidityErrorFunc) pprintf2;
4109 	cvp->warning  = (xmlValidityWarningFunc) pprintf2;
4110 
4111 	if (!xmlValidateDtd(cvp, doc, dtd)) {
4112 	    const char *buf = gretl_print_get_buffer(prn);
4113 
4114 	    errbox(buf);
4115 	    err = 1;
4116 	} else if (verbose) {
4117 	    infobox_printf(_("%s: validated against DTD OK"), pkgname);
4118 	} else {
4119 	    fprintf(stderr, "%s: validated against DTD OK\n", pkgname);
4120 	}
4121 
4122 	gretl_print_destroy(prn);
4123 	xmlFreeValidCtxt(cvp);
4124 	xmlFreeDtd(dtd);
4125     }
4126 
4127     xmlFreeDoc(doc);
4128 
4129     return err;
4130 }
4131 
4132 /* Collect pkg.gfn plus additional package files (PDF doc and/or data
4133    files) into a temporary dir under the user's dotdir, and make a zip
4134    archive. If @dest is non-NULL, that's the name of the zipfile to
4135    build; otherwise if @pzipname is non-NULL the zipfile will be named
4136    automatically based on the package name, and this name will be
4137    "returned" in @pzipname.
4138 */
4139 
gui_pkg_make_zipfile(function_info * finfo,gchar ** pzipname,const char * dest)4140 static int gui_pkg_make_zipfile (function_info *finfo,
4141 				 gchar **pzipname,
4142 				 const char *dest)
4143 {
4144     windata_t *vwin;
4145     PRN *prn = NULL;
4146     int err = 0;
4147 
4148     if (pzipname == NULL && dest == NULL) {
4149 	/* we need one or the other */
4150 	return E_DATA;
4151     }
4152 
4153     if (finfo->pdfname != NULL) {
4154 	err = maybe_copy_pdf_file(finfo);
4155 	if (err) {
4156 	    return err;
4157 	}
4158     }
4159 
4160     /* open printer for recording */
4161     bufopen(&prn);
4162 
4163     err = package_make_zipfile(finfo->fname,
4164 			       finfo->pdfdoc,
4165 			       finfo->datafiles,
4166 			       finfo->n_files,
4167 			       pzipname, dest,
4168 			       OPT_G, prn);
4169 
4170     /* show details of operation */
4171     vwin = view_buffer(prn, 78, 300, _("build zip file"), BUILD_PKG, NULL);
4172     gtk_window_set_transient_for(GTK_WINDOW(vwin->main),
4173 				 GTK_WINDOW(finfo->dlg));
4174     gtk_window_set_destroy_with_parent(GTK_WINDOW(vwin->main), TRUE);
4175 
4176     return err;
4177 }
4178 
do_pkg_upload(function_info * finfo)4179 static void do_pkg_upload (function_info *finfo)
4180 {
4181     const char *fname;
4182     gchar *buf = NULL;
4183     char *retbuf = NULL;
4184     gchar *zipname = NULL;
4185     GdkWindow *cwin = NULL;
4186     login_info linfo;
4187     gsize buflen;
4188     int error_printed = 0;
4189     int err;
4190 
4191     err = validate_package_file(finfo->fname, 0);
4192     if (err) {
4193 	return;
4194     }
4195 
4196     if (finfo->pdfdoc || finfo->datafiles != NULL) {
4197 	err = gui_pkg_make_zipfile(finfo, &zipname, NULL);
4198 	if (err) {
4199 	    /* the error message will have been handled above */
4200 	    return;
4201 	}
4202     }
4203 
4204     fname = zipname != NULL ? zipname : finfo->fname;
4205 
4206     login_dialog(&linfo, finfo->dlg);
4207 
4208     if (linfo.canceled) {
4209 	linfo_free(&linfo);
4210 	g_free(zipname);
4211 	return;
4212     }
4213 
4214     /* call for the "watch" cursor */
4215     set_wait_cursor(&cwin);
4216 
4217     err = gretl_file_get_contents(fname, &buf, &buflen);
4218 
4219     if (err) {
4220 	error_printed = 1;
4221     } else {
4222 	err = upload_function_package(linfo.login, linfo.pass,
4223 				      path_last_element(fname),
4224 				      buf, buflen, &retbuf);
4225 	fprintf(stderr, "upload_function_package: err = %d\n", err);
4226     }
4227 
4228     /* restore default cursor */
4229     unset_wait_cursor(cwin);
4230 
4231     if (err) {
4232 	if (!error_printed) {
4233 	    gui_errmsg(err);
4234 	}
4235     } else if (retbuf != NULL && *retbuf != '\0') {
4236 	infobox(retbuf);
4237     }
4238 
4239     if (zipname != NULL) {
4240 	/* delete the upload zipfile */
4241 	gretl_remove(zipname);
4242 	g_free(zipname);
4243     }
4244 
4245     g_free(buf);
4246     free(retbuf);
4247 
4248     linfo_free(&linfo);
4249 }
4250 
upload_precheck_gfn(const char * fname,gchar ** zname)4251 static int upload_precheck_gfn (const char *fname,
4252 				gchar **zname)
4253 {
4254     char **datafiles = NULL;
4255     int pdfdoc = 0;
4256     int n_files = 0;
4257     int err;
4258 
4259     err = validate_package_file(fname, 0);
4260     if (err) {
4261 	return err;
4262     }
4263 
4264     if (package_needs_zipping(fname, &pdfdoc, &datafiles, &n_files)) {
4265 	int resp;
4266 
4267 	resp = yes_no_dialog(NULL,
4268 			     _("This package must be uploaded as a zip file.\n"
4269 			       "Try to create the zip file now?"),
4270 			     NULL);
4271 	if (resp == GRETL_YES) {
4272 	    PRN *prn = NULL;
4273 
4274 	    if (bufopen(&prn)) {
4275 		err = 1;
4276 	    } else {
4277 		err = package_make_zipfile(fname, pdfdoc,
4278 					   datafiles, n_files,
4279 					   zname, NULL,
4280 					   OPT_G, prn);
4281 		/* show result on error */
4282 		if (err) {
4283 		    view_buffer(prn, 78, 300, _("build zip file"),
4284 				BUILD_PKG, NULL);
4285 		} else {
4286 		    gretl_print_destroy(prn);
4287 		}
4288 	    }
4289 	} else {
4290 	    /* not really an error, but canceled */
4291 	    err = 1;
4292 	}
4293 
4294 	strings_array_free(datafiles, n_files);
4295     }
4296 
4297     return err;
4298 }
4299 
4300 /* callback from file selector, so @fname will be a full path */
4301 
upload_specified_package(const char * fname)4302 void upload_specified_package (const char *fname)
4303 {
4304     const char *realname;
4305     gchar *zname = NULL;
4306     gchar *buf = NULL;
4307     char *retbuf = NULL;
4308     login_info linfo;
4309     GdkDisplay *disp;
4310     GdkCursor *cursor;
4311     GdkWindow *w1;
4312     gint x, y;
4313     gsize buflen;
4314     int error_printed = 0;
4315     int err;
4316 
4317     if (has_suffix(fname, ".gfn")) {
4318 	err = upload_precheck_gfn(fname, &zname);
4319 	if (err) {
4320 	    return;
4321 	}
4322     }
4323 
4324     login_dialog(&linfo, mdata->main);
4325 
4326     if (linfo.canceled) {
4327 	if (zname != NULL) {
4328 	    gretl_remove(zname);
4329 	    g_free(zname);
4330 	}
4331 	linfo_free(&linfo);
4332 	return;
4333     }
4334 
4335     realname = zname != NULL ? zname : fname;
4336 
4337     /* set waiting cursor */
4338     disp = gdk_display_get_default();
4339     w1 = gdk_display_get_window_at_pointer(disp, &x, &y);
4340     if (w1 != NULL) {
4341 	cursor = gdk_cursor_new(GDK_WATCH);
4342 	if (cursor != NULL) {
4343 	    gdk_window_set_cursor(w1, cursor);
4344 	    gdk_display_sync(disp);
4345 	    gdk_cursor_unref(cursor);
4346 	}
4347     }
4348 
4349     err = gretl_file_get_contents(realname, &buf, &buflen);
4350 
4351     if (err) {
4352 	error_printed = 1;
4353     } else {
4354 	err = upload_function_package(linfo.login, linfo.pass,
4355 				      path_last_element(realname),
4356 				      buf, buflen, &retbuf);
4357 	fprintf(stderr, "upload_function_package: err = %d\n", err);
4358     }
4359 
4360     /* reset default cursor */
4361     if (w1 != NULL) {
4362 	gdk_window_set_cursor(w1, NULL);
4363     }
4364 
4365     if (err) {
4366 	if (!error_printed) {
4367 	    gui_errmsg(err);
4368 	}
4369     } else if (retbuf != NULL && *retbuf != '\0') {
4370 	infobox(retbuf);
4371     }
4372 
4373     if (zname != NULL) {
4374 	/* we created an on-the-fly temporary zipfile */
4375 	gretl_remove(zname);
4376 	g_free(zname);
4377     }
4378 
4379     g_free(buf);
4380     free(retbuf);
4381 
4382     linfo_free(&linfo);
4383 }
4384 
4385 /* the basename of a function package file must meet some sanity
4386    requirements */
4387 
check_package_filename(const char * fname,int fullpath,GtkWidget * parent)4388 static int check_package_filename (const char *fname,
4389 				   int fullpath,
4390 				   GtkWidget *parent)
4391 {
4392     const char *p = fname;
4393     int n, err = 0;
4394 
4395     if (fullpath) {
4396 	p = path_last_slash_const(fname);
4397 	if (p == NULL) {
4398 	    p = fname;
4399 	} else {
4400 	    p++;
4401 	}
4402     }
4403 
4404     if (fullpath && !has_suffix(p, ".gfn")) {
4405 	/* must have the right suffix */
4406 	err = 1;
4407     } else {
4408 	n = strlen(p) - (fullpath ? 4 : 0);
4409 	if (n >= FN_NAMELEN) {
4410 	    /* too long */
4411 	    err = 1;
4412 	} else if (gretl_namechar_spn(p) != n) {
4413 	    /* contains funny stuff */
4414 	    err = 1;
4415 	}
4416     }
4417 
4418     if (err) {
4419 	if (fullpath) {
4420 	    msgbox(_("Invalid package filename: the name must start with a letter,\n"
4421 		     "must be less than 32 characters in length, must include only\n"
4422 		     "ASCII letters, numbers and '_', and must end with \".gfn\"."),
4423 		   GTK_MESSAGE_ERROR, parent);
4424 	} else {
4425 	    msgbox(_("Invalid package name: the name must start with a letter,\n"
4426 		     "must be less than 32 characters in length, and must include\n"
4427 		     "only ASCII letters, numbers and '_'."),
4428 		   GTK_MESSAGE_ERROR, parent);
4429 	}
4430     }
4431 
4432     return err;
4433 }
4434 
pkg_save_special_functions(function_info * finfo)4435 static int pkg_save_special_functions (function_info *finfo)
4436 {
4437     const char *key;
4438     int i, role, err = 0;
4439 
4440     for (i=0; i<N_SPECIALS && !err; i++) {
4441 	role = i + 1;
4442 	key = package_role_get_key(role);
4443 	err = function_set_package_role(finfo->specials[i],
4444 					finfo->pkg,
4445 					key,
4446 					NULL);
4447 	if (!err && role == UFUN_GUI_MAIN) {
4448 	    /* ensure that the gui-main for a model-window
4449 	       package is set as menu-only */
4450 	    if (finfo->menupath != NULL &&
4451 		strstr(finfo->menupath, "MODELWIN")) {
4452 		finfo->gui_attrs[i] |= UFUN_MENU_ONLY;
4453 	    }
4454 	}
4455     }
4456 
4457     return err;
4458 }
4459 
4460 /* We're saving a previously saved/installed package, and it
4461    (now) ought to be in its own subdir (PDF doc or data files
4462    have been specified). We check to see if the gfn file is
4463    actually just sitting in /some/path/functions.
4464 
4465    If so, we try to move it into its own subdir and adjust
4466    everything that depends on its path accordingly.
4467 */
4468 
maybe_fix_package_location(function_info * finfo)4469 static int maybe_fix_package_location (function_info *finfo)
4470 {
4471     const char *pkgname;
4472     int err = 0;
4473 
4474     pkgname = function_package_get_name(finfo->pkg);
4475 
4476     if (pkg_path_is_toplevel(finfo, pkgname)) {
4477 	char *p, newpath[FILENAME_MAX];
4478 
4479 	strcpy(newpath, finfo->fname);
4480 	/* trim off pkgname.gfn */
4481 	p = strrslash(newpath);
4482 	*(p+1) = '\0';
4483 	/* append own subdir name */
4484 	strcat(newpath, pkgname);
4485 	/* make/verify the subdir */
4486 	err = gretl_mkdir(newpath);
4487 	if (!err) {
4488 	    /* append pkgname.gfn */
4489 	    strcat(newpath, SLASHSTR);
4490 	    strcat(newpath, pkgname);
4491 	    strcat(newpath, ".gfn");
4492 	    /* and try moving the file */
4493 	    err = gretl_rename(finfo->fname, newpath);
4494 	}
4495 	if (!err) {
4496 	    /* maybe revise "recent" gfn list */
4497 	    delete_from_filelist(FILE_LIST_GFN, finfo->fname);
4498 	    /* update the record in @finfo */
4499 	    g_free(finfo->fname);
4500 	    finfo->fname = g_strdup(newpath);
4501 	    /* and also the in-memory package */
4502 	    function_package_set_properties(finfo->pkg, "fname",
4503 					    newpath, NULL);
4504 	}
4505 
4506 	fprintf(stderr, "maybe_fix_package_location: err = %d\n", err);
4507     }
4508 
4509     return err;
4510 }
4511 
retitle_gfn_dialog(function_info * finfo,const char * pkgname)4512 static void retitle_gfn_dialog (function_info *finfo,
4513 				const char *pkgname)
4514 {
4515     gchar *title;
4516 
4517     title = g_strdup_printf("gretl: %s", pkgname);
4518     gtk_window_set_title(GTK_WINDOW(finfo->dlg), title);
4519     g_free(title);
4520 }
4521 
4522 /* Callback from file selector when saving a function package, or
4523    directly from the package editor if using the package's
4524    existing filename.
4525 */
4526 
save_function_package(const char * fname,gpointer p)4527 int save_function_package (const char *fname, gpointer p)
4528 {
4529     function_info *finfo = p;
4530     gchar *pdfstr = NULL;
4531     int err = 0;
4532 
4533     if (finfo->fname == NULL) {
4534 	/* new save: no filename recorded yet */
4535 	err = check_package_filename(fname, 1, finfo->dlg);
4536 	if (err) {
4537 	    return err;
4538 	}
4539 	finfo->fname = g_strdup(fname);
4540     }
4541 
4542     if (finfo->pkg == NULL) {
4543 	/* starting from scratch */
4544 	finfo->pkg = function_package_new(fname, finfo->pubnames, finfo->n_pub,
4545 					  finfo->privnames, finfo->n_priv,
4546 					  &err);
4547 	function_package_set_editor(finfo->pkg, finfo->dlg);
4548     } else {
4549 	/* revising an existing package */
4550 	err = function_package_connect_funcs(finfo->pkg, finfo->pubnames, finfo->n_pub,
4551 					     finfo->privnames, finfo->n_priv);
4552 	if (err) {
4553 	    fprintf(stderr, "function_package_connect_funcs: err = %d\n", err);
4554 	}
4555     }
4556 
4557     if (!err) {
4558 	/* we need to do this before setting the "gui-attrs" below */
4559 	pkg_save_special_functions(finfo);
4560     }
4561 
4562     if (!err && finfo->pdfdoc) {
4563 	/* make temporary "help" placeholder */
4564 	pdfstr = g_strdup_printf("pdfdoc:%s.pdf",
4565 				 function_package_get_name(finfo->pkg));
4566     }
4567 
4568     if (!err) {
4569 	err = function_package_set_properties(finfo->pkg,
4570 					      "author",  finfo->author,
4571 					      "email",   finfo->email,
4572 					      "version", finfo->version,
4573 					      "date",    finfo->date,
4574 					      "description", finfo->pkgdesc,
4575 					      "tags", finfo->tags,
4576 					      "help", pdfstr ? pdfstr : finfo->help,
4577 					      "sample-script", finfo->sample,
4578 					      "data-requirement", finfo->dreq,
4579 					      "min-version", finfo->minver,
4580 					      "menu-attachment", finfo->menupath,
4581 					      "label", finfo->menulabel,
4582 					      "gui-help", finfo->gui_help,
4583 					      "gui-attrs", finfo->gui_attrs,
4584 					      "lives-in-subdir", finfo->uses_subdir,
4585 					      "wants-data-access", finfo->data_access,
4586 					      "model-requirement", finfo->mreq,
4587 					      "provider", finfo->provider,
4588 					      NULL);
4589 	if (err) {
4590 	    fprintf(stderr, "function_package_set_properties: err = %d\n", err);
4591 	}
4592     }
4593 
4594     if (!err) {
4595 	function_package_set_data_files(finfo->pkg,
4596 					finfo->datafiles,
4597 					finfo->n_files);
4598 	function_package_set_depends(finfo->pkg,
4599 				     finfo->depends,
4600 				     finfo->n_depends);
4601     }
4602 
4603     if (!err && finfo->uses_subdir) {
4604 	maybe_fix_package_location(finfo);
4605     }
4606 
4607     if (pdfstr != NULL) {
4608 	/* free temporary placeholder */
4609 	g_free(pdfstr);
4610     }
4611 
4612     if (!err) {
4613 	err = function_package_write_file(finfo->pkg);
4614 	if (err) {
4615 	    fprintf(stderr, "function_package_write_file: err = %d\n", err);
4616 	}
4617     }
4618 
4619     if (err) {
4620 	gui_errmsg(err);
4621     } else {
4622 	const char *pkgname = function_package_get_name(finfo->pkg);
4623 
4624 	retitle_gfn_dialog(finfo, pkgname);
4625 	finfo_set_modified(finfo, FALSE);
4626 	gtk_widget_set_sensitive(finfo->validate, TRUE);
4627 	maybe_update_gfn_browser(pkgname,
4628 				 finfo->version,
4629 				 finfo->date,
4630 				 finfo->author,
4631 				 finfo->pkgdesc,
4632 				 finfo->fname,
4633 				 finfo->uses_subdir,
4634 				 finfo->pdfdoc);
4635 	mkfilelist(FILE_LIST_GFN, finfo->fname, 0);
4636 
4637 	/* destroy the temporary pkgname variable */
4638 	g_free(finfo->ininame);
4639 	finfo->ininame = NULL;
4640 
4641 	/* revise stored gui package info in accordance with any
4642 	   changes above, as needed */
4643 	gui_function_pkg_revise_status(pkgname,
4644 				       finfo->fname,
4645 				       finfo->menulabel,
4646 				       finfo->menupath,
4647 				       finfo->uses_subdir,
4648 				       finfo->dreq,
4649 				       finfo->mreq);
4650     }
4651 
4652     return err;
4653 }
4654 
4655 /* callback from file selector when exporting a package in the form
4656    of a regular script */
4657 
save_function_package_as_script(const char * fname,gpointer p)4658 int save_function_package_as_script (const char *fname, gpointer p)
4659 {
4660     function_info *finfo = p;
4661     ufunc *fun;
4662     PRN *prn;
4663     int i, err = 0;
4664 
4665     prn = gretl_print_new_with_filename(fname, &err);
4666     if (err) {
4667 	file_write_errbox(fname);
4668 	return err;
4669     }
4670 
4671     /* write basic package info */
4672     pprintf(prn, "# author='%s'\n", finfo->author);
4673     if (finfo->email != NULL && *finfo->email != '\0') {
4674 	pprintf(prn, "# email='%s'\n", finfo->email);
4675     }
4676     pprintf(prn, "# version='%s'\n", finfo->version);
4677     pprintf(prn, "# date='%s'\n", finfo->date);
4678 
4679     /* write private functions, if any */
4680     for (i=0; i<finfo->n_priv; i++) {
4681 	fun = get_function_from_package(finfo->privnames[i],
4682 					finfo->pkg);
4683 	if (fun != NULL) {
4684 	    pputc(prn, '\n');
4685 	    gretl_function_print_code(fun, tabwidth, prn);
4686 	}
4687     }
4688 
4689     /* write public functions */
4690     for (i=0; i<finfo->n_pub; i++) {
4691 	fun = get_function_from_package(finfo->pubnames[i],
4692 					finfo->pkg);
4693 	if (fun != NULL) {
4694 	    pputc(prn, '\n');
4695 	    gretl_function_print_code(fun, tabwidth, prn);
4696 	}
4697     }
4698 
4699     /* append sample script? */
4700     if ((finfo->save_flags & APPEND_SAMPLE) &&
4701 	finfo->sample != NULL) {
4702 	int n = strlen(finfo->sample);
4703 
4704 	pputs(prn, "\n# sample function call\n");
4705 	pputs(prn, finfo->sample);
4706 	if (finfo->sample[n-1] != '\n') {
4707 	    pputc(prn, '\n');
4708 	}
4709     }
4710 
4711     gretl_print_destroy(prn);
4712 
4713     return 0;
4714 }
4715 
maybe_print(PRN * prn,const char * key,const char * arg)4716 static void maybe_print (PRN *prn, const char *key,
4717 			 const char *arg)
4718 {
4719     if (arg != NULL && *arg != '\0') {
4720 	pprintf(prn, "%s = %s\n", key, arg);
4721     } else {
4722 	pprintf(prn, "%s = \n", key);
4723     }
4724 }
4725 
maybe_write_aux_file(function_info * finfo,const char * fname,const char * id,const gchar * content,PRN * prn)4726 static int maybe_write_aux_file (function_info *finfo,
4727 				 const char *fname,
4728 				 const char *id,
4729 				 const gchar *content,
4730 				 PRN *prn)
4731 {
4732     int ret = 0;
4733 
4734     if (content != NULL && *content != '\0') {
4735 	const char *auxname = NULL;
4736 	int flag;
4737 
4738 	if (!strcmp(id, "sample-script")) {
4739 	    auxname = finfo->sample_fname;
4740 	    flag = WRITE_SAMPFILE;
4741 	} else if (!strcmp(id, "help")) {
4742 	    auxname = finfo->help_fname;
4743 	    flag = WRITE_HELPFILE;
4744 	} else {
4745 	    auxname = finfo->gui_help_fname;
4746 	    flag = WRITE_GUI_HELP;
4747 	}
4748 
4749 	if (auxname != NULL && (finfo->save_flags & flag)) {
4750 	    /* we'll write out the actual file */
4751 	    FILE *fp = NULL;
4752 
4753 	    if (path_last_slash_const(fname)) {
4754 		/* package fname has directory component */
4755 		char *s, tmp[FILENAME_MAX];
4756 
4757 		strcpy(tmp, fname);
4758 		s = strrslash(tmp);
4759 		*(s + 1) = '\0';
4760 		strcat(tmp, auxname);
4761 		fp = gretl_fopen(tmp, "wb"); /* 21017-02-22: was "w" */
4762 	    } else {
4763 		fp = gretl_fopen(auxname, "wb"); /* 21017-02-22: was "w" */
4764 	    }
4765 
4766 	    if (fp != NULL) {
4767 		fputs(content, fp);
4768 		fputc('\n', fp);
4769 		fclose(fp);
4770 		ret = 1;
4771 	    }
4772 	}
4773 
4774 	if (auxname != NULL) {
4775 	    /* record filename in spec file */
4776 	    pputs(prn, auxname);
4777 	}
4778     }
4779 
4780     return ret;
4781 }
4782 
4783 /* Given the in-memory representation of a gfn package, write
4784    out the corresponding .spec file. Also write out to separate
4785    files the package's help text and sample script, if available.
4786 */
4787 
save_function_package_spec(const char * fname,gpointer p)4788 int save_function_package_spec (const char *fname, gpointer p)
4789 {
4790     const char *extra_keys[] = {
4791 	GUI_MAIN,
4792 	"label",
4793 	"menu-attachment",
4794 	BUNDLE_PRINT,
4795 	BUNDLE_PLOT,
4796 	BUNDLE_TEST,
4797 	BUNDLE_FCAST,
4798 	BUNDLE_EXTRA,
4799 	GUI_PRECHECK,
4800 	LIST_MAKER,
4801 	NULL
4802     };
4803     const char *reqstr = NULL;
4804     const char *gui_help;
4805     const char *sample;
4806     gchar *strval;
4807     function_info *finfo = p;
4808     PRN *prn;
4809     char vstr[10];
4810     int nnp = 0, nmo = 0;
4811     int i, len;
4812     int err = 0;
4813 
4814     prn = gretl_print_new_with_filename(fname, &err);
4815     if (err) {
4816 	file_write_errbox(fname);
4817 	return err;
4818     }
4819 
4820     maybe_print(prn, "author", finfo->author);
4821     maybe_print(prn, "email", finfo->email);
4822     maybe_print(prn, "version", finfo->version);
4823     maybe_print(prn, "date", finfo->date);
4824     maybe_print(prn, "description", finfo->pkgdesc);
4825     maybe_print(prn, "tags", finfo->tags);
4826 
4827     if (finfo->minver > 20000 && finfo->minver < 20151) {
4828 	int oldv = translate_program_version(finfo->minver, NEW_TO_OLD);
4829 
4830 	gretl_version_string(vstr, oldv);
4831     } else {
4832 	gretl_version_string(vstr, finfo->minver);
4833     }
4834 
4835     pprintf(prn,"min-version = %s\n", vstr);
4836 
4837     if (finfo->dreq == FN_NEEDS_TS) {
4838 	reqstr = NEEDS_TS;
4839     } else if (finfo->dreq == FN_NEEDS_QM) {
4840 	reqstr = NEEDS_QM;
4841     } else if (finfo->dreq == FN_NEEDS_PANEL) {
4842 	reqstr = NEEDS_PANEL;
4843     } else if (finfo->dreq == FN_NODATA_OK) {
4844 	reqstr = NO_DATA_OK;
4845     }
4846 
4847     if (reqstr != NULL) {
4848 	pprintf(prn, "data-requirement = %s\n", reqstr);
4849     }
4850 
4851     for (i=0; extra_keys[i] != NULL; i++) {
4852 	function_package_get_properties(finfo->pkg, extra_keys[i],
4853 					&strval, NULL);
4854 	if (strval != NULL) {
4855 	    if (*strval != '\0') {
4856 		pprintf(prn, "%s = %s\n", extra_keys[i], strval);
4857 	    }
4858 	    g_free(strval);
4859 	    strval = NULL;
4860 	}
4861     }
4862 
4863     if (finfo->mreq > 0) {
4864 	reqstr = gretl_command_word(finfo->mreq);
4865 	if (*reqstr != '\0') {
4866 	    pprintf(prn, "model-requirement = %s\n", reqstr);
4867 	}
4868     }
4869 
4870     /* public interface names */
4871     pputs(prn, "public = ");
4872     len = 9;
4873     for (i=0; i<finfo->n_pub; i++) {
4874 	const char *s = finfo->pubnames[i];
4875 	int n = strlen(s);
4876 	ufunc *fun;
4877 
4878 	len += n;
4879 	if (len > 72) {
4880 	    pputs(prn, "\\\n");
4881 	    pprintf(prn, "  %s ", s);
4882 	    len = n + 3;
4883 	} else {
4884 	    pprintf(prn, "%s ", s);
4885 	    len++;
4886 	}
4887 	fun = get_function_from_package(s, finfo->pkg);
4888 	if (user_func_is_noprint(fun)) {
4889 	    nnp++;
4890 	}
4891 	if (user_func_is_menu_only(fun)) {
4892 	    nmo++;
4893 	}
4894     }
4895     pputc(prn, '\n');
4896 
4897     if (nnp > 0) {
4898 	/* no-print interface names */
4899 	pputs(prn, "no-print = ");
4900 	for (i=0; i<finfo->n_pub; i++) {
4901 	    const char *s = finfo->pubnames[i];
4902 	    ufunc *fun = get_function_from_package(s, finfo->pkg);
4903 
4904 	    if (user_func_is_noprint(fun)) {
4905 		pprintf(prn, "%s ", s);
4906 	    }
4907 	}
4908 	pputc(prn, '\n');
4909     }
4910 
4911     if (nmo > 0) {
4912 	/* menu-only interface names */
4913 	pputs(prn, "menu-only = ");
4914 	for (i=0; i<finfo->n_pub; i++) {
4915 	    const char *s = finfo->pubnames[i];
4916 	    ufunc *fun = get_function_from_package(s, finfo->pkg);
4917 
4918 	    if (user_func_is_menu_only(fun)) {
4919 		pprintf(prn, "%s ", s);
4920 	    }
4921 	}
4922 	pputc(prn, '\n');
4923     }
4924 
4925     /* write out help text? */
4926     if (finfo->pdfdoc || finfo->help != NULL) {
4927 	pputs(prn, "help = ");
4928 	if (finfo->pdfdoc) {
4929 	    pprintf(prn, "%s.pdf\n", function_package_get_name(finfo->pkg));
4930 	} else {
4931 	    maybe_write_aux_file(finfo, fname, "help", finfo->help, prn);
4932 	    pputc(prn, '\n');
4933 	}
4934     }
4935 
4936     gui_help = (finfo->gui_help != NULL)? finfo->gui_help :
4937 	function_package_get_string(finfo->pkg, "gui-help");
4938 
4939     /* write out GUI-specific help text? */
4940     if (gui_help != NULL) {
4941 	pputs(prn, "gui-help = ");
4942 	maybe_write_aux_file(finfo, fname, "gui-help",
4943 			     gui_help, prn);
4944 	pputc(prn, '\n');
4945     }
4946 
4947     sample = (finfo->sample != NULL)? finfo->sample :
4948 	function_package_get_string(finfo->pkg, "sample-script");
4949 
4950     /* write out sample script? */
4951     pputs(prn, "sample-script = ");
4952     maybe_write_aux_file(finfo, fname, "sample-script",
4953 			 sample, prn);
4954     pputc(prn, '\n');
4955 
4956     /* write out data-files listing? */
4957     if (finfo->datafiles != NULL) {
4958 	pputs(prn, "data-files = ");
4959 	for (i=0; i<finfo->n_files; i++) {
4960 	    pputs(prn, finfo->datafiles[i]);
4961 	    pputc(prn, (i == finfo->n_files - 1)? '\n' : ' ');
4962 	}
4963     }
4964 
4965     /* write out dependency listing? */
4966     if (finfo->depends != NULL) {
4967 	pputs(prn, "depends = ");
4968 	for (i=0; i<finfo->n_depends; i++) {
4969 	    pputs(prn, finfo->depends[i]);
4970 	    pputc(prn, (i == finfo->n_files - 1)? '\n' : ' ');
4971 	}
4972     }
4973 
4974     /* write out provider name? */
4975     if (finfo->provider != NULL) {
4976 	pprintf(prn, "provider = %s\n", finfo->provider);
4977     }
4978 
4979     gretl_print_destroy(prn);
4980 
4981     return 0;
4982 }
4983 
save_function_package_zipfile(const char * fname,gpointer p)4984 int save_function_package_zipfile (const char *fname, gpointer p)
4985 {
4986     function_info *finfo = p;
4987 
4988     gui_pkg_make_zipfile(finfo, NULL, fname);
4989 
4990     return 0;
4991 }
4992 
set_package_pdfname(const char * fname,gpointer p)4993 int set_package_pdfname (const char *fname, gpointer p)
4994 {
4995     function_info *finfo = p;
4996 
4997     g_free(finfo->pdfname);
4998     finfo->pdfname = g_strdup(fname);
4999 
5000     return 0;
5001 }
5002 
5003 /* Called from function selection dialog: a name has been specified
5004    anda set of functions has been selected -- now we need to add info
5005    on author, version, etc, etc.
5006 */
5007 
edit_new_function_package(gchar * pkgname,char ** pubnames,int npub,char ** privnames,int npriv)5008 void edit_new_function_package (gchar *pkgname,
5009 				char **pubnames, int npub,
5010 				char **privnames, int npriv)
5011 {
5012     function_info *finfo = finfo_new();
5013 
5014     if (finfo != NULL) {
5015 	finfo->ininame = pkgname;
5016 	finfo->pubnames = pubnames;
5017 	finfo->n_pub = npub;
5018 	finfo->privnames = privnames;
5019 	finfo->n_priv = npriv;
5020 
5021 	/* set up dialog to do the actual editing */
5022 	finfo_dialog(finfo);
5023     }
5024 }
5025 
5026 /* callback from GUI selector to add/remove functions
5027    when editing a package */
5028 
revise_function_package(void * p,char ** pubnames,int npub,char ** privnames,int npriv)5029 void revise_function_package (void *p, char **pubnames, int npub,
5030 			      char **privnames, int npriv)
5031 {
5032     function_info *finfo = p;
5033     int changed = 0;
5034     int err = 0;
5035 
5036     fprintf(stderr, "original: n_pub=%d, n_priv=%d\n",
5037 	    finfo->n_pub, finfo->n_priv);
5038 
5039     err = finfo_reset_function_names(finfo,
5040 				     pubnames, npub,
5041 				     privnames, npriv,
5042 				     &changed);
5043 
5044     fprintf(stderr, "revised: n_pub=%d, n_priv=%d (changed=%d)\n",
5045 	    finfo->n_pub, finfo->n_priv, changed);
5046 
5047     if (!err && changed) {
5048 	depopulate_combo_box(GTK_COMBO_BOX(finfo->codesel));
5049 	func_selector_set_strings(finfo, finfo->codesel);
5050 	verify_selected_specials(finfo);
5051 	if (finfo->pkg != NULL) {
5052 	    /* sync with gretl_func.c */
5053 	    function_package_connect_funcs(finfo->pkg,
5054 					   finfo->pubnames,
5055 					   finfo->n_pub,
5056 					   finfo->privnames,
5057 					   finfo->n_priv);
5058 	}
5059 	finfo_set_modified(finfo, TRUE);
5060     }
5061 }
5062 
finfo_set_menuwin(function_info * finfo)5063 static void finfo_set_menuwin (function_info *finfo)
5064 {
5065     if (finfo->menupath == NULL) {
5066 	finfo->menuwin = NO_WINDOW;
5067     } else if (!strncmp(finfo->menupath, "MAINWIN", 7)) {
5068 	finfo->menuwin = MAIN_WINDOW;
5069     } else if (!strncmp(finfo->menupath, "MODELWIN", 8)) {
5070 	finfo->menuwin = MODEL_WINDOW;
5071     } else {
5072 	finfo->menuwin = NO_WINDOW;
5073     }
5074 }
5075 
finfo_set_special_names(function_info * finfo)5076 static int finfo_set_special_names (function_info *finfo)
5077 {
5078     const char *key;
5079     int i, err = 0;
5080 
5081     for (i=0; i<N_SPECIALS && !err; i++) {
5082 	key = package_role_get_key(i+1);
5083 	err = function_package_get_properties(finfo->pkg, key,
5084 					      &finfo->specials[i],
5085 					      NULL);
5086     }
5087 
5088     return err;
5089 }
5090 
finfo_set_data_files(function_info * finfo)5091 static int finfo_set_data_files (function_info *finfo)
5092 {
5093     char **S;
5094     int n = 0;
5095 
5096     S = function_package_get_data_files(finfo->pkg, &n);
5097 
5098     if (S != NULL) {
5099 	finfo->datafiles = S;
5100 	finfo->n_files = n;
5101     }
5102 
5103     return 0;
5104 }
5105 
finfo_set_dependencies(function_info * finfo)5106 static int finfo_set_dependencies (function_info *finfo)
5107 {
5108     char **S;
5109     int n = 0;
5110 
5111     S = function_package_get_depends(finfo->pkg, &n);
5112 
5113     if (S != NULL) {
5114 	finfo->depends = S;
5115 	finfo->n_depends = n;
5116     }
5117 
5118     return 0;
5119 }
5120 
is_pdf_reference(const char * s)5121 static int is_pdf_reference (const char *s)
5122 {
5123     if (s != NULL) {
5124 	if (!strncmp(s, "pdfdoc", 6) || has_suffix(s, ".pdf")) {
5125 	    return 1;
5126 	}
5127     }
5128 
5129     return 0;
5130 }
5131 
5132 #define EDIT_ZIPS 0 /* not yet */
5133 
5134 #if EDIT_ZIPS
5135 
load_gfn_from_zip(const char * fname,int * err)5136 static fnpkg *load_gfn_from_zip (const char *fname, int *err)
5137 {
5138     fnpkg *pkg = NULL;
5139     char tmpgfn[MAXLEN];
5140     gchar *tmpname, *tmp2;
5141     char *p;
5142 
5143     tmpname = g_path_get_basename(fname);
5144     p = strrchr(tmpname, '.');
5145     *p = '\0';
5146     tmp2 = g_strdup(tmpname);
5147     strcat(p, ".gfn");
5148 
5149     gretl_build_path(tmpgfn, gretl_dotdir(), tmp2, tmpname, NULL);
5150 
5151 #if 0
5152     fprintf(stderr, "from zip: gfn is '%s'\n", tmpgfn);
5153 #endif
5154 
5155     *err = gretl_unzip_into(fname, gretl_dotdir());
5156     if (!*err) {
5157 	pkg = get_function_package_by_filename(tmpgfn, err);
5158     }
5159 
5160     g_free(tmpname);
5161     g_free(tmp2);
5162 
5163     return pkg;
5164 }
5165 
5166 #endif
5167 
edit_function_package(const char * fname)5168 void edit_function_package (const char *fname)
5169 {
5170     GtkWidget *editor;
5171     function_info *finfo = NULL;
5172     int *publist = NULL;
5173     int *privlist = NULL;
5174     fnpkg *pkg;
5175     int err = 0;
5176 
5177 #if EDIT_ZIPS
5178     if (has_suffix(fname, ".zip")) {
5179 	pkg = load_gfn_from_zip(fname, &err);
5180     } else {
5181 	pkg = get_function_package_by_filename(fname, &err);
5182     }
5183 #else
5184     pkg = get_function_package_by_filename(fname, &err);
5185 #endif
5186     if (err) {
5187 	gui_errmsg(err);
5188 	goto bailout;
5189     }
5190 
5191     editor = function_package_get_editor(pkg);
5192     if (editor != NULL) {
5193 	/* don't open a second editor for a given package */
5194 	gtk_window_present(GTK_WINDOW(editor));
5195 	return;
5196     }
5197 
5198     finfo = finfo_new();
5199     if (finfo == NULL) {
5200 	err = E_ALLOC;
5201 	goto bailout;
5202     }
5203 
5204     finfo->pkg = pkg;
5205 
5206     err = function_package_get_properties(finfo->pkg,
5207 					  "publist",  &publist,
5208 					  "privlist", &privlist,
5209 					  "author",   &finfo->author,
5210 					  "email",    &finfo->email,
5211 					  "version",  &finfo->version,
5212 					  "date",     &finfo->date,
5213 					  "description", &finfo->pkgdesc,
5214 					  "tags", &finfo->tags,
5215 					  "help", &finfo->help,
5216 					  "sample-script", &finfo->sample,
5217 					  "data-requirement", &finfo->dreq,
5218 					  "min-version", &finfo->minver,
5219 					  "menu-attachment", &finfo->menupath,
5220 					  "label", &finfo->menulabel,
5221 					  "gui-help", &finfo->gui_help,
5222 					  "lives-in-subdir", &finfo->uses_subdir,
5223 					  "wants-data-access", &finfo->data_access,
5224 					  "model-requirement", &finfo->mreq,
5225 					  "gui-attrs", finfo->gui_attrs,
5226 					  "provider", &finfo->provider,
5227 					  NULL);
5228 
5229     if (!err && publist == NULL) {
5230 	err = E_DATA;
5231     }
5232 
5233     if (!err) {
5234 	err = finfo_set_function_names(finfo, publist, privlist);
5235     }
5236 
5237     if (!err) {
5238 	err = finfo_set_special_names(finfo);
5239     }
5240 
5241     if (!err) {
5242 	finfo_set_menuwin(finfo);
5243     }
5244 
5245     if (!err) {
5246 	err = finfo_set_data_files(finfo);
5247     }
5248 
5249     if (!err) {
5250 	err = finfo_set_dependencies(finfo);
5251     }
5252 
5253     if (is_pdf_reference(finfo->help)) {
5254 	g_free(finfo->help);
5255 	finfo->help = NULL;
5256 	finfo->pdfdoc = TRUE;
5257     }
5258 
5259 #if PKG_DEBUG
5260     printlist(publist, "publist");
5261     printlist(privlist, "privlist");
5262 #endif
5263 
5264     free(publist);
5265     free(privlist);
5266 
5267     if (err) {
5268 	fprintf(stderr, "function_package_get_info: failed on %s\n", fname);
5269 	errbox("Couldn't get function package information");
5270 	finfo_free(finfo);
5271 	goto bailout;
5272     }
5273 
5274     finfo->fname = g_strdup(fname);
5275 
5276  bailout:
5277 
5278     if (err) {
5279 	delete_from_filelist(FILE_LIST_GFN, fname);
5280     } else {
5281 	/* record opening */
5282 	mkfilelist(FILE_LIST_GFN, finfo->fname, 0);
5283 	/* and go for it */
5284 	finfo_dialog(finfo);
5285     }
5286 }
5287 
edit_specified_package(const char * fname)5288 gboolean edit_specified_package (const char *fname)
5289 {
5290     FILE *fp = gretl_fopen(fname, "rb"); /* 2017-02-22: was "r" */
5291     gboolean ret = FALSE;
5292 
5293     if (fp == NULL) {
5294 	file_read_errbox(fname);
5295 	delete_from_filelist(FILE_LIST_GFN, fname);
5296     } else {
5297 	fclose(fp);
5298 	edit_function_package(fname);
5299 	ret = TRUE;
5300     }
5301 
5302     return ret;
5303 }
5304 
no_user_functions_check(GtkWidget * parent)5305 int no_user_functions_check (GtkWidget *parent)
5306 {
5307     int err = 0;
5308 
5309     if (n_free_functions() == 0) {
5310 	const gchar *query =
5311 	    N_("No functions are available for packaging at present.\n"
5312 	       "Do you want to write a function now?");
5313 	int resp;
5314 
5315 	err = 1;
5316 	resp = yes_no_dialog(_("gretl: function packages"),
5317 			     _(query), parent);
5318 	if (resp == GRETL_YES) {
5319 	    do_new_script(FUNC, NULL);
5320 	}
5321     }
5322 
5323     return err;
5324 }
5325 
5326 /* called from toolbar.c in response to the "build" option from
5327    window editing a .spec file */
5328 
build_package_from_spec_file(windata_t * vwin)5329 void build_package_from_spec_file (windata_t *vwin)
5330 {
5331     char inpname[FILENAME_MAX];
5332     char gfnname[FILENAME_MAX];
5333     int resp, err = 0;
5334 
5335     switch_ext(inpname, vwin->fname, "inp");
5336     err = gretl_test_fopen(inpname, "rb"); /* 2017-02-22: was "r" */
5337     if (err) {
5338 	gchar *msg = g_strdup_printf(_("Couldn't open %s"), inpname);
5339 
5340 	msgbox(msg, GTK_MESSAGE_ERROR, vwin->main);
5341 	g_free(msg);
5342 	return;
5343     }
5344 
5345     switch_ext(gfnname, vwin->fname, "gfn");
5346     resp = overwrite_gfn_check(gfnname, vwin->main, NULL);
5347 
5348     if (resp == GRETL_YES) {
5349 	int save_batch = gretl_in_batch_mode();
5350 	windata_t *prnwin;
5351 	PRN *prn;
5352 
5353 	if (bufopen(&prn)) {
5354 	    return;
5355 	}
5356 
5357 	function_package_unload_by_filename(gfnname);
5358 
5359 	pprintf(prn, "Found script file '%s'\n", inpname);
5360 	err = execute_script(inpname, NULL, prn, SCRIPT_EXEC | INCLUDE_EXEC,
5361 			     vwin->main);
5362 	if (!err) {
5363 	    err = create_and_write_function_package(gfnname, OPT_G, prn);
5364 	    if (err) {
5365 		pputs(prn, "Failed to produce gfn file\n");
5366 	    } else {
5367 		pprintf(prn, "Wrote '%s'\n", gfnname);
5368 	    }
5369 	}
5370 	gretl_set_batch_mode(save_batch);
5371 	prnwin = view_buffer(prn, 78, 450, _("build gfn file"), BUILD_PKG, NULL);
5372 	gtk_window_set_transient_for(GTK_WINDOW(prnwin->main),
5373 				     GTK_WINDOW(vwin->main));
5374 	gtk_window_set_destroy_with_parent(GTK_WINDOW(prnwin->main), TRUE);
5375     }
5376 }
5377