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 /* helpfiles.c for gretl */
21 
22 #include "gretl.h"
23 #include "textbuf.h"
24 #include "gretl_www.h"
25 #include "treeutils.h"
26 #include "dlgutils.h"
27 #include "menustate.h"
28 #include "toolbar.h"
29 #include "winstack.h"
30 #include "database.h"
31 #include "fncall.h"
32 
33 #ifdef G_OS_WIN32
34 # include "gretlwin32.h"
35 #else
36 # include <sys/stat.h>
37 # include <sys/types.h>
38 # include <fcntl.h>
39 # include <unistd.h>
40 # include <dirent.h>
41 #endif
42 
43 #define HDEBUG 0
44 
45 #define cmdref_role(r)  (r == CMD_HELP || r == CMD_HELP_EN)
46 #define funcref_role(r) (r == FUNC_HELP || r == FUNC_HELP_EN)
47 
48 static int translated_cmdref;
49 static int translated_fnref;
50 
51 static windata_t *real_do_help (int idx, int pos, int role);
52 static void en_help_callback (GtkWidget *w, windata_t *hwin);
53 static void helpwin_set_topic_index (windata_t *hwin, int idx);
54 
55 /* searching stuff */
56 static void find_in_text (GtkWidget *button, GtkWidget *dialog);
57 static void find_in_listbox (GtkWidget *button, GtkWidget *dialog);
58 static void find_string_dialog (void (*findfunc)(), windata_t *vwin);
59 static gboolean real_find_in_text (GtkTextView *view, const gchar *s,
60 				   gboolean sensitive,
61 				   gboolean from_cursor,
62 				   gboolean search_all);
63 static gboolean real_find_in_listbox (windata_t *vwin, const gchar *s,
64 				      gboolean sensitive,
65 				      gboolean vnames);
66 static void vwin_finder_callback (GtkEntry *entry, windata_t *vwin);
67 
68 static GtkWidget *find_dialog = NULL;
69 static GtkWidget *find_entry;
70 static char *needle;
71 
72 enum {
73     /* don't collide with the enumeration in toolbar.c */
74     EN_ITEM = 255
75 };
76 
77 static GretlToolItem help_tools[] = {
78     { N_("Larger"), GTK_STOCK_ZOOM_IN, G_CALLBACK(text_larger), 0},
79     { N_("Smaller"), GTK_STOCK_ZOOM_OUT, G_CALLBACK(text_smaller), 0},
80     { N_("Show English help"), GRETL_STOCK_EN, G_CALLBACK(en_help_callback), EN_ITEM }
81 };
82 
83 static gint n_help_tools = G_N_ELEMENTS(help_tools);
84 
85 struct gui_help_item {
86     int code;
87     char *string;
88 };
89 
90 /* codes and strings for GUI help items other than
91    regular gretl commands */
92 
93 static struct gui_help_item gui_help_items[] = {
94     { 0,              "nothing" },
95     { GR_PLOT,        "graphing" },
96     { GR_XY,          "graphing" },
97     { GR_DUMMY,       "factorized" },
98     { GR_XYZ,         "controlled" },
99     { BXPLOT,         "boxplots" },
100     { GR_BOX,         "boxplots" },
101     { GR_FBOX,        "boxplots" },
102     { GR_3D,          "3-D" },
103     { ONLINE,         "online" },
104     { EXPORT,         "export" },
105     { SMPLBOOL,       "sampling" },
106     { SMPLDUM,        "sampling" },
107     { COMPACT,        "compact" },
108     { EXPAND,         "expand" },
109     { TDISAGG,        "tdisagg" },
110     { VSETMISS,       "missing" },
111     { GSETMISS,       "missing" },
112     { GUI_HELP,       "dialog" },
113     { GENR_RANDOM,    "genrand" },
114     { SEED_RANDOM,    "genseed" },
115     { KERNEL_DENSITY, "density" },
116     { HCCME,          "hccme" },
117     { IRF_BOOT,       "irfboot" },
118     { HTEST,          "gui-htest" },
119     { HTESTNP,        "gui-htest-np" },
120     { MODEL_RESTR,    "restrict-model" },
121     { SYS_RESTR,      "restrict-system" },
122     { VECM_RESTR,     "restrict-vecm" },
123     { LAGS_DIALOG,    "lags-dialog" },
124     { MINIBUF,        "minibuffer" },
125     { VLAGSEL,        "VAR-lagselect" },
126     { VAROMIT,        "VAR-omit" },
127     { PANEL_MODE,     "panel-mode" },
128     { PANEL_WLS,      "panel-wls" },
129     { PANEL_B,        "panel-between" },
130     { BOOTSTRAP,      "bootstrap" },
131     { TRANSPOS,       "transpos" },
132     { DATASORT,       "datasort" },
133     { WORKDIR,        "workdir" },
134     { DFGLS,          "dfgls" },
135     { GPT_ADDLINE,    "addline" },
136     { GPT_CURVE,      "curve" },
137     { SAVE_SESSION,   "save-session" },
138     { SAVE_CMD_LOG,   "save-script" },
139     { BFGS_CONFIG,    "bfgs-config" },
140     { SAVE_LABELS,    "save-labels" }, /* FIXME */
141     { OPEN_LABELS,    "add-labels" },  /* FIXME */
142     { COUNTMOD,       "count-model" },
143     { REGLS,          "regls" },
144     { REGLS_ADV,      "regls-advanced" },
145     { BWFILTER,       "bwfilter" },
146     { POLYWEIGHTS,    "polyweights" },
147     { EMAFILTER,      "ema-filter" },
148     { X12AHELP,       "x12a" },
149     { MAILHELP,       "mailer" },
150     { LOESS,          "loess" },
151     { NADARWAT,       "nadarwat" },
152     { CLUSTER,        "cluster" },
153     { GUI_FUNCS,      "gui-funcs" },
154     { MENU_ATTACH,    "menu-attach" },
155     { REPROBIT,       "reprobit" },
156     { DAILY_PURGE,    "daily-purge" },
157     { PKG_FILES,      "data-files" },
158     { PKG_DEPS,       "pkg-depends" },
159     { EDITOR,         "script-editor" },
160     { MIDAS_LIST,     "MIDAS_list" },
161     { MIDAS_PARM,     "MIDAS_parm" },
162     { PKGHELP,        "packages" },
163     { DBNHELP,        "dbnomics" },
164     { MAPHELP,        "maps" },
165     { -1,             NULL },
166 };
167 
168 /* state the topic headings from the script help files so they
169    can be translated */
170 
171 const char *intl_topics[] = {
172     N_("Dataset"),
173     N_("Estimation"),
174     N_("Graphs"),
175     N_("Prediction"),
176     N_("Printing"),
177     N_("Programming"),
178     N_("Statistics"),
179     N_("Tests"),
180     N_("Transformations"),
181     N_("Utilities")
182 };
183 
184 /* Handle non-uniqueness of map from 'extra' command words to codes
185    (e.g. both GR_PLOT and GR_XY correspond to "graphing").  We want
186    the first code, to find the right place in the help file
187    when responding to a "context help" request.
188 */
189 
gui_ci_to_index(int ci)190 static int gui_ci_to_index (int ci)
191 {
192     if (ci < NC) {
193 	/* regular gretl command, no problem */
194 	return ci;
195     } else {
196 	int i, k, ret = ci;
197 
198 	for (i=1; gui_help_items[i].code > 0; i++) {
199 	    if (ci == gui_help_items[i].code) {
200 		for (k=i-1; k>0; k--) {
201 		    /* back up the list so long as the word above
202 		       is the same as the current one */
203 		    if (!strcmp(gui_help_items[k].string,
204 				gui_help_items[i].string)) {
205 			ret = gui_help_items[k].code;
206 		    } else {
207 			break;
208 		    }
209 		}
210 		return ret;
211 	    }
212 	}
213     }
214 
215     return -1;
216 }
217 
218 /* Public because it's wanted in textbuf.c for getting
219    the help indices of certain items that are not actual
220    gretl commands.
221 */
222 
extra_command_number(const char * s)223 int extra_command_number (const char *s)
224 {
225     int i;
226 
227     for (i=1; gui_help_items[i].code > 0; i++) {
228 	if (!strcmp(s, gui_help_items[i].string)) {
229 	    return gui_help_items[i].code;
230 	}
231     }
232 
233     return -1;
234 }
235 
helpfile_init(void)236 void helpfile_init (void)
237 {
238     translated_cmdref = using_translated_helpfile(GRETL_CMDREF);
239     translated_fnref = using_translated_helpfile(GRETL_FUNCREF);
240 }
241 
quoted_help_string(const char * s)242 char *quoted_help_string (const char *s)
243 {
244     const char *p, *q;
245 
246     p = strchr(s, '"');
247     q = strrchr(s, '"');
248 
249     if (p != NULL && q != NULL && q - p > 1) {
250 	p++;
251 	return g_strndup(p, q - p);
252     }
253 
254     return g_strdup("Missing string");
255 }
256 
gui_help_topic_index(const char * word)257 static int gui_help_topic_index (const char *word)
258 {
259     int h = gretl_command_number(word);
260 
261     if (h == 0) {
262 	h = extra_command_number(word);
263     }
264 
265     return h;
266 }
267 
268 enum {
269     STRING_COL,
270     POSITION_COL,
271     INDEX_COL,
272     NUM_COLS
273 };
274 
help_tree_select_row(GtkTreeSelection * selection,windata_t * hwin)275 static void help_tree_select_row (GtkTreeSelection *selection,
276 				  windata_t *hwin)
277 {
278     GtkTreeIter iter;
279     GtkTreeModel *model;
280 
281     if (!gtk_tree_selection_get_selected(selection, &model, &iter)) {
282 	return;
283     }
284 
285     if (!gtk_tree_model_iter_has_child(model, &iter)) {
286 	int pos, idx;
287 
288 	gtk_tree_model_get(model, &iter,
289 			   POSITION_COL, &pos,
290 			   INDEX_COL, &idx,
291 			   -1);
292 
293 	if (idx != hwin->active_var) {
294 	    /* not already in position */
295 	    real_do_help(idx, pos, hwin->role);
296 	}
297     }
298 }
299 
get_section_iter(GtkTreeModel * model,const char * s,GtkTreeIter * iter)300 static int get_section_iter (GtkTreeModel *model, const char *s,
301 			     GtkTreeIter *iter)
302 {
303     gchar *sect;
304     int found = 0;
305 
306     if (!gtk_tree_model_get_iter_first(model, iter)) {
307 	return 0;
308     }
309 
310     while (!found && gtk_tree_model_iter_next(model, iter)) {
311 	if (gtk_tree_model_iter_has_child(model, iter)) {
312 	    gtk_tree_model_get(model, iter, STRING_COL, &sect, -1);
313 	    if (!strcmp(s, sect)) {
314 		found = 1;
315 	    }
316 	    g_free(sect);
317 	}
318     }
319 
320     return found;
321 }
322 
real_funcs_heading(const char * s)323 static const char *real_funcs_heading (const char *s)
324 {
325     if (!strcmp(s, "access")) {
326 	return _("Accessors");
327     } else if (!strcmp(s, "straccess")) {
328 	return _("Built-in strings");
329     } else if (!strcmp(s, "data-utils")) {
330 	return _("Data utilities");
331     } else if (!strcmp(s, "math")) {
332 	return _("Mathematical");
333     } else if (!strcmp(s, "transforms")) {
334 	return _("Transformations");
335     } else if (!strcmp(s, "matrix")) {
336 	return _("Matrix manipulation");
337     } else if (!strcmp(s, "linalg")) {
338 	return _("Linear algebra");
339     } else if (!strcmp(s, "complex")) {
340 	return _("Complex numbers");
341     } else if (!strcmp(s, "numerical")) {
342 	return _("Numerical methods");
343     } else if (!strcmp(s, "probdist")) {
344 	return _("Probability");
345     } else if (!strcmp(s, "panel")) {
346 	return _("Panel data");
347     } else if (!strcmp(s, "calendar")) {
348 	return _("Calendar");
349     } else if (!strcmp(s, "timeseries")) {
350 	return _("Time-series");
351     } else if (!strcmp(s, "stats")) {
352 	return _("Statistical");
353     } else if (!strcmp(s, "nonparam")) {
354 	return _("Non-parametric");
355     } else if (!strcmp(s, "midas")) {
356 	return _("MIDAS");
357     } else if (!strcmp(s, "sspace")) {
358 	return _("State space");
359     } else if (!strcmp(s, "programming")) {
360 	return _("Programming");
361     } else if (!strcmp(s, "strings")) {
362 	return _("Strings");
363     } else if (!strcmp(s, "mpi")) {
364 	return _("MPI");
365     } else {
366 	return "??";
367     }
368 }
369 
make_help_topics_tree(int role)370 static GtkTreeStore *make_help_topics_tree (int role)
371 {
372     const char *fname;
373     GtkTreeStore *store;
374     GtkTreeIter iter, parent;
375     gchar *s, *buf = NULL;
376     char word[32], sect[48];
377     const char *heading;
378     int pos = 0, idx = 0;
379     int err;
380 
381     if (role == CMD_HELP) {
382 	fname = helpfile_path(GRETL_CMDREF, 0, 0);
383     } else if (role == GUI_HELP) {
384 	fname = helpfile_path(GRETL_GUI_HELP, 0, 0);
385     } else if (role == FUNC_HELP) {
386 	fname = helpfile_path(GRETL_FUNCREF, 0, 0);
387     } else if (role == CMD_HELP_EN) {
388 	fname = helpfile_path(GRETL_CMDREF, 0, 1);
389     } else if (role == GUI_HELP_EN) {
390 	fname = helpfile_path(GRETL_GUI_HELP, 0, 1);
391     } else if (role == FUNC_HELP_EN) {
392 	fname = helpfile_path(GRETL_FUNCREF, 0, 1);
393     } else {
394 	return NULL;
395     }
396 
397     err = gretl_file_get_contents(fname, &buf, NULL);
398     if (err) {
399 	return NULL;
400     }
401 
402     store = gtk_tree_store_new(NUM_COLS, G_TYPE_STRING,
403 			       G_TYPE_INT, G_TYPE_INT);
404 
405     gtk_tree_store_append(store, &iter, NULL);
406     gtk_tree_store_set(store, &iter, STRING_COL, _("Index"),
407 		       POSITION_COL, 0, -1);
408 
409     s = buf;
410 
411 #if HDEBUG
412     fprintf(stderr, "*** processing %s ***\n", fname);
413 #endif
414 
415     while (*s) {
416 	if (*s == '\n' && *(s+1) == '#' &&
417 	    *(s+2) != '#' && *(s+2) != '\0') {
418 	    if (sscanf(s+2, "%31s %47s", word, sect) == 2) {
419 		if (funcref_role(role)) {
420 		    heading = real_funcs_heading(sect);
421 		} else {
422 		    heading = _(sect);
423 		}
424 		if (!get_section_iter(GTK_TREE_MODEL(store), heading, &parent)) {
425 		    gtk_tree_store_append(store, &parent, NULL);
426 		    gtk_tree_store_set(store, &parent,
427 				       STRING_COL, heading,
428 				       POSITION_COL, 0,
429 				       INDEX_COL, 0,
430 				       -1);
431 		}
432 		gtk_tree_store_append(store, &iter, &parent);
433 		if (funcref_role(role)) {
434 		    ++idx;
435 		} else if (role == GUI_HELP || role == GUI_HELP_EN) {
436 		    idx = gui_help_topic_index(word);
437 		} else {
438 		    idx = gretl_command_number(word);
439 		}
440 #if HDEBUG
441 		fprintf(stderr, " %s: pos %d, idx, %d\n", word, pos+1, idx);
442 #endif
443 		gtk_tree_store_set(store, &iter,
444 				   STRING_COL, word,
445 				   POSITION_COL, pos + 1,
446 				   INDEX_COL, idx,
447 				   -1);
448 	    }
449 	}
450 	s++;
451 	pos++;
452     }
453 
454     g_free(buf);
455 
456     return store;
457 }
458 
get_help_topics_tree(int role)459 static GtkTreeStore *get_help_topics_tree (int role)
460 {
461     static GtkTreeStore *cmd_tree;
462     static GtkTreeStore *gui_tree;
463     static GtkTreeStore *func_tree;
464     static GtkTreeStore *en_cmd_tree;
465     static GtkTreeStore *en_gui_tree;
466     static GtkTreeStore *en_func_tree;
467     GtkTreeStore **ptree = NULL;
468 
469     if (role == CMD_HELP) {
470 	ptree = &cmd_tree;
471     } else if (role == GUI_HELP) {
472 	ptree = &gui_tree;
473     } else if (role == FUNC_HELP) {
474 	ptree = &func_tree;
475     } else if (role == CMD_HELP_EN) {
476 	ptree = &en_cmd_tree;
477     } else if (role == GUI_HELP_EN) {
478 	ptree = &en_gui_tree;
479     } else if (role == FUNC_HELP_EN) {
480 	ptree = &en_func_tree;
481     } else {
482 	return NULL;
483     }
484 
485     if (*ptree == NULL) {
486 	*ptree = make_help_topics_tree(role);
487     }
488 
489     return *ptree;
490 }
491 
492 /* add a tree-style navigation pane to the left of the help index or
493    text */
494 
add_help_navigator(windata_t * vwin,GtkWidget * hp)495 int add_help_navigator (windata_t *vwin, GtkWidget *hp)
496 {
497     GtkTreeStore *store;
498     GtkWidget *view, *sw;
499     GtkCellRenderer *renderer;
500     GtkTreeViewColumn *column;
501     GtkTreeSelection *select;
502 
503     store = get_help_topics_tree(vwin->role);
504     if (store == NULL) {
505 	return 1;
506     }
507 
508     view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
509     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
510     g_object_set(view, "enable-tree-lines", TRUE, NULL);
511 
512     renderer = gtk_cell_renderer_text_new();
513 
514     column = gtk_tree_view_column_new_with_attributes("",
515 						      renderer,
516 						      "text", 0,
517 						      NULL);
518     gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
519 
520     select = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
521     gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
522     g_signal_connect(G_OBJECT(select), "changed",
523 		     G_CALLBACK(help_tree_select_row),
524 		     vwin);
525 
526     sw = gtk_scrolled_window_new(NULL, NULL);
527     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
528 				   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
529     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
530 					GTK_SHADOW_IN);
531     gtk_container_add(GTK_CONTAINER(sw), view);
532     gtk_paned_pack1(GTK_PANED(hp), sw, FALSE, TRUE);
533     gtk_widget_set_size_request(sw, 150, -1);
534     gtk_tree_view_columns_autosize(GTK_TREE_VIEW(view));
535 
536     g_object_set_data(G_OBJECT(vwin->text), "tview", view);
537 
538     return 0;
539 }
540 
help_attr_from_word(const char * word,int role,int col)541 static int help_attr_from_word (const char *word,
542 				int role, int col)
543 {
544     GtkTreeModel *model;
545     GtkTreeIter iter, child;
546     gchar *s;
547     int attr = 0;
548 
549     model = GTK_TREE_MODEL(get_help_topics_tree(role));
550 
551     if (!model || !gtk_tree_model_get_iter_first(model, &iter)) {
552 	return 0;
553     }
554 
555     while (gtk_tree_model_iter_next(model, &iter)) {
556 	if (gtk_tree_model_iter_children(model, &child, &iter)) {
557 	    while (1) {
558 		gtk_tree_model_get(model, &child, STRING_COL, &s, -1);
559 		if (!strcmp(s, word)) {
560 		    gtk_tree_model_get(model, &child, col, &attr, -1);
561 		    g_free(s);
562 		    return attr;
563 		}
564 		g_free(s);
565 		if (!gtk_tree_model_iter_next(model, &child)) {
566 		    break;
567 		}
568 	    }
569 	}
570     }
571 
572     return 0;
573 }
574 
function_help_index_from_word(const char * word,int role)575 int function_help_index_from_word (const char *word, int role)
576 {
577     return help_attr_from_word(word, role, INDEX_COL);
578 }
579 
function_help_pos_from_word(const char * word,int role)580 static int function_help_pos_from_word (const char *word, int role)
581 {
582     return help_attr_from_word(word, role, POSITION_COL);
583 }
584 
help_pos_from_index(int idx,int role)585 static int help_pos_from_index (int idx, int role)
586 {
587     GtkTreeModel *model;
588     GtkTreeIter iter, child;
589     int pos, tidx;
590 
591     model = GTK_TREE_MODEL(get_help_topics_tree(role));
592 
593     if (!model || !gtk_tree_model_get_iter_first(model, &iter)) {
594 	return 0;
595     }
596 
597     while (gtk_tree_model_iter_next(model, &iter)) {
598 	if (gtk_tree_model_iter_children(model, &child, &iter)) {
599 	    while (1) {
600 		gtk_tree_model_get(model, &child, INDEX_COL, &tidx, -1);
601 		if (tidx == idx) {
602 		    gtk_tree_model_get(model, &child, POSITION_COL, &pos, -1);
603 		    return pos;
604 		}
605 		if (!gtk_tree_model_iter_next(model, &child)) {
606 		    break;
607 		}
608 	    }
609 	}
610     }
611 
612     return 0;
613 }
614 
help_index_from_pos(int pos,int role)615 static int help_index_from_pos (int pos, int role)
616 {
617     GtkTreeModel *model;
618     GtkTreeIter iter, child;
619     int idx, tpos;
620 
621     model = GTK_TREE_MODEL(get_help_topics_tree(role));
622 
623     if (!model || !gtk_tree_model_get_iter_first(model, &iter)) {
624 	return 0;
625     }
626 
627     while (gtk_tree_model_iter_next(model, &iter)) {
628 	if (gtk_tree_model_iter_children(model, &child, &iter)) {
629 	    while (1) {
630 		gtk_tree_model_get(model, &child, POSITION_COL, &tpos, -1);
631 		if (tpos == pos) {
632 		    gtk_tree_model_get(model, &child, INDEX_COL, &idx, -1);
633 		    return idx;
634 		}
635 		if (!gtk_tree_model_iter_next(model, &child)) {
636 		    break;
637 		}
638 	    }
639 	}
640     }
641 
642     return 0;
643 }
644 
help_iter_from_index(int idx,int role,GtkTreeIter * iter,GtkTreeIter * parent)645 static gboolean help_iter_from_index (int idx, int role,
646 				      GtkTreeIter *iter,
647 				      GtkTreeIter *parent)
648 {
649     GtkTreeModel *model;
650     int fnum;
651 
652     model = GTK_TREE_MODEL(get_help_topics_tree(role));
653 
654     if (!model || !gtk_tree_model_get_iter_first(model, parent)) {
655 	return 0;
656     }
657 
658     while (gtk_tree_model_iter_next(model, parent)) {
659 	if (gtk_tree_model_iter_children(model, iter, parent)) {
660 	    while (1) {
661 		gtk_tree_model_get(model, iter, INDEX_COL, &fnum, -1);
662 		if (idx == fnum) {
663 		    return TRUE;
664 		}
665 		if (!gtk_tree_model_iter_next(model, iter)) {
666 		    break;
667 		}
668 	    }
669 	}
670     }
671 
672     return FALSE;
673 }
674 
en_help_callback(GtkWidget * w,windata_t * hwin)675 static void en_help_callback (GtkWidget *w, windata_t *hwin)
676 {
677     int idx = hwin->active_var;
678     int pos, role = 0;
679 
680     if (hwin->role == CMD_HELP) {
681 	role = CMD_HELP_EN;
682     } else if (hwin->role == GUI_HELP) {
683 	role = GUI_HELP_EN;
684     } else if (hwin->role == FUNC_HELP) {
685 	role = FUNC_HELP_EN;
686     } else {
687 	return;
688     }
689 
690     pos = help_pos_from_index(idx, role);
691 
692     if (pos < 0 && role != GUI_HELP_EN) {
693 	/* missed, but we can at least show the index page */
694 	pos = 0;
695     }
696 
697     real_do_help(idx, pos, role);
698 }
699 
700 #if GTK_MAJOR_VERSION == 2
701 
normalize_base(GtkWidget * w,gpointer p)702 static void normalize_base (GtkWidget *w, gpointer p)
703 {
704     gtk_widget_modify_base(w, GTK_STATE_SELECTED, NULL);
705 }
706 
notify_string_not_found(GtkWidget * entry)707 void notify_string_not_found (GtkWidget *entry)
708 {
709     GdkColor color;
710 
711     gdk_color_parse("red", &color);
712     gtk_widget_grab_focus(entry);
713     gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
714     gtk_widget_modify_base(entry, GTK_STATE_SELECTED, &color);
715     g_signal_connect(G_OBJECT(entry), "changed",
716 		     G_CALLBACK(normalize_base), NULL);
717 }
718 
719 #else /* GTK 3.0 */
720 
normalize_style(GtkWidget * w,gpointer p)721 static void normalize_style (GtkWidget *w, gpointer p)
722 {
723     GtkStyleContext *context;
724 
725     if (p == NULL) {
726 	context = gtk_widget_get_style_context(w);
727     } else {
728 	context = GTK_STYLE_CONTEXT(p);
729     }
730     gtk_style_context_remove_class(context, GTK_STYLE_CLASS_ERROR);
731 }
732 
notify_string_not_found(GtkWidget * entry)733 void notify_string_not_found (GtkWidget *entry)
734 {
735     GtkStyleContext *context;
736 
737     context = gtk_widget_get_style_context(entry);
738     gtk_style_context_add_class(context,
739 				GTK_STYLE_CLASS_ERROR);
740     gtk_widget_grab_focus(entry);
741     gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
742     g_signal_connect(G_OBJECT(entry), "changed",
743 		     G_CALLBACK(normalize_style), context);
744 }
745 
746 #endif
747 
748 #define help_index_ok(r) (r == CMD_HELP || \
749                           r == CMD_HELP_EN || \
750                           r == FUNC_HELP || \
751 			  r == FUNC_HELP_EN)
752 
finder_key_handler(GtkEntry * entry,GdkEventKey * event,windata_t * vwin)753 static gboolean finder_key_handler (GtkEntry *entry, GdkEventKey *event,
754 				    windata_t *vwin)
755 {
756     guint keyval = event->keyval;
757 
758 #ifdef OS_OSX
759     if (keyval == GDK_g && cmd_key(event)) {
760 	/* Command-G: repeat search */
761 	vwin_finder_callback(entry, vwin);
762 	return TRUE;
763     }
764 #endif
765 
766     if (keyval == GDK_Tab && help_index_ok(vwin->role) &&
767 	vwin->active_var == 0) {
768 	/* tab-completion in help index mode */
769 	const gchar *s = gtk_entry_get_text(entry);
770 
771 	if (s != NULL && *s != '\0') {
772 	    const char *comp = NULL;
773 
774 	    if (cmdref_role(vwin->role)) {
775 		comp = gretl_command_complete(s);
776 	    } else if (funcref_role(vwin->role)) {
777 		comp = gretl_function_complete(s);
778 	    }
779 
780 	    if (comp != NULL) {
781 		gtk_entry_set_text(entry, comp);
782 		gtk_editable_set_position(GTK_EDITABLE(entry), -1);
783 	    }
784 
785 	    return TRUE;
786 	}
787     } else if (keyval == GDK_g && (event->state & GDK_CONTROL_MASK)) {
788 	/* Ctrl-G: repeat search */
789 	vwin_finder_callback(entry, vwin);
790 	return TRUE;
791     }
792 
793     return FALSE;
794 }
795 
796 #define starts_topic(s) (s[0]=='\n' && s[1]=='#' && s[2]==' ')
797 
798 /* apparatus for permitting the user to search across all the
799    "pages" in a help file
800 */
801 
maybe_switch_page(const char * s,windata_t * hwin)802 static int maybe_switch_page (const char *s, windata_t *hwin)
803 {
804     const gchar *src, *hbuf;
805     int currpos, newpos = 0;
806     int wrapped = 0;
807     int k, n = strlen(s);
808     int ok = 0;
809 
810     /* where are we in the help file right now? */
811     currpos = help_pos_from_index(hwin->active_var, hwin->role);
812     hbuf = (const gchar *) hwin->data;
813     k = currpos;
814     src = hbuf + k;
815 
816     /* skip to start of next page */
817     while (*src != '\0') {
818 	if (starts_topic(src)) {
819 	    break;
820 	}
821 	k++;
822 	src++;
823     }
824 
825  retry:
826 
827     /* see if the search text can be found on a page other than
828        the current one; if so, we'll switch to it */
829 
830     while (!ok && *src != '\0') {
831 	if (starts_topic(src)) {
832 	    /* record page position */
833 	    newpos = k + 1;
834 	} else if (wrapped && newpos == currpos) {
835 	    /* we got back to where we started */
836 	    break;
837 	} else if (newpos != currpos && !strncmp(s, src, n)) {
838 	    /* found the text on a different page */
839 	    ok = 1;
840 	}
841 	k++;
842 	src++;
843     }
844 
845     if (!ok && !wrapped) {
846 	/* start from the top */
847 	src = hbuf;
848 	newpos = k = 0;
849 	wrapped = 1;
850 	goto retry;
851     }
852 
853     if (ok) {
854 	/* text found: move to new position */
855 	int idx = help_index_from_pos(newpos, hwin->role);
856 	int en = (hwin->role == CMD_HELP_EN || hwin->role == FUNC_HELP_EN);
857 
858 	set_help_topic_buffer(hwin, newpos, en);
859 	helpwin_set_topic_index(hwin, idx);
860     }
861 
862     return ok;
863 }
864 
all_lower_case(const char * s)865 static int all_lower_case (const char *s)
866 {
867     while (*s) {
868 	if (tolower(*s) != *s) {
869 	    return 0;
870 	}
871 	s++;
872     }
873 
874     return 1;
875 }
876 
maybe_go_to_page(windata_t * vwin)877 static int maybe_go_to_page (windata_t *vwin)
878 {
879     gboolean ret = FALSE;
880     int idx, pos = 0;
881 
882     if (funcref_role(vwin->role)) {
883 	/* looking for a function */
884 	idx = function_help_index_from_word(needle, vwin->role);
885 	if (idx > 0) {
886 	    pos = help_pos_from_index(idx, vwin->role);
887 	}
888     } else {
889 	/* looking for a command */
890 	idx = gretl_command_number(needle);
891 	if (idx > 0) {
892 	    pos = help_pos_from_index(idx, vwin->role);
893 	}
894     }
895 
896     if (pos > 0) {
897 	real_do_help(idx, pos, vwin->role);
898 	ret = TRUE;
899     }
900 
901     return ret;
902 }
903 
904 /* respond to Enter key in the 'finder' entry */
905 
vwin_finder_callback(GtkEntry * entry,windata_t * vwin)906 static void vwin_finder_callback (GtkEntry *entry, windata_t *vwin)
907 {
908     gboolean search_all = FALSE;
909     gboolean sensitive;
910     gboolean found;
911 
912     needle = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
913     if (needle == NULL || *needle == '\0') {
914 	return;
915     }
916 
917     sensitive = !all_lower_case(needle);
918 
919     if (g_object_get_data(G_OBJECT(entry), "search-all")) {
920 	if (vwin->role == DBNOMICS_TOP ||
921 	    vwin->role == DBNOMICS_DB ||
922 	    vwin->role == DBNOMICS_SERIES) {
923 	    dbnomics_search(needle, vwin);
924 	    return;
925 	} else {
926 	    search_all = TRUE;
927 	}
928     }
929 
930     if (vwin->text != NULL) {
931 	gboolean from_cursor = TRUE;
932 
933 	found = real_find_in_text(GTK_TEXT_VIEW(vwin->text), needle,
934 				  sensitive, from_cursor, search_all);
935     } else {
936 	found = real_find_in_listbox(vwin, needle, sensitive, 0);
937     }
938 
939     if (!found && (vwin->role == CMD_HELP ||
940 		   vwin->role == CMD_HELP_EN ||
941 		   vwin->role == FUNC_HELP ||
942 		   vwin->role == FUNC_HELP_EN)) {
943 	found = maybe_go_to_page(vwin);
944     }
945 
946     if (!found && search_all) {
947 	if (maybe_switch_page(needle, vwin)) {
948 	    found = real_find_in_text(GTK_TEXT_VIEW(vwin->text), needle,
949 				      sensitive, TRUE, TRUE);
950 	}
951     }
952 
953     if (!found) {
954 	notify_string_not_found(GTK_WIDGET(entry));
955     }
956 }
957 
toggle_search_all_help(GtkComboBox * box,GtkWidget * entry)958 static void toggle_search_all_help (GtkComboBox *box, GtkWidget *entry)
959 {
960     gint i = gtk_combo_box_get_active(box);
961 
962     if (i > 0) {
963 	g_object_set_data(G_OBJECT(entry), "search-all", GINT_TO_POINTER(1));
964     } else {
965 	g_object_steal_data(G_OBJECT(entry), "search-all");
966     }
967 }
968 
finder_add_options(GtkWidget * hbox,GtkWidget * entry)969 static void finder_add_options (GtkWidget *hbox, GtkWidget *entry)
970 {
971     GtkWidget *combo = gtk_combo_box_text_new();
972 
973     combo_box_append_text(combo, _("this page"));
974     combo_box_append_text(combo, _("all pages"));
975     gtk_box_pack_end(GTK_BOX(hbox), combo, FALSE, FALSE, 5);
976     gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
977 
978     g_signal_connect(G_OBJECT(combo), "changed",
979 		     G_CALLBACK(toggle_search_all_help),
980 		     entry);
981 }
982 
toggle_search_this_help(GtkComboBox * box,GtkWidget * entry)983 static void toggle_search_this_help (GtkComboBox *box, GtkWidget *entry)
984 {
985     gint i = gtk_combo_box_get_active(box);
986 
987     if (i > 0) {
988 	g_object_steal_data(G_OBJECT(entry), "search-all");
989     } else {
990 	g_object_set_data(G_OBJECT(entry), "search-all", GINT_TO_POINTER(1));
991     }
992 }
993 
finder_add_dbn_options(windata_t * vwin,GtkWidget * hbox,GtkWidget * entry)994 static void finder_add_dbn_options (windata_t *vwin,
995 				    GtkWidget *hbox,
996 				    GtkWidget *entry)
997 {
998     GtkWidget *combo = gtk_combo_box_text_new();
999 
1000     if (vwin->role == DBNOMICS_DB) {
1001 	combo_box_append_text(combo, _("selected dataset"));
1002     } else if (vwin->role == DBNOMICS_SERIES) {
1003 	combo_box_append_text(combo, _("this dataset"));
1004     } else {
1005 	combo_box_append_text(combo, _("all DB.NOMICS"));
1006     }
1007     combo_box_append_text(combo, _("this window"));
1008     gtk_box_pack_end(GTK_BOX(hbox), combo, FALSE, FALSE, 5);
1009     gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1010 
1011     g_object_set_data(G_OBJECT(entry), "search-all", GINT_TO_POINTER(1));
1012     g_signal_connect(G_OBJECT(combo), "changed",
1013 		     G_CALLBACK(toggle_search_this_help),
1014 		     entry);
1015 }
1016 
finder_icon_press(GtkEntry * entry,GtkEntryIconPosition pos,GdkEvent * event,windata_t * vwin)1017 static void finder_icon_press (GtkEntry *entry,
1018 			       GtkEntryIconPosition pos,
1019 			       GdkEvent *event,
1020 			       windata_t *vwin)
1021 {
1022     vwin_finder_callback(entry, vwin);
1023 }
1024 
add_finder_icon(windata_t * vwin,GtkWidget * entry)1025 static void add_finder_icon (windata_t *vwin, GtkWidget *entry)
1026 {
1027     gtk_entry_set_icon_from_stock(GTK_ENTRY(entry),
1028 				  GTK_ENTRY_ICON_SECONDARY,
1029 				  GTK_STOCK_FIND);
1030     gtk_entry_set_icon_activatable(GTK_ENTRY(entry),
1031 				   GTK_ENTRY_ICON_SECONDARY,
1032 				   TRUE);
1033     g_signal_connect(G_OBJECT(entry), "icon-press",
1034 		     G_CALLBACK(finder_icon_press),
1035 		     vwin);
1036 }
1037 
1038 /* add a "search box" to the right of a viewer window's toolbar */
1039 
vwin_add_finder(windata_t * vwin)1040 void vwin_add_finder (windata_t *vwin)
1041 {
1042     GtkWidget *entry;
1043     GtkWidget *hbox;
1044     int fwidth = 16;
1045 
1046     hbox = gtk_widget_get_parent(vwin->mbar);
1047     vwin->finder = entry = gtk_entry_new();
1048 
1049     if (vwin->role == CMD_HELP ||
1050 	vwin->role == CMD_HELP_EN ||
1051 	vwin->role == FUNC_HELP ||
1052 	vwin->role == FUNC_HELP_EN) {
1053 	finder_add_options(hbox, entry);
1054     } else if (vwin->role == DBNOMICS_TOP ||
1055 	       vwin->role == DBNOMICS_DB ||
1056 	       vwin->role == DBNOMICS_SERIES) {
1057 	finder_add_dbn_options(vwin, hbox, entry);
1058 	fwidth = 28;
1059     }
1060 
1061     gtk_entry_set_width_chars(GTK_ENTRY(entry), fwidth);
1062     gtk_box_pack_end(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1063     add_finder_icon(vwin, entry);
1064 
1065     if (vwin->role == DBNOMICS_TOP ||
1066 	vwin->role == VIEW_DBSEARCH ||
1067 	vwin->role == DBNOMICS_SERIES ||
1068 	vwin->role == DBNOMICS_DB) {
1069 	maybe_fill_dbn_finder(vwin->finder);
1070     }
1071 
1072     g_signal_connect(G_OBJECT(entry), "key-press-event",
1073 		     G_CALLBACK(finder_key_handler), vwin);
1074     g_signal_connect(G_OBJECT(entry), "activate",
1075 		     G_CALLBACK(vwin_finder_callback),
1076 		     vwin);
1077 }
1078 
add_footer_close_button(GtkWidget * hbox)1079 static void add_footer_close_button (GtkWidget *hbox)
1080 {
1081     GtkWidget *img = gtk_image_new_from_stock(GRETL_STOCK_CLOSE,
1082 					      GTK_ICON_SIZE_MENU);
1083     GtkWidget *button = gtk_button_new();
1084 
1085     gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
1086     gtk_container_add(GTK_CONTAINER(button), img);
1087     gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 5);
1088     g_signal_connect_swapped(button, "clicked",
1089 			     G_CALLBACK(gtk_widget_hide),
1090 			     hbox);
1091     gtk_widget_show_all(button);
1092 }
1093 
catch_footer_key(GtkWidget * w,GdkEventKey * event,GtkWidget * targ)1094 static gint catch_footer_key (GtkWidget *w, GdkEventKey *event,
1095 			      GtkWidget *targ)
1096 {
1097     if (event->keyval == GDK_Escape) {
1098 	gtk_widget_hide(targ);
1099 	return TRUE;
1100     } else {
1101 	return FALSE;
1102     }
1103 }
1104 
1105 /* Callback from "hide" signal on the footer finder: we
1106    want to turn the focus back onto the associated
1107    text widget
1108 */
1109 
vwin_refocus_text(GtkWidget * w,windata_t * vwin)1110 static void vwin_refocus_text (GtkWidget *w, windata_t *vwin)
1111 {
1112     /* in case the prior string was not found, cancel the
1113        error indicator
1114     */
1115 #if GTK_MAJOR_VERSION == 2
1116     normalize_base(vwin->finder, NULL);
1117 #else
1118     normalize_style(vwin->finder, NULL);
1119 #endif
1120 
1121     if (vwin_is_editing(vwin)) {
1122 	/* let the text area regain focus */
1123 	gtk_widget_grab_focus(vwin->text);
1124 	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(vwin->text), TRUE);
1125     }
1126 }
1127 
vwin_add_footer_finder(windata_t * vwin)1128 static void vwin_add_footer_finder (windata_t *vwin)
1129 {
1130     GtkWidget *hbox, *entry;
1131 
1132     hbox = gtk_hbox_new(FALSE, 5);
1133     vwin->finder = entry = gtk_entry_new();
1134     gtk_entry_set_width_chars(GTK_ENTRY(entry), 20);
1135     add_finder_icon(vwin, entry);
1136 
1137     gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 10);
1138     add_footer_close_button(hbox);
1139     gtk_box_pack_end(GTK_BOX(vwin->vbox), hbox, FALSE, FALSE, 2);
1140 
1141     g_signal_connect(G_OBJECT(entry), "key-press-event",
1142 		     G_CALLBACK(catch_footer_key), hbox);
1143     g_signal_connect(G_OBJECT(entry), "key-press-event",
1144 		     G_CALLBACK(finder_key_handler), vwin);
1145     g_signal_connect(G_OBJECT(entry), "activate",
1146 		     G_CALLBACK(vwin_finder_callback),
1147 		     vwin);
1148     g_signal_connect(G_OBJECT(hbox), "hide",
1149 		     G_CALLBACK(vwin_refocus_text),
1150 		     vwin);
1151 
1152     gtk_widget_show_all(hbox);
1153     gtk_widget_grab_focus(entry);
1154 }
1155 
1156 #define SHOW_FINDER(r)    (r != GUI_HELP && r != GUI_HELP_EN)
1157 #define SHOW_EN_BUTTON(r) ((translated_cmdref && (r==CMD_HELP || r==GUI_HELP)) || \
1158 			   (translated_fnref && r == FUNC_HELP))
1159 
set_up_helpview_menu(windata_t * hwin)1160 void set_up_helpview_menu (windata_t *hwin)
1161 {
1162     GretlToolItem *item = NULL;
1163     GtkWidget *hbox;
1164     int i;
1165 
1166     hbox = gtk_hbox_new(FALSE, 0);
1167     hwin->mbar = gretl_toolbar_new(NULL);
1168 
1169     for (i=0; i<n_help_tools; i++) {
1170 	item = &help_tools[i];
1171 	if (!SHOW_EN_BUTTON(hwin->role) && item->flag == EN_ITEM) {
1172 	    continue;
1173 	}
1174 	gretl_toolbar_insert(hwin->mbar, item, item->func, hwin, -1);
1175     }
1176 
1177     gtk_box_pack_start(GTK_BOX(hwin->vbox), hbox, FALSE, FALSE, 0);
1178     gtk_box_pack_start(GTK_BOX(hbox), hwin->mbar, FALSE, FALSE, 0);
1179 
1180     vwin_add_winlist(hwin);
1181 
1182     if (SHOW_FINDER(hwin->role)) {
1183 	vwin_add_finder(hwin);
1184     }
1185 
1186     gtk_widget_show_all(hbox);
1187 }
1188 
show_gui_help(int helpcode)1189 void show_gui_help (int helpcode)
1190 {
1191     int idx = gui_ci_to_index(helpcode);
1192     int pos, role = GUI_HELP;
1193 
1194     /* try for GUI help first */
1195     pos = help_pos_from_index(idx, role);
1196 
1197     if (pos <= 0 && translated_cmdref) {
1198 	/* English GUI help? */
1199 	role = GUI_HELP_EN;
1200 	pos = help_pos_from_index(idx, role);
1201     }
1202 
1203     if (pos <= 0) {
1204 	/* command help? */
1205 	role = CMD_HELP;
1206 	pos = help_pos_from_index(idx, role);
1207     }
1208 
1209     if (pos <= 0 && translated_cmdref) {
1210 	/* English command help? */
1211 	role = CMD_HELP_EN;
1212 	pos = help_pos_from_index(idx, role);
1213     }
1214 
1215     if (pos > 0) {
1216 	real_do_help(idx, pos, role);
1217     } else {
1218 	warnbox(_("Sorry, no help is available"));
1219     }
1220 }
1221 
context_help(GtkWidget * widget,gpointer data)1222 static void context_help (GtkWidget *widget, gpointer data)
1223 {
1224     int helpcode = GPOINTER_TO_INT(data);
1225 
1226     show_gui_help(helpcode);
1227 }
1228 
context_help_button(GtkWidget * hbox,int helpcode)1229 GtkWidget *context_help_button (GtkWidget *hbox, int helpcode)
1230 {
1231     GtkWidget *button;
1232 
1233     button = gtk_button_new_from_stock(GTK_STOCK_HELP);
1234     gtk_widget_set_can_default(button, TRUE);
1235     gtk_container_add(GTK_CONTAINER(hbox), button);
1236     gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(hbox),
1237 				       button, TRUE);
1238 
1239     if (helpcode >= 0) {
1240 	/* passing helpcode < 0 is a way of reserving
1241 	   the callback for something special
1242 	*/
1243 	g_signal_connect(G_OBJECT(button), "clicked",
1244 			 G_CALLBACK(context_help),
1245 			 GINT_TO_POINTER(helpcode));
1246     }
1247 
1248     return button;
1249 }
1250 
nullify_hwin(GtkWidget * w,windata_t ** phwin)1251 static gboolean nullify_hwin (GtkWidget *w, windata_t **phwin)
1252 {
1253     *phwin = NULL;
1254     return FALSE;
1255 }
1256 
1257 /* sync the tree index view with the currently selected topic, if it's
1258    not already in sync */
1259 
helpwin_set_topic_index(windata_t * hwin,int idx)1260 static void helpwin_set_topic_index (windata_t *hwin, int idx)
1261 {
1262     GtkWidget *w =
1263 	g_object_get_data(G_OBJECT(hwin->text), "tview");
1264     GtkTreeView *view = GTK_TREE_VIEW(w);
1265 
1266     hwin->active_var = idx;
1267 
1268     if (view != NULL) {
1269 	GtkTreeModel *model = gtk_tree_view_get_model(view);
1270 	GtkTreeIter iter, parent;
1271 	gboolean ok;
1272 
1273 	if (idx == 0) {
1274 	    ok = gtk_tree_model_get_iter_first(model, &iter);
1275 	} else {
1276 	    ok = help_iter_from_index(idx, hwin->role, &iter,
1277 				      &parent);
1278 	}
1279 
1280 	if (ok) {
1281 	    GtkTreeSelection *sel;
1282 
1283 	    sel = gtk_tree_view_get_selection(view);
1284 	    if (!gtk_tree_selection_iter_is_selected(sel, &iter)) {
1285 		GtkTreePath *path;
1286 
1287 		/* gtk_tree_view_collapse_all(view); should we? */
1288 		gtk_tree_selection_select_iter(sel, &iter);
1289 		path = gtk_tree_model_get_path(model, &iter);
1290 		gtk_tree_view_expand_to_path(view, path);
1291 		gtk_tree_view_set_cursor(view, path, NULL, FALSE);
1292 		gtk_tree_path_free(path);
1293 	    }
1294 	}
1295     }
1296 }
1297 
real_do_help(int idx,int pos,int role)1298 static windata_t *real_do_help (int idx, int pos, int role)
1299 {
1300     static windata_t *gui_hwin;
1301     static windata_t *cmd_hwin;
1302     static windata_t *func_hwin;
1303     static windata_t *en_gui_hwin;
1304     static windata_t *en_cmd_hwin;
1305     static windata_t *en_func_hwin;
1306     windata_t *hwin = NULL;
1307     const char *fname = NULL;
1308     int en = 0;
1309 
1310     if (pos < 0) {
1311 	dummy_call();
1312 	return NULL;
1313     }
1314 
1315 #if HDEBUG
1316     fprintf(stderr, "real_do_help: idx=%d, pos=%d, role=%d\n",
1317 	    idx, pos, role);
1318     fprintf(stderr, "gui_hwin = %p\n", (void *) gui_hwin);
1319     fprintf(stderr, "cmd_hwin = %p\n", (void *) cmd_hwin);
1320     fprintf(stderr, "func_hwin = %p\n", (void *) func_hwin);
1321     fprintf(stderr, "en_gui_hwin = %p\n", (void *) en_gui_hwin);
1322     fprintf(stderr, "en_cmd_hwin = %p\n", (void *) en_cmd_hwin);
1323     fprintf(stderr, "en_func_hwin = %p\n", (void *) en_func_hwin);
1324 #endif
1325 
1326     if (role == CMD_HELP) {
1327 	hwin = cmd_hwin;
1328 	fname = helpfile_path(GRETL_CMDREF, 0, 0);
1329     } else if (role == GUI_HELP) {
1330 	hwin = gui_hwin;
1331 	fname = helpfile_path(GRETL_GUI_HELP, 0, 0);
1332     } else if (role == FUNC_HELP) {
1333 	hwin = func_hwin;
1334 	fname = helpfile_path(GRETL_FUNCREF, 0, 0);
1335     } else if (role == CMD_HELP_EN) {
1336 	hwin = en_cmd_hwin;
1337 	fname = helpfile_path(GRETL_CMDREF, 0, 1);
1338 	en = 1;
1339     } else if (role == GUI_HELP_EN) {
1340 	hwin = en_gui_hwin;
1341 	fname = helpfile_path(GRETL_GUI_HELP, 0, 1);
1342 	en = 1;
1343     } else if (role == FUNC_HELP_EN) {
1344 	hwin = en_func_hwin;
1345 	fname = helpfile_path(GRETL_FUNCREF, 0, 1);
1346 	en = 1;
1347     }
1348 
1349     if (hwin != NULL) {
1350 	gtk_window_present(GTK_WINDOW(hwin->main));
1351     } else {
1352 	hwin = view_help_file(fname, role);
1353 	if (hwin != NULL) {
1354 	    windata_t **phwin = NULL;
1355 
1356 	    if (role == CMD_HELP) {
1357 		cmd_hwin = hwin;
1358 		phwin = &cmd_hwin;
1359 	    } else if (role == GUI_HELP) {
1360 		gui_hwin = hwin;
1361 		phwin = &gui_hwin;
1362 	    } else if (role == FUNC_HELP) {
1363 		func_hwin = hwin;
1364 		phwin = &func_hwin;
1365 	    } else if (role == CMD_HELP_EN) {
1366 		en_cmd_hwin = hwin;
1367 		phwin = &en_cmd_hwin;
1368 	    } else if (role == GUI_HELP_EN) {
1369 		en_gui_hwin = hwin;
1370 		phwin = &en_gui_hwin;
1371 	    } else if (role == FUNC_HELP_EN) {
1372 		en_func_hwin = hwin;
1373 		phwin = &en_func_hwin;
1374 	    }
1375 
1376 	    g_signal_connect(G_OBJECT(hwin->main), "destroy",
1377 			     G_CALLBACK(nullify_hwin), phwin);
1378 	}
1379     }
1380 
1381 #if HDEBUG
1382     fprintf(stderr, "real_do_help: doing set_help_topic_buffer:\n"
1383 	    " hwin=%p, hcode=%d, pos=%d, role=%d\n",
1384 	    (void *) hwin, hcode, pos, role);
1385 #endif
1386 
1387     if (hwin != NULL) {
1388 	int ret = set_help_topic_buffer(hwin, pos, en);
1389 
1390 	if (ret >= 0) {
1391 	    helpwin_set_topic_index(hwin, idx);
1392 	}
1393     }
1394 
1395     return hwin;
1396 }
1397 
display_text_help(GtkAction * action)1398 void display_text_help (GtkAction *action)
1399 {
1400     if (action != NULL) {
1401 	const char *aname = gtk_action_get_name(action);
1402 
1403 	if (!strcmp(aname, "TextCmdRef")) {
1404 	    real_do_help(0, 0, CMD_HELP);
1405 	} else if (!strcmp(aname, "FuncRef")) {
1406 	    real_do_help(0, 0, FUNC_HELP);
1407 	} else if (!strcmp(aname, "PkgHelp")) {
1408 	    show_gui_help(PKGHELP);
1409 	}
1410     } else {
1411 	/* default: commands help */
1412 	real_do_help(0, 0, CMD_HELP);
1413     }
1414 }
1415 
1416 /* called from textbuf.c */
1417 
command_help_callback(int idx,int en)1418 void command_help_callback (int idx, int en)
1419 {
1420     int role = (en)? CMD_HELP_EN : CMD_HELP;
1421     int pos = 0;
1422 
1423     if (idx > NC) {
1424 	/* a GUI-special help item */
1425 	show_gui_help(idx);
1426 	return;
1427     }
1428 
1429     if (idx > 0) {
1430 	pos = help_pos_from_index(idx, role);
1431 	if (pos < 0 && !en && translated_cmdref) {
1432 	    /* no translated entry: fall back on English */
1433 	    role = CMD_HELP_EN;
1434 	    pos = help_pos_from_index(idx, role);
1435 	}
1436     }
1437 
1438     real_do_help(idx, pos, role);
1439 }
1440 
1441 /* called from textbuf.c */
1442 
function_help_callback(int idx,int en)1443 void function_help_callback (int idx, int en)
1444 {
1445     int role = (en)? FUNC_HELP_EN : FUNC_HELP;
1446     int pos = help_pos_from_index(idx, role);
1447 
1448     real_do_help(idx, pos, role);
1449 }
1450 
1451 /* below: must return > 0 to do anything useful */
1452 
help_pos_from_string(const char * s,int * idx,int * role)1453 static int help_pos_from_string (const char *s, int *idx, int *role)
1454 {
1455     char word[16];
1456     int pos;
1457 
1458     *word = '\0';
1459     strncat(word, s, 15);
1460 
1461     *idx = gretl_command_number(word);
1462     pos = help_pos_from_index(*idx, *role);
1463 
1464     if (pos <= 0 && translated_cmdref) {
1465 	pos = help_pos_from_index(*idx, CMD_HELP_EN);
1466 	if (pos > 0) {
1467 	    *role = CMD_HELP_EN;
1468 	}
1469     }
1470 
1471     if (pos <= 0) {
1472 	/* try for function instead of command? */
1473 	pos = function_help_pos_from_word(word, FUNC_HELP);
1474 	if (pos > 0) {
1475 	    *role = FUNC_HELP;
1476 	    *idx = function_help_index_from_word(word, *role);
1477 	} else if (translated_fnref) {
1478 	    pos = function_help_pos_from_word(word, FUNC_HELP_EN);
1479 	    if (pos > 0) {
1480 		*role = FUNC_HELP_EN;
1481 		*idx = function_help_index_from_word(word, *role);
1482 	    }
1483 	}
1484     }
1485 
1486     return pos;
1487 }
1488 
interactive_script_help(GtkWidget * widget,GdkEventButton * b,windata_t * vwin)1489 gint interactive_script_help (GtkWidget *widget, GdkEventButton *b,
1490 			      windata_t *vwin)
1491 {
1492     if (!window_help_is_active(vwin)) {
1493 	/* command help not activated */
1494 	return FALSE;
1495     } else {
1496 	gchar *text = NULL;
1497 	int pos = -1;
1498 	int idx = 0;
1499 	int role = CMD_HELP;
1500 	GtkTextBuffer *buf;
1501 	GtkTextIter iter;
1502 
1503 	buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
1504 	gtk_text_buffer_get_iter_at_mark(buf, &iter,
1505 					 gtk_text_buffer_get_insert(buf));
1506 
1507 	if (gtk_text_iter_inside_word(&iter)) {
1508 	    GtkTextIter w_start, w_end;
1509 
1510 	    w_start = iter;
1511 	    w_end = iter;
1512 
1513 	    if (!gtk_text_iter_starts_word(&iter)) {
1514 		gtk_text_iter_backward_word_start(&w_start);
1515 	    }
1516 
1517 	    if (!gtk_text_iter_ends_word(&iter)) {
1518 		gtk_text_iter_forward_word_end(&w_end);
1519 	    }
1520 
1521 	    text = gtk_text_buffer_get_text(buf, &w_start, &w_end, FALSE);
1522 
1523 	    /* dollar accessors */
1524 	    if (text != NULL) {
1525 		GtkTextIter dstart = w_start;
1526 		gchar *dtest = NULL;
1527 
1528 		if (gtk_text_iter_backward_char(&dstart)) {
1529 		    dtest = gtk_text_buffer_get_text(buf, &dstart,
1530 						     &w_start, FALSE);
1531 		    if (*dtest == '$') {
1532 			gchar *s = g_strdup_printf("$%s", text);
1533 
1534 			g_free(text);
1535 			text = s;
1536 		    }
1537 		    g_free(dtest);
1538 		}
1539 	    }
1540 	}
1541 
1542 	if (text != NULL && *text != '\0') {
1543 	    pos = help_pos_from_string(text, &idx, &role);
1544 	}
1545 
1546 	unset_window_help_active(vwin);
1547 	text_set_cursor(vwin->text, 0);
1548 
1549 	if (text != NULL && *text != '\0') {
1550 	    if (pos <= 0) {
1551 		warnbox(_("Sorry, help not found"));
1552 	    } else {
1553 		real_do_help(idx, pos, role);
1554 	    }
1555 	}
1556 
1557 	g_free(text);
1558     }
1559 
1560     return FALSE;
1561 }
1562 
1563 /* First response to "help <param>" in GUI console, when given with no
1564    option: if we got a command word or function name, pop open a
1565    nicely formatted help window.  If this function returns non-zero
1566    we'll fall back on the command-line help function.
1567 */
1568 
gui_console_help(const char * param)1569 int gui_console_help (const char *param)
1570 {
1571     int idx = 0, role = CMD_HELP;
1572     int pos, err = 0;
1573 
1574     pos = help_pos_from_string(param, &idx, &role);
1575 
1576     if (pos <= 0) {
1577 	err = 1;
1578     } else {
1579 	real_do_help(idx, pos, role);
1580     }
1581 
1582     return err;
1583 }
1584 
text_find(gpointer unused,gpointer data)1585 void text_find (gpointer unused, gpointer data)
1586 {
1587     windata_t *vwin = (windata_t *) data;
1588 
1589     if (vwin->finder != NULL) {
1590 	GtkWidget *p = gtk_widget_get_parent(vwin->finder);
1591 
1592 	if (!gtk_widget_get_visible(p)) {
1593 	    gtk_widget_show_all(p);
1594 	}
1595 	gtk_widget_grab_focus(vwin->finder);
1596 	gtk_editable_select_region(GTK_EDITABLE(vwin->finder),
1597 				   0, -1);
1598     } else if (vwin->flags & VWIN_USE_FOOTER) {
1599 	vwin_add_footer_finder(vwin);
1600     } else {
1601 	find_string_dialog(find_in_text, vwin);
1602     }
1603 }
1604 
text_find_again(gpointer unused,gpointer data)1605 void text_find_again (gpointer unused, gpointer data)
1606 {
1607     windata_t *vwin = (windata_t *) data;
1608 
1609     if (vwin->finder != NULL) {
1610 	if (gtk_widget_get_visible(vwin->finder)) {
1611 	    g_signal_emit_by_name(G_OBJECT(vwin->finder), "activate", NULL);
1612 	}
1613     } else if (find_dialog != NULL) {
1614 	if (vwin == g_object_get_data(G_OBJECT(find_dialog), "windat")) {
1615 	    find_in_text(NULL, find_dialog);
1616 	}
1617     }
1618 }
1619 
listbox_find(gpointer unused,gpointer data)1620 void listbox_find (gpointer unused, gpointer data)
1621 {
1622     windata_t *vwin = (windata_t *) data;
1623 
1624     if (vwin->finder != NULL) {
1625 	gtk_widget_grab_focus(vwin->finder);
1626 	gtk_editable_select_region(GTK_EDITABLE(vwin->finder),
1627 				   0, -1);
1628     } else {
1629 	find_string_dialog(find_in_listbox, vwin);
1630     }
1631 }
1632 
listbox_find_again(gpointer unused,gpointer data)1633 void listbox_find_again (gpointer unused, gpointer data)
1634 {
1635     windata_t *vwin = (windata_t *) data;
1636 
1637     if (vwin->finder != NULL) {
1638 	g_signal_emit_by_name(G_OBJECT(vwin->finder), "activate", NULL);
1639     } else if (find_dialog != NULL) {
1640 	if (vwin == g_object_get_data(G_OBJECT(find_dialog), "windat")) {
1641 	    find_in_listbox(NULL, find_dialog);
1642 	}
1643     }
1644 }
1645 
close_find_dialog(GtkWidget * widget,gpointer data)1646 static gint close_find_dialog (GtkWidget *widget, gpointer data)
1647 {
1648     find_dialog = NULL;
1649     return FALSE;
1650 }
1651 
string_match_pos(const char * haystack,const char * needle,gboolean sensitive,int start)1652 static int string_match_pos (const char *haystack, const char *needle,
1653 			     gboolean sensitive, int start)
1654 {
1655     int hlen = strlen(haystack);
1656     int nlen = strlen(needle);
1657     int pos, found;
1658 
1659     for (pos = start; pos < hlen; pos++) {
1660 	if (sensitive) {
1661 	    found = !strncmp(&haystack[pos], needle, nlen);
1662 	} else {
1663 	    found = !g_ascii_strncasecmp(&haystack[pos], needle, nlen);
1664 	}
1665         if (found) {
1666              return pos;
1667 	}
1668     }
1669 
1670     return -1;
1671 }
1672 
1673 /* search for string @s in text buffer associated with @view */
1674 
real_find_in_text(GtkTextView * view,const gchar * s,gboolean sensitive,gboolean from_cursor,gboolean search_all)1675 static gboolean real_find_in_text (GtkTextView *view, const gchar *s,
1676 				   gboolean sensitive,
1677 				   gboolean from_cursor,
1678 				   gboolean search_all)
1679 {
1680     GtkTextBuffer *buf;
1681     GtkTextIter iter, start, end;
1682     GtkTextMark *vis;
1683     int found = 0;
1684     int wrapped = 0;
1685     int n = strlen(s);
1686     gchar *got;
1687 
1688     buf = gtk_text_view_get_buffer(view);
1689 
1690  text_search_wrap:
1691 
1692     if (from_cursor) {
1693 	GtkTextIter sel_bound;
1694 
1695 	gtk_text_buffer_get_iter_at_mark(buf, &iter,
1696 					 gtk_text_buffer_get_insert(buf));
1697 	gtk_text_buffer_get_iter_at_mark(buf, &sel_bound,
1698 					 gtk_text_buffer_get_selection_bound(buf));
1699 	gtk_text_iter_order(&sel_bound, &iter);
1700     } else {
1701 	gtk_text_buffer_get_iter_at_offset(buf, &iter, 0);
1702     }
1703 
1704     start = end = iter;
1705 
1706     if (!gtk_text_iter_forward_chars(&end, n)) {
1707 	/* we're already at end of the buffer */
1708 	if (from_cursor && !wrapped && !search_all) {
1709 	    from_cursor = FALSE;
1710 	    wrapped = 1;
1711 	    goto text_search_wrap;
1712 	} else {
1713 	    return 0;
1714 	}
1715     }
1716 
1717     while (!found) {
1718 	got = gtk_text_buffer_get_text(buf, &start, &end, FALSE);
1719 	if (sensitive) {
1720 	    found = !strcmp(got, s);
1721 	} else {
1722 	    found = !g_ascii_strcasecmp(got, s);
1723 	}
1724 	g_free(got);
1725 	if (found || !gtk_text_iter_forward_char(&start) ||
1726 	    !gtk_text_iter_forward_char(&end)) {
1727 	    break;
1728 	}
1729     }
1730 
1731     if (found) {
1732 	gtk_text_buffer_place_cursor(buf, &start);
1733 	gtk_text_buffer_move_mark_by_name(buf, "selection_bound", &end);
1734 	vis = gtk_text_buffer_create_mark(buf, "vis", &end, FALSE);
1735 	gtk_text_view_scroll_to_mark(view, vis, 0.05, FALSE, 0, 0);
1736     } else if (from_cursor && !wrapped && !search_all) {
1737 	/* try wrapping */
1738 	from_cursor = FALSE;
1739 	wrapped = 1;
1740 	goto text_search_wrap;
1741     }
1742 
1743     return found;
1744 }
1745 
find_in_text(GtkWidget * button,GtkWidget * dialog)1746 static void find_in_text (GtkWidget *button, GtkWidget *dialog)
1747 {
1748     windata_t *vwin = g_object_get_data(G_OBJECT(dialog), "windat");
1749     gboolean found, sensitive;
1750 
1751     needle = gtk_editable_get_chars(GTK_EDITABLE(find_entry), 0, -1);
1752     if (needle == NULL || *needle == '\0') {
1753 	return;
1754     }
1755 
1756     sensitive = !all_lower_case(needle);
1757 
1758     found = real_find_in_text(GTK_TEXT_VIEW(vwin->text), needle,
1759 			      sensitive, TRUE, FALSE);
1760 
1761     if (!found) {
1762 	notify_string_not_found(find_entry);
1763     }
1764 }
1765 
1766 static void
get_tree_model_haystack(GtkTreeModel * mod,GtkTreeIter * iter,int col,char * haystack)1767 get_tree_model_haystack (GtkTreeModel *mod, GtkTreeIter *iter, int col,
1768 			 char *haystack)
1769 {
1770     gchar *tmp = NULL;
1771 
1772     gtk_tree_model_get(mod, iter, col, &tmp, -1);
1773     if (tmp != NULL) {
1774 	strcpy(haystack, tmp);
1775 	g_free(tmp);
1776     } else {
1777 	*haystack = '\0';
1778     }
1779 }
1780 
real_find_in_listbox(windata_t * vwin,const gchar * s,gboolean sensitive,gboolean vnames)1781 static gboolean real_find_in_listbox (windata_t *vwin,
1782 				      const gchar *s,
1783 				      gboolean sensitive,
1784 				      gboolean vnames)
1785 {
1786     int search_cols[4] = {0, 0, -1, -1};
1787     int minvar, wrapped = 0;
1788     char haystack[MAXLEN];
1789     char pstr[16];
1790     GtkTreeModel *model = NULL;
1791     GtkTreeIter iter;
1792     gboolean got_iter;
1793     int i, pos = -1;
1794 
1795     /* first check that there's something to search */
1796     if (vwin->listbox != NULL) {
1797 	model = gtk_tree_view_get_model(GTK_TREE_VIEW(vwin->listbox));
1798     }
1799     if (model == NULL) {
1800 	return FALSE;
1801     }
1802 
1803     /* if searching in the main gretl window, start on line 1 */
1804     minvar = (vwin == mdata)? 1 : 0;
1805 
1806     /* first try to get the current line plus one as starting point */
1807     sprintf(pstr, "%d", vwin->active_var);
1808     got_iter = gtk_tree_model_get_iter_from_string(model, &iter, pstr);
1809     if (got_iter) {
1810 	got_iter = gtk_tree_model_iter_next(model, &iter);
1811     }
1812 
1813     if (!got_iter) {
1814 	/* fallback: start from the top */
1815 	got_iter = gtk_tree_model_get_iter_first(model, &iter);
1816     }
1817 
1818     if (!got_iter) {
1819 	/* failed totally, get out */
1820 	return FALSE;
1821     }
1822 
1823     if (vnames) {
1824 	/* case-sensitive search for series names */
1825 	search_cols[0] = 1;  /* series name */
1826 	search_cols[1] = -1; /* invalid */
1827     } else if (vwin == mdata) {
1828 	search_cols[0] = 1;  /* series name */
1829 	search_cols[1] = 2;  /* description */
1830     } else if (vwin->role == FUNC_FILES) {
1831 	search_cols[0] = 0;  /* package name */
1832 	search_cols[1] = 4;  /* description */
1833 	search_cols[2] = 3;  /* author */
1834     } else if (vwin->role == REMOTE_FUNC_FILES) {
1835 	search_cols[0] = 0;  /* package name */
1836 	search_cols[1] = 4;  /* description */
1837 	search_cols[2] = 3;  /* author */
1838     } else {
1839 	/* databases, datafiles */
1840 	search_cols[0] = 1; /* description */
1841 	search_cols[1] = 0; /* filename */
1842     }
1843 
1844  search_wrap:
1845 
1846     while (pos < 0) {
1847 	for (i=0; pos < 0 && search_cols[i] >= 0; i++) {
1848 	    get_tree_model_haystack(model, &iter, search_cols[i], haystack);
1849 	    pos = string_match_pos(haystack, needle, sensitive, 0);
1850 	}
1851 	if (pos >= 0 || !gtk_tree_model_iter_next(model, &iter)) {
1852 	    break;
1853 	}
1854     }
1855 
1856     if (pos < 0 && vwin->active_var > minvar && !wrapped) {
1857 	/* try wrapping to start */
1858 	gtk_tree_model_get_iter_first(model, &iter);
1859 	if (minvar > 0 && !gtk_tree_model_iter_next(model, &iter)) {
1860 	    ; /* do nothing: there's only one line in the box */
1861 	} else {
1862 	    wrapped = 1;
1863 	    goto search_wrap;
1864 	}
1865     }
1866 
1867     if (pos >= 0) {
1868 	GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1869 
1870 	gtk_tree_view_set_cursor(GTK_TREE_VIEW(vwin->listbox),
1871 				 path, NULL, FALSE);
1872 	gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(vwin->listbox),
1873 				     path, NULL, TRUE, 0.5, 0);
1874 	vwin->active_var = tree_path_get_row_number(path);
1875 	gtk_tree_path_free(path);
1876     }
1877 
1878     return (pos >= 0);
1879 }
1880 
1881 /* Given @targ, the name of a function package, try to find
1882    it in @vwin's listbox, and if found focus that row.
1883    Return TRUE if found, FALSE otherwise.
1884 */
1885 
find_package_in_viewer(windata_t * vwin,const gchar * targ)1886 gboolean find_package_in_viewer (windata_t *vwin,
1887 				 const gchar *targ)
1888 {
1889     char haystack[MAXLEN];
1890     GtkTreeModel *model;
1891     GtkTreeIter iter;
1892     int pos = -1;
1893 
1894     model = gtk_tree_view_get_model(GTK_TREE_VIEW(vwin->listbox));
1895 
1896     if (model == NULL || !gtk_tree_model_get_iter_first(model, &iter)) {
1897 	return FALSE;
1898     }
1899 
1900     while (pos < 0) {
1901 	get_tree_model_haystack(model, &iter, 0, haystack);
1902 	pos = string_match_pos(haystack, targ, TRUE, 0);
1903 	if (pos >= 0 || !gtk_tree_model_iter_next(model, &iter)) {
1904 	    break;
1905 	}
1906     }
1907 
1908     if (pos >= 0) {
1909 	GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1910 
1911 	gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(vwin->listbox),
1912 				     path, NULL, FALSE, 0, 0);
1913 	gtk_tree_view_set_cursor(GTK_TREE_VIEW(vwin->listbox),
1914 				 path, NULL, FALSE);
1915 	vwin->active_var = tree_path_get_row_number(path);
1916 	gtk_tree_path_free(path);
1917     }
1918 
1919     return (pos >= 0);
1920 }
1921 
1922 /* used for windows that do not have a built-in search entry,
1923    but which call the function find_string_dialog() */
1924 
find_in_listbox(GtkWidget * w,GtkWidget * dialog)1925 static void find_in_listbox (GtkWidget *w, GtkWidget *dialog)
1926 {
1927     windata_t *vwin = g_object_get_data(G_OBJECT(dialog), "windat");
1928     gpointer vp;
1929     gboolean sensitive;
1930     gboolean vnames = FALSE;
1931     gboolean found;
1932 
1933     if (needle != NULL) {
1934 	g_free(needle);
1935 	needle = NULL;
1936     }
1937 
1938     needle = gtk_editable_get_chars(GTK_EDITABLE(find_entry), 0, -1);
1939     if (needle == NULL || *needle == '\0') {
1940 	return;
1941     }
1942 
1943     sensitive = !all_lower_case(needle);
1944 
1945     /* are we confining the search to variable names? */
1946     vp = g_object_get_data(G_OBJECT(dialog), "vnames_only");
1947     if (vp != NULL) {
1948 	vnames = GPOINTER_TO_INT(vp);
1949 	if (vnames) {
1950 	    /* varname search is advertised as case-sensitive */
1951 	    sensitive = TRUE;
1952 	}
1953     }
1954 
1955     found = real_find_in_listbox(vwin, needle, sensitive, vnames);
1956 
1957     if (!found) {
1958 	notify_string_not_found(find_entry);
1959     }
1960 }
1961 
cancel_find(GtkWidget * button,GtkWidget * dialog)1962 static void cancel_find (GtkWidget *button, GtkWidget *dialog)
1963 {
1964     if (find_dialog != NULL) {
1965 	gtk_widget_destroy(dialog);
1966 	find_dialog = NULL;
1967     }
1968 }
1969 
parent_find(GtkWidget * finder,windata_t * caller)1970 static void parent_find (GtkWidget *finder, windata_t *caller)
1971 {
1972     GtkWidget *w = vwin_toplevel(caller);
1973 
1974     if (w != NULL) {
1975 	gtk_window_set_transient_for(GTK_WINDOW(finder), GTK_WINDOW(w));
1976 	gtk_window_set_destroy_with_parent(GTK_WINDOW(finder), TRUE);
1977     }
1978 }
1979 
toggle_vname_search(GtkToggleButton * tb,GtkWidget * w)1980 static void toggle_vname_search (GtkToggleButton *tb, GtkWidget *w)
1981 {
1982     if (gtk_toggle_button_get_active(tb)) {
1983 	g_object_set_data(G_OBJECT(w), "vnames_only",
1984 			  GINT_TO_POINTER(1));
1985     } else {
1986 	g_object_set_data(G_OBJECT(w), "vnames_only",
1987 			  GINT_TO_POINTER(0));
1988     }
1989 }
1990 
maybe_find_again(GtkWidget * w,GdkEventKey * event,GtkWidget * button)1991 static gint maybe_find_again (GtkWidget *w, GdkEventKey *event,
1992 			      GtkWidget *button)
1993 {
1994     if ((event->state & GDK_CONTROL_MASK) &&
1995 	(event->keyval == GDK_g || event->keyval == GDK_G)) {
1996 	g_signal_emit_by_name(G_OBJECT(button), "clicked", NULL);
1997 	return TRUE;
1998     } else {
1999 	return FALSE;
2000     }
2001 }
2002 
find_string_dialog(void (* findfunc)(),windata_t * vwin)2003 static void find_string_dialog (void (*findfunc)(), windata_t *vwin)
2004 {
2005     GtkWidget *parent;
2006     GtkWidget *label;
2007     GtkWidget *button;
2008     GtkWidget *vbox;
2009     GtkWidget *hbox;
2010 
2011     if (find_dialog != NULL) {
2012 	g_object_set_data(G_OBJECT(find_dialog), "windat", vwin);
2013 	parent_find(find_dialog, vwin);
2014 	gtk_window_present(GTK_WINDOW(find_dialog));
2015 	return;
2016     }
2017 
2018     parent = vwin->topmain != NULL ? vwin->topmain : vwin->main;
2019     find_dialog = gretl_dialog_new(_("gretl: find"), parent, 0);
2020     g_object_set_data(G_OBJECT(find_dialog), "windat", vwin);
2021 
2022     g_signal_connect(G_OBJECT(find_dialog), "destroy",
2023 		     G_CALLBACK(close_find_dialog),
2024 		     find_dialog);
2025 
2026     hbox = gtk_hbox_new(FALSE, 5);
2027     label = gtk_label_new(_(" Find what:"));
2028     gtk_widget_show(label);
2029     find_entry = gtk_entry_new();
2030 
2031     if (needle != NULL) {
2032 	gtk_entry_set_text(GTK_ENTRY(find_entry), needle);
2033 	gtk_editable_select_region(GTK_EDITABLE(find_entry), 0, -1);
2034     }
2035 
2036     g_signal_connect(G_OBJECT(find_entry), "activate",
2037 		     G_CALLBACK(findfunc), find_dialog);
2038     gtk_widget_show(find_entry);
2039     gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 5);
2040     gtk_box_pack_start(GTK_BOX(hbox), find_entry, TRUE, TRUE, 5);
2041     gtk_widget_show(hbox);
2042 
2043     vbox = gtk_dialog_get_content_area(GTK_DIALOG(find_dialog));
2044 
2045     gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
2046 
2047     if (vwin == mdata) {
2048 	hbox = gtk_hbox_new(FALSE, 5);
2049 	button = gtk_check_button_new_with_label(_("Variable names only (case sensitive)"));
2050 	g_signal_connect(G_OBJECT(button), "toggled",
2051 			 G_CALLBACK(toggle_vname_search), find_dialog);
2052 	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
2053 	gtk_widget_show_all(hbox);
2054 	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
2055     }
2056 
2057     hbox = gtk_dialog_get_action_area(GTK_DIALOG(find_dialog));
2058 
2059     /* Close button */
2060     button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
2061     gtk_container_add(GTK_CONTAINER(hbox), button);
2062     g_signal_connect(G_OBJECT(button), "clicked",
2063 		     G_CALLBACK(cancel_find), find_dialog);
2064     gtk_widget_show(button);
2065 
2066     /* Find button */
2067     button = gtk_button_new_from_stock(GTK_STOCK_FIND);
2068     gtk_widget_set_can_default(button, TRUE);
2069     gtk_container_add(GTK_CONTAINER(hbox), button);
2070     g_signal_connect(G_OBJECT(button), "clicked",
2071 		     G_CALLBACK(findfunc), find_dialog);
2072     gtk_widget_grab_default(button);
2073     gtk_widget_show(button);
2074 
2075     g_signal_connect(G_OBJECT(find_entry), "key-press-event",
2076 		     G_CALLBACK(maybe_find_again), button);
2077 
2078     gtk_widget_grab_focus(find_entry);
2079     gtk_widget_show(find_dialog);
2080 }
2081 
2082 enum {
2083     GRETL_GUIDE = 1,
2084     GRETL_REF,
2085     GNUPLOT_REF,
2086     X12A_REF,
2087     GRETL_KEYS,
2088     HANSL_PRIMER,
2089     PKGBOOK,
2090     GRETL_MPI,
2091     GRETL_SVM,
2092     GRETL_DBN,
2093     GRETL_GEO,
2094     GRETL_LP
2095 };
2096 
get_writable_doc_path(char * path,const char * fname)2097 static int get_writable_doc_path (char *path, const char *fname)
2098 {
2099     static int sysdoc_writable = -1;
2100     static int userdoc_writable = -1;
2101     const char *gretldir = gretl_home();
2102     const char *dotdir = gretl_dotdir();
2103     FILE *fp;
2104     int err = 0;
2105 
2106 #ifdef G_OS_WIN32
2107     sysdoc_writable = 0;
2108 #endif
2109 
2110     if (sysdoc_writable == 1) {
2111 	sprintf(path, "%sdoc%c%s", gretldir, SLASH, fname);
2112 	return 0;
2113     } else if (userdoc_writable == 1) {
2114 	sprintf(path, "%sdoc%c%s", dotdir, SLASH, fname);
2115 	return 0;
2116     }
2117 
2118     if (sysdoc_writable < 0) {
2119 	sysdoc_writable = 0;
2120 	sprintf(path, "%sdoc", gretldir);
2121 	if (gretl_mkdir(path) == 0) {
2122 	    strcat(path, SLASHSTR);
2123 	    strcat(path, fname);
2124 	    fp = gretl_fopen(path, "w");
2125 	    if (fp != NULL) {
2126 		sysdoc_writable = 1;
2127 		fclose(fp);
2128 		gretl_remove(path);
2129 	    }
2130 	}
2131     }
2132 
2133     if (!sysdoc_writable && userdoc_writable < 0) {
2134 	/* can't write to 'sys' dir, user dir not tested yet */
2135 	userdoc_writable = 0;
2136 	sprintf(path, "%sdoc", dotdir);
2137 	if (gretl_mkdir(path) == 0) {
2138 	    sprintf(path, "%sdoc%c%s", dotdir, SLASH, fname);
2139 	    fp = gretl_fopen(path, "w");
2140 	    if (fp != NULL) {
2141 		userdoc_writable = 1;
2142 		fclose(fp);
2143 		gretl_remove(path);
2144 	    }
2145 	}
2146     }
2147 
2148     if (!sysdoc_writable && !userdoc_writable) {
2149 	err = 1;
2150     }
2151 
2152     return err;
2153 }
2154 
get_x12a_doc_path(char * path,const char * fname)2155 static int get_x12a_doc_path (char *path, const char *fname)
2156 {
2157     const char *x12a = gretl_x12_arima();
2158     int ret = 0;
2159 
2160     *path = '\0';
2161 
2162     if (x12a != NULL && *x12a != '\0') {
2163 	char *p;
2164 
2165 	strcpy(path, x12a);
2166 	p = strrslash(path);
2167 	if (p != NULL) {
2168 	    sprintf(p + 1, "docs%c%s", SLASH, fname);
2169 	    ret = 1;
2170 	} else {
2171 	    *path = '\0';
2172 	}
2173 
2174 #if !defined(G_OS_WIN32) && !defined(OS_OSX)
2175 	if (!ret) {
2176 	    /* using gretl x12a package? */
2177 	    if (gretl_x12_is_x13()) {
2178 		sprintf(path, "/opt/x13as/docs/%s", fname);
2179 	    } else {
2180 		sprintf(path, "/opt/x12arima/docs/%s", fname);
2181 	    }
2182 	    ret = 1;
2183 	}
2184 #endif
2185     }
2186 
2187     return ret;
2188 }
2189 
2190 /* Get a language-specific query string, for asking
2191    the user whether a translation is preferred.
2192 */
2193 
tr_query(const char * lang)2194 static const char *tr_query (const char *lang)
2195 {
2196     if (!strcmp(lang, "es")) {
2197 	return "¿Mostrar traducción al español?";
2198     } else if (!strcmp(lang, "gl")) {
2199 	return "Mostrar tradución ao galego?";
2200     } else if (!strcmp(lang, "it")) {
2201 	return "Mostra traduzione in italiano?";
2202     } else if (!strcmp(lang, "pt")) {
2203 	return "Mostrar portugues tradução?";
2204     } else if (!strcmp(lang, "ru")) {
2205 	return "Показать перевод на русский язык?";
2206     } else {
2207 	return NULL;
2208     }
2209 }
2210 
2211 /* For @code giving the ID number of a doc resource and
2212    @lang identifying a language, return the filename of a
2213    language-specific version of the resource, or NULL if
2214    none is available.
2215 */
2216 
have_translation(int code,const char * lang)2217 static const char *have_translation (int code, const char *lang)
2218 {
2219     gchar *ret = NULL;
2220 
2221     if (code == HANSL_PRIMER) {
2222 	if (!strcmp(lang, "ru")) {
2223 	    ret = "hansl-primer-ru.pdf";
2224 	}
2225     } else if (code == GRETL_REF) {
2226 	if (!strcmp(lang, "es")) {
2227 	    ret = "gretl-ref-es.pdf";
2228 	} else if (!strcmp(lang, "gl")) {
2229 	    ret = "gretl-ref-gl.pdf";
2230 	} else if (!strcmp(lang, "it")) {
2231 	    ret = "gretl-ref-it.pdf";
2232 	} else if (!strcmp(lang, "pt")) {
2233 	    ret = "gretl-ref-pt.pdf";
2234 	}
2235     }
2236 
2237     return ret;
2238 }
2239 
2240 /* Determine if we should show a translation of the
2241    doc resource indentified by @code. If so, return
2242    the required filename; if not, return NULL.
2243 */
2244 
show_translation(int code)2245 static const char *show_translation (int code)
2246 {
2247     const char *fname = NULL;
2248     char lang[3] = {0};
2249 
2250 #ifdef WIN32
2251     gchar *loc = g_win32_getlocale();
2252 
2253     strncat(lang, loc, 2);
2254     if (loc != NULL) {
2255 	fname = have_translation(code, lang);
2256 	g_free(loc);
2257     }
2258 #elif defined(ENABLE_NLS)
2259     char *loc = setlocale(LC_MESSAGES, NULL);
2260 
2261     if (loc != NULL) {
2262 	strncat(lang, loc, 2);
2263 	fname = have_translation(code, lang);
2264     }
2265 #endif
2266 
2267     if (fname != NULL) {
2268 	/* We have a translation, but does the user want it? */
2269 	const char *msg = tr_query(lang);
2270 	int resp = yes_no_dialog(NULL, msg, NULL);
2271 
2272 	if (resp != GRETL_YES) {
2273 	    fname = NULL;
2274 	}
2275     }
2276 
2277     return fname;
2278 }
2279 
2280 /* @pref is the documentation preference registered in settings.c:
2281    0 = English, US letter
2282    1 = English, A4
2283    [2 = Translation, if available]
2284 */
2285 
find_or_download_pdf(int code,int pref,char * fullpath)2286 static int find_or_download_pdf (int code, int pref, char *fullpath)
2287 {
2288     const char *guide_files[] = {
2289 	"gretl-guide.pdf",
2290 	"gretl-guide-a4.pdf"
2291     };
2292     const char *ref_files[] = {
2293 	"gretl-ref.pdf",
2294 	"gretl-ref-a4.pdf",
2295     };
2296     const char *kbd_files[] = {
2297 	"gretl-keys.pdf",
2298 	"gretl-keys-a4.pdf"
2299     };
2300     const char *primer_files[] = {
2301 	"hansl-primer.pdf",
2302 	"hansl-primer-a4.pdf",
2303     };
2304     const char *pkgbook_files[] = {
2305 	"pkgbook.pdf",
2306 	"pkgbook-a4.pdf"
2307     };
2308     const char *gretlMPI_files[] = {
2309 	"gretl-mpi.pdf",
2310 	"gretl-mpi-a4.pdf"
2311     };
2312     const char *gretlSVM_files[] = {
2313 	"gretl-svm.pdf",
2314 	"gretl-svm-a4.pdf"
2315     };
2316     const char *gretlLP_files[] = {
2317 	"gretl-lpsolve.pdf",
2318 	"gretl-lpsolve-a4.pdf"
2319     };
2320     const char *fname = NULL;
2321     int gotit = 0;
2322     int err = 0;
2323 
2324     if (pref < 0 || pref > 2) {
2325 	/* out of bounds */
2326 	pref = 0;
2327     }
2328 
2329     if (pref > 0) {
2330 	/* Try offering a translation where available: currently only
2331 	   for the Gretl Reference and Hansl primer (Russian).
2332 	*/
2333 	pref = 1;
2334 	if (code == HANSL_PRIMER || code == GRETL_REF) {
2335 	    fname = show_translation(code);
2336 	}
2337     }
2338 
2339 #if 0
2340     fprintf(stderr, "HERE code=%d, pref=%d, fname %s\n",
2341 	    code, pref, fname != NULL ? fname : "TBD");
2342 #endif
2343 
2344     if (fname != NULL) {
2345 	/* we got a specific translation */
2346 	goto next_step;
2347     }
2348 
2349     if (code == GRETL_GUIDE) {
2350 	fname = guide_files[pref];
2351     } else if (code == GRETL_REF) {
2352 	fname = ref_files[pref];
2353     } else if (code == GRETL_KEYS) {
2354 	fname = kbd_files[pref];
2355     } else if (code == HANSL_PRIMER) {
2356 	fname = primer_files[pref];
2357     } else if (code == PKGBOOK) {
2358 	fname = pkgbook_files[pref];
2359     } else if (code == GRETL_MPI) {
2360 	fname = gretlMPI_files[pref];
2361     } else if (code == GRETL_SVM) {
2362 	fname = gretlSVM_files[pref];
2363     } else if (code == GRETL_LP) {
2364 	fname = gretlLP_files[pref];
2365     } else if (code == GNUPLOT_REF) {
2366 	fname = "gnuplot.pdf";
2367     } else if (code == X12A_REF) {
2368 	fname = gretl_x12_is_x13() ? "docX13AS.pdf" : "x12adocV03.pdf";
2369     } else if (code == GRETL_DBN) {
2370 	fname = "dbnomics.pdf";
2371 	sprintf(fullpath, "%sfunctions%cdbnomics%c%s",
2372 		gretl_home(), SLASH, SLASH, fname);
2373     } else if (code == GRETL_GEO) {
2374 	fname = "geoplot.pdf";
2375 	sprintf(fullpath, "%sfunctions%cgeoplot%c%s",
2376 		gretl_home(), SLASH, SLASH, fname);
2377     } else {
2378 	return E_DATA;
2379     }
2380 
2381  next_step:
2382 
2383     fprintf(stderr, "pdf help: looking for %s\n", fname);
2384 
2385     if (code != GRETL_DBN && code != GRETL_GEO) {
2386 	/* is the file available in public dir? */
2387 	sprintf(fullpath, "%sdoc%c%s", gretl_home(), SLASH, fname);
2388     }
2389 
2390     err = gretl_test_fopen(fullpath, "r");
2391     if (!err) {
2392 	gotit = 1;
2393     }
2394 
2395     if (!gotit && code == X12A_REF) {
2396 	get_x12a_doc_path(fullpath, fname);
2397 	if (*fullpath != '\0') {
2398 	    err = gretl_test_fopen(fullpath, "r");
2399 	    if (!err) {
2400 		gotit = 1;
2401 	    }
2402 	}
2403     }
2404 
2405     if (!gotit) {
2406 	/* try in the user's dotdir? */
2407 	if (code == GRETL_DBN) {
2408 	    sprintf(fullpath, "%sfunctions%cdbnomics%cdbnomics.pdf",
2409 		    gretl_dotdir(), SLASH, SLASH);
2410 	} else if (code == GRETL_GEO) {
2411 	    sprintf(fullpath, "%sfunctions%cgeojson%cgeoplot.pdf",
2412 		    gretl_dotdir(), SLASH, SLASH);
2413 	} else {
2414 	    sprintf(fullpath, "%sdoc%c%s", gretl_dotdir(), SLASH, fname);
2415 	}
2416 	err = gretl_test_fopen(fullpath, "r");
2417 	if (!err) {
2418 	    gotit = 1;
2419 	}
2420     }
2421 
2422     if (!gotit) {
2423 	if (code == GRETL_DBN) {
2424 	    /* try installing the dbnomics package */
2425 	    char *dlpath = NULL;
2426 
2427 	    err = download_addon("dbnomics", &dlpath);
2428 	    if (!err) {
2429 		/* .gfn -> .pdf */
2430 		switch_ext(fullpath, dlpath, "pdf");
2431 		free(dlpath);
2432 	    }
2433 	} else {
2434 	    /* try downloading the manual file */
2435 	    err = get_writable_doc_path(fullpath, fname);
2436 	    if (!err) {
2437 		err = retrieve_manfile(fname, fullpath);
2438 	    }
2439 	}
2440 	if (err) {
2441 	    const char *buf = gretl_errmsg_get();
2442 
2443 	    if (*buf) {
2444 		errbox(buf);
2445 	    } else {
2446 		errbox(_("Failed to download file"));
2447 	    }
2448 	}
2449     }
2450 
2451     return err;
2452 }
2453 
get_pdf_path(const char * name,char * fullpath)2454 int get_pdf_path (const char *name, char *fullpath)
2455 {
2456     int code = 0;
2457 
2458     if (!strcmp(name, "gretl-lpsolve.pdf")) {
2459 	code = GRETL_LP;
2460     } else if (!strcmp(name, "gretl-svm.pdf")) {
2461 	code = GRETL_SVM;
2462     }
2463 
2464     if (code > 0) {
2465 	return find_or_download_pdf(code, 0, fullpath);
2466     } else {
2467 	return 1;
2468     }
2469 }
2470 
gretl_show_pdf(const char * fname,const char * option)2471 void gretl_show_pdf (const char *fname, const char *option)
2472 {
2473 #if defined(G_OS_WIN32)
2474     if (option != NULL) {
2475 	win32_open_pdf(fname, option);
2476     } else {
2477 	win32_open_file(fname);
2478     }
2479 #elif defined(OS_OSX)
2480     if (option != NULL) {
2481 	osx_open_pdf(fname, option);
2482     } else {
2483 	osx_open_file(fname);
2484     }
2485 #else
2486     gretl_fork("viewpdf", fname, option);
2487 #endif
2488 }
2489 
display_pdf_help(GtkAction * action)2490 void display_pdf_help (GtkAction *action)
2491 {
2492     char fname[FILENAME_MAX];
2493     int err, code = GRETL_GUIDE;
2494 
2495     if (action != NULL) {
2496 	const char *aname = gtk_action_get_name(action);
2497 
2498 	if (!strcmp(aname, "PDFCmdRef")) {
2499 	    code = GRETL_REF;
2500 	} else if (!strcmp(aname, "KbdRef")) {
2501 	    code = GRETL_KEYS;
2502 	} else if (!strcmp(aname, "Primer")) {
2503 	    code = HANSL_PRIMER;
2504 	} else if (!strcmp(aname, "Pkgbook")) {
2505 	    code = PKGBOOK;
2506 	} else if (!strcmp(aname, "gretlMPI")) {
2507 	    code = GRETL_MPI;
2508 	} else if (!strcmp(aname, "gretlSVM")) {
2509 	    code = GRETL_SVM;
2510 	} else if (!strcmp(aname, "gretlDBN")) {
2511 	    code = GRETL_DBN;
2512 	} else if (!strcmp(aname, "GeoplotDoc")) {
2513 	    code = GRETL_GEO;
2514 	} else if (!strcmp(aname, "LpsolveDoc")) {
2515 	    code = GRETL_LP;
2516 	}
2517     }
2518 
2519     err = find_or_download_pdf(code, get_manpref(), fname);
2520 
2521     if (!err) {
2522 	gretl_show_pdf(fname, NULL);
2523     }
2524 }
2525 
display_guide_chapter(const char * dest)2526 void display_guide_chapter (const char *dest)
2527 {
2528     char fname[FILENAME_MAX];
2529     int err;
2530 
2531     err = find_or_download_pdf(GRETL_GUIDE, get_manpref(), fname);
2532 
2533 #ifdef G_OS_WIN32
2534     if (!err) {
2535 	gretl_show_pdf(fname, dest);
2536     }
2537 #elif defined(OS_OSX)
2538     if (!err) {
2539 	gretl_show_pdf(fname, dest);
2540     }
2541 #else /* Linux */
2542     if (!err) {
2543 	gchar *tmp = NULL;
2544 
2545 	if (strstr(viewpdf, "okular")) {
2546 	    /* special case: option stuck onto fname */
2547 	    tmp = g_strdup_printf("%s#%s", fname, dest);
2548 	    gretl_show_pdf(tmp, NULL);
2549 	} else {
2550 	    if (strstr(viewpdf, "xpdf")) {
2551 		tmp = g_strdup_printf("+%s", dest);
2552 	    } else if (strstr(viewpdf, "evince")) {
2553 		tmp = g_strdup_printf("--named-dest=%s", dest);
2554 	    }
2555 	    gretl_show_pdf(fname, tmp);
2556 	}
2557 	g_free(tmp);
2558     }
2559 #endif
2560 }
2561 
display_gnuplot_help(void)2562 void display_gnuplot_help (void)
2563 {
2564     char fname[FILENAME_MAX];
2565     int err;
2566 
2567     err = find_or_download_pdf(GNUPLOT_REF, 0, fname);
2568 
2569     if (!err) {
2570 	gretl_show_pdf(fname, NULL);
2571     }
2572 }
2573 
display_x12a_help(void)2574 void display_x12a_help (void)
2575 {
2576     char fname[FILENAME_MAX];
2577     int err;
2578 
2579     err = find_or_download_pdf(X12A_REF, 0, fname);
2580 
2581     if (!err) {
2582 	gretl_show_pdf(fname, NULL);
2583     }
2584 }
2585