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 "textbuf.h"
22 #include "toolbar.h"
23 #include "dlgutils.h"
24 #include "winstack.h"
25 #include "tabwin.h"
26 #include "guiprint.h"
27 #include "gui_recode.h"
28 #include "gretl_func.h"
29 #include "addons_utils.h"
30 #include "datafiles.h"
31 #include "database.h"
32 #include "fncall.h"
33 
34 #ifdef G_OS_WIN32
35 # include "gretlwin32.h" /* for browser_open() */
36 #endif
37 
38 #if GTKSOURCEVIEW_VERSION > 2
39 # define GTK_IS_SOURCE_VIEW GTK_SOURCE_IS_VIEW
40 #else /* using GtkSourceView 2 */
41 # include <gtksourceview/gtksourcelanguagemanager.h>
42 # include <gtksourceview/gtksourceprintcompositor.h>
43 # include <gtksourceview/gtksourcestyleschememanager.h>
44 #endif
45 
46 #ifdef HAVE_GTKSV_COMPLETION
47 # include "completions.h"
48 #endif
49 
50 #define TABDEBUG 0
51 #define KDEBUG 0
52 
53 /* Dummy "page" numbers for use in hyperlinks: these
54    must be greater than the number of gretl commands
55    and built-in functions to avoid collisions.
56 */
57 
58 #define GUIDE_PAGE  999
59 #define SCRIPT_PAGE 998
60 #define GFR_PAGE    997
61 #define BIB_PAGE    996
62 #define EXT_PAGE    995
63 #define PDF_PAGE    994
64 #define MNU_PAGE    993
65 #define DBN_PAGE    992
66 #define DBS_PAGE    991
67 #define NEXT_PAGE   990
68 
69 enum {
70     PLAIN_TEXT,
71     BLUE_TEXT,
72     RED_TEXT
73 };
74 
75 #define gui_help(r) (r == GUI_HELP || r == GUI_HELP_EN)
76 #define function_help(r) (r == FUNC_HELP || r == FUNC_HELP_EN)
77 #define foreign_script_role(r) (r == EDIT_GP || \
78 				r == EDIT_R || \
79 				r == EDIT_OX || \
80 				r == EDIT_OCTAVE || \
81 				r == EDIT_PYTHON || \
82 				r == EDIT_STATA ||  \
83 				r == EDIT_JULIA || \
84 				r == EDIT_DYNARE || \
85 				r == EDIT_LPSOLVE)
86 
87 /* globals accessed in settings.c */
88 int tabwidth = 4;
89 int smarttab = 1;
90 int script_line_numbers = 0;
91 int script_auto_bracket = 0;
92 
93 static gboolean script_electric_enter (windata_t *vwin);
94 static gboolean script_tab_handler (windata_t *vwin, GdkEvent *event);
95 static gboolean
96 script_popup_handler (GtkWidget *w, GdkEventButton *event, gpointer p);
97 static gchar *textview_get_current_line_with_newline (GtkWidget *view);
98 static gboolean
99 insert_text_with_markup (GtkTextBuffer *tbuf, GtkTextIter *iter,
100 			 const char *s, int role);
101 static void connect_link_signals (windata_t *vwin);
102 static void auto_indent_script (GtkWidget *w, windata_t *vwin);
103 
text_set_cursor(GtkWidget * w,GdkCursorType cspec)104 void text_set_cursor (GtkWidget *w, GdkCursorType cspec)
105 {
106     GdkWindow *win = gtk_text_view_get_window(GTK_TEXT_VIEW(w),
107                                               GTK_TEXT_WINDOW_TEXT);
108 
109     if (cspec == 0) {
110 	gdk_window_set_cursor(win, NULL);
111     } else {
112 	GdkCursor *cursor = gdk_cursor_new(cspec);
113 
114 	if (cursor != NULL) {
115 	    gdk_window_set_cursor(win, cursor);
116 	    gdk_cursor_unref(cursor);
117 	}
118     }
119 }
120 
cursor_to_top(windata_t * vwin)121 void cursor_to_top (windata_t *vwin)
122 {
123     GtkTextView *view = GTK_TEXT_VIEW(vwin->text);
124     GtkTextBuffer *buf = gtk_text_view_get_buffer(view);
125     GtkTextIter start;
126     GtkTextMark *mark;
127 
128     gtk_text_buffer_get_start_iter(buf, &start);
129     gtk_text_buffer_place_cursor(buf, &start);
130     mark = gtk_text_buffer_create_mark(buf, NULL, &start, FALSE);
131     gtk_text_view_scroll_to_mark(view, mark, 0.0, FALSE, 0, 0);
132     gtk_text_buffer_delete_mark(buf, mark);
133 }
134 
cursor_to_end(windata_t * vwin)135 void cursor_to_end (windata_t *vwin)
136 {
137     GtkTextView *view = GTK_TEXT_VIEW(vwin->text);
138     GtkTextBuffer *buf = gtk_text_view_get_buffer(view);
139     GtkTextIter end;
140 
141     gtk_text_buffer_get_end_iter(buf, &end);
142     gtk_text_buffer_place_cursor(buf, &end);
143 }
144 
scroll_to_foot(windata_t * vwin)145 void scroll_to_foot (windata_t *vwin)
146 {
147     GtkTextView *view = GTK_TEXT_VIEW(vwin->text);
148     GtkTextBuffer *buf = gtk_text_view_get_buffer(view);
149     GtkTextIter end;
150     GtkTextMark *mark;
151 
152     gtk_text_buffer_get_end_iter(buf, &end);
153     mark = gtk_text_buffer_create_mark(buf, NULL, &end, FALSE);
154     gtk_text_view_scroll_to_mark(view, mark, 0.0, FALSE, 0, 0);
155     gtk_text_buffer_delete_mark(buf, mark);
156 }
157 
cursor_to_mark(windata_t * vwin,GtkTextMark * mark)158 void cursor_to_mark (windata_t *vwin, GtkTextMark *mark)
159 {
160     GtkTextView *view = GTK_TEXT_VIEW(vwin->text);
161     GtkTextBuffer *buf = gtk_text_view_get_buffer(view);
162     GtkTextIter iter;
163 
164     gtk_text_buffer_get_iter_at_mark(buf, &iter, mark);
165     gtk_text_buffer_place_cursor(buf, &iter);
166     gtk_text_view_scroll_to_mark(view, mark, 0.0, TRUE, 0, 0.1);
167 }
168 
get_char_width_and_height(GtkWidget * widget,int * width,int * height)169 static void get_char_width_and_height (GtkWidget *widget,
170 				       int *width,
171 				       int *height)
172 {
173     PangoContext *pc;
174     PangoLayout *pl;
175     int w = 0, h = 0;
176 
177     pc = gtk_widget_get_pango_context(widget);
178     pango_context_set_font_description(pc, fixed_font);
179     pl = pango_layout_new(pc);
180     pango_layout_set_text(pl, "X", -1);
181     pango_layout_get_pixel_size(pl, &w, &h);
182     g_object_unref(pl);
183 
184     if (width != NULL) {
185 	*width = w;
186     }
187     if (height != NULL) {
188 	*height = h;
189     }
190 }
191 
get_char_width(GtkWidget * widget)192 gint get_char_width (GtkWidget *widget)
193 {
194     int width;
195 
196     get_char_width_and_height(widget, &width, NULL);
197     return width;
198 }
199 
get_char_height(GtkWidget * widget)200 gint get_char_height (GtkWidget *widget)
201 {
202     int height;
203 
204     get_char_width_and_height(widget, NULL, &height);
205     return height;
206 }
207 
textview_get_text(GtkWidget * view)208 gchar *textview_get_text (GtkWidget *view)
209 {
210     GtkTextBuffer *tbuf;
211     GtkTextIter start, end;
212 
213     g_return_val_if_fail(GTK_IS_TEXT_VIEW(view), NULL);
214 
215     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
216     gtk_text_buffer_get_start_iter(tbuf, &start);
217     gtk_text_buffer_get_end_iter(tbuf, &end);
218 
219     return gtk_text_buffer_get_text(tbuf, &start, &end, FALSE);
220 }
221 
textview_get_trimmed_text(GtkWidget * view)222 gchar *textview_get_trimmed_text (GtkWidget *view)
223 {
224     g_return_val_if_fail(GTK_IS_TEXT_VIEW(view), NULL);
225 
226     return g_strchug(g_strchomp(textview_get_text(view)));
227 }
228 
normalize_line(const char * s)229 static gchar *normalize_line (const char *s)
230 {
231     int i, j = 0, n = strlen(s);
232     gchar *ret = g_malloc0(n);
233 
234      for (i=0; s[i]; i++) {
235 	if (isspace(s[i])) {
236 	    ret[j++] = ' ';
237 	    while (isspace(s[i])) i++;
238 	}
239         ret[j++] = s[i];
240     }
241 
242     return ret;
243 }
244 
textview_get_normalized_line(GtkWidget * view)245 gchar *textview_get_normalized_line (GtkWidget *view)
246 {
247     gchar *ret = NULL;
248 
249     g_return_val_if_fail(GTK_IS_TEXT_VIEW(view), NULL);
250 
251     ret = g_strchug(g_strchomp(textview_get_text(view)));
252     if (strchr(ret, '\n') != NULL || strstr(ret, "  ") != NULL) {
253 	gchar *alt = normalize_line(ret);
254 
255 	g_free(ret);
256 	ret = alt;
257     }
258 
259     return ret;
260 }
261 
262 /* Special: handle the case where text has been line-wrapped
263    in an editor window and we want to save the text as
264    wrapped -- that is, forcibly to truncate excessively
265    long lines. We use this for function-package help text.
266 */
267 
textview_get_wrapped_text(GtkWidget * view)268 gchar *textview_get_wrapped_text (GtkWidget *view)
269 {
270     GtkTextView *tview;
271     GtkTextBuffer *tbuf;
272     GtkTextIter start, end;
273     GString *str;
274     gchar *line;
275 
276     g_return_val_if_fail(GTK_IS_TEXT_VIEW(view), NULL);
277 
278     tview = GTK_TEXT_VIEW(view);
279     tbuf = gtk_text_view_get_buffer(tview);
280     gtk_text_buffer_get_start_iter(tbuf, &start);
281     end = start;
282 
283     /* first detect and handle the case where no wrapping has
284        occurred */
285     if (!gtk_text_view_forward_display_line(tview, &end)) {
286 	gtk_text_buffer_get_end_iter(tbuf, &end);
287 	return gtk_text_buffer_get_text(tbuf, &start, &end, FALSE);
288     }
289 
290     str = g_string_new(NULL);
291 
292     end = start;
293     while (gtk_text_view_forward_display_line(tview, &end)) {
294 	line = gtk_text_buffer_get_text(tbuf, &start, &end, FALSE);
295 	g_strchomp(line);
296 	g_string_append(str, line);
297 	g_string_append_c(str, '\n');
298 	g_free(line);
299 	start = end;
300     }
301 
302     if (!gtk_text_iter_is_end(&start)) {
303 	/* there's some residual text */
304 	gtk_text_buffer_get_end_iter(tbuf, &end);
305 	line = gtk_text_buffer_get_text(tbuf, &start, &end, FALSE);
306 	g_strchomp(line);
307 	g_string_append(str, line);
308 	g_string_append_c(str, '\n');
309 	g_free(line);
310     }
311 
312     return g_string_free(str, FALSE);
313 }
314 
textview_get_selection_or_all(GtkWidget * view,gboolean * selection)315 gchar *textview_get_selection_or_all (GtkWidget *view,
316 				      gboolean *selection)
317 {
318     GtkTextBuffer *tbuf;
319     GtkTextIter start, end;
320 
321     g_return_val_if_fail(GTK_IS_TEXT_VIEW(view), NULL);
322 
323     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
324     if (tbuf == NULL) {
325 	return NULL;
326     }
327 
328     if (gtk_text_buffer_get_selection_bounds(tbuf, &start, &end)) {
329 	*selection = TRUE;
330     } else {
331 	*selection = FALSE;
332 	gtk_text_buffer_get_start_iter(tbuf, &start);
333 	gtk_text_buffer_get_end_iter(tbuf, &end);
334     }
335 
336     return gtk_text_buffer_get_text(tbuf, &start, &end, FALSE);
337 }
338 
real_textview_set_text(GtkWidget * view,const gchar * text,gboolean select)339 static int real_textview_set_text (GtkWidget *view,
340 				   const gchar *text,
341 				   gboolean select)
342 {
343     GtkTextBuffer *tbuf;
344 
345     g_return_val_if_fail(GTK_IS_TEXT_VIEW(view), 1);
346 
347     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
348     g_return_val_if_fail(tbuf != NULL, 1);
349 
350     if (text != NULL && select) {
351 	GtkTextIter start, end;
352 
353 	gtk_text_buffer_set_text(tbuf, text, -1);
354 	gtk_text_buffer_get_start_iter(tbuf, &start);
355 	gtk_text_buffer_get_end_iter(tbuf, &end);
356 	gtk_text_buffer_select_range(tbuf, &start, &end);
357     } else if (text != NULL) {
358 	gtk_text_buffer_set_text(tbuf, text, -1);
359     } else {
360 	gtk_text_buffer_set_text(tbuf, "", -1);
361     }
362 
363     return 0;
364 }
365 
textview_set_text(GtkWidget * view,const gchar * text)366 int textview_set_text (GtkWidget *view, const gchar *text)
367 {
368     return real_textview_set_text(view, text, FALSE);
369 }
370 
textview_set_text_selected(GtkWidget * view,const gchar * text)371 int textview_set_text_selected (GtkWidget *view, const gchar *text)
372 {
373     return real_textview_set_text(view, text, TRUE);
374 }
375 
textview_set_cursor_at_line(GtkWidget * view,int line)376 int textview_set_cursor_at_line (GtkWidget *view, int line)
377 {
378     GtkTextBuffer *tbuf;
379     GtkTextIter iter;
380 
381     g_return_val_if_fail(GTK_IS_TEXT_VIEW(view), 1);
382 
383     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
384     g_return_val_if_fail(tbuf != NULL, 1);
385 
386     gtk_text_buffer_get_iter_at_line(tbuf, &iter, line);
387     gtk_text_buffer_place_cursor(tbuf, &iter);
388 
389     return 0;
390 }
391 
viewer_char_count(windata_t * vwin)392 int viewer_char_count (windata_t *vwin)
393 {
394     GtkTextBuffer *tbuf;
395 
396     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
397     return gtk_text_buffer_get_char_count(tbuf);
398 }
399 
text_paste(GtkWidget * w,windata_t * vwin)400 void text_paste (GtkWidget *w, windata_t *vwin)
401 {
402     gtk_text_buffer_paste_clipboard(gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text)),
403 				    gtk_clipboard_get(GDK_NONE),
404 				    NULL, TRUE);
405 }
406 
text_redo(GtkWidget * w,windata_t * vwin)407 void text_redo (GtkWidget *w, windata_t *vwin)
408 {
409     if (vwin->sbuf != NULL && gtk_source_buffer_can_redo(vwin->sbuf)) {
410 	gtk_source_buffer_redo(vwin->sbuf);
411     } else {
412 	warnbox(_("No redo information available"));
413     }
414 }
415 
text_undo(GtkWidget * w,windata_t * vwin)416 void text_undo (GtkWidget *w, windata_t *vwin)
417 {
418     if (vwin->sbuf != NULL && gtk_source_buffer_can_undo(vwin->sbuf)) {
419 	gtk_source_buffer_undo(vwin->sbuf);
420     } else {
421 	warnbox(_("No undo information available"));
422     }
423 }
424 
text_can_undo(windata_t * vwin)425 int text_can_undo (windata_t *vwin)
426 {
427     if (vwin->sbuf != NULL) {
428 	return gtk_source_buffer_can_undo(vwin->sbuf);
429     } else {
430 	return 0;
431     }
432 }
433 
source_buffer_load_file(GtkSourceBuffer * sbuf,int role,const char * fname)434 static int source_buffer_load_file (GtkSourceBuffer *sbuf,
435 				    int role,
436 				    const char *fname)
437 {
438     GtkTextBuffer *tbuf = GTK_TEXT_BUFFER(sbuf);
439     GtkTextIter iter;
440     gchar *buf = NULL;
441     gsize sz = 0;
442 
443     gtk_source_buffer_begin_not_undoable_action(sbuf);
444     gtk_text_buffer_set_text(tbuf, "", -1);
445     gtk_text_buffer_get_iter_at_offset(tbuf, &iter, 0);
446 
447     gretl_file_get_contents(fname, &buf, &sz);
448 
449     if (buf != NULL) {
450 	gchar *trbuf = NULL;
451 
452 	if (!g_utf8_validate(buf, -1, NULL)) {
453 	    trbuf = my_locale_to_utf8(buf);
454 	    if (trbuf != NULL) {
455 		gtk_text_buffer_insert(tbuf, &iter, trbuf, -1);
456 		g_free(trbuf);
457 	    }
458 	} else {
459 	    gtk_text_buffer_insert(tbuf, &iter, buf, -1);
460 	}
461 	g_free(buf);
462     }
463 
464     gtk_source_buffer_end_not_undoable_action(sbuf);
465     gtk_text_buffer_set_modified(tbuf, role == EDIT_PKG_SAMPLE);
466 
467     /* move cursor to the beginning */
468     gtk_text_buffer_get_start_iter(tbuf, &iter);
469     gtk_text_buffer_place_cursor(tbuf, &iter);
470 
471     return 0;
472 }
473 
source_buffer_load_buf(GtkSourceBuffer * sbuf,const char * buf)474 static int source_buffer_load_buf (GtkSourceBuffer *sbuf, const char *buf)
475 {
476     GtkTextBuffer *tbuf = GTK_TEXT_BUFFER(sbuf);
477     GtkTextIter iter;
478 
479     gtk_source_buffer_begin_not_undoable_action(sbuf);
480     gtk_text_buffer_set_text(tbuf, buf, -1);
481     gtk_source_buffer_end_not_undoable_action(sbuf);
482     gtk_text_buffer_set_modified(tbuf, FALSE);
483 
484     /* move cursor to the beginning */
485     gtk_text_buffer_get_start_iter(tbuf, &iter);
486     gtk_text_buffer_place_cursor(tbuf, &iter);
487 
488     return 0;
489 }
490 
sourceview_apply_language(windata_t * vwin)491 static void sourceview_apply_language (windata_t *vwin)
492 {
493     GtkSourceLanguageManager *lm;
494     GtkSourceLanguage *lang = NULL;
495     const char *id = NULL;
496 
497     lm = g_object_get_data(G_OBJECT(vwin->sbuf), "languages-manager");
498 
499     if (lm == NULL) {
500 	return;
501     }
502 
503     if (vwin->role == EDIT_GP) {
504 	id = "gnuplot";
505     } else if (vwin->role == EDIT_R) {
506 	id = "r";
507     } else if (vwin->role == EDIT_OX) {
508 	id = "cpp";
509     } else if (vwin->role == EDIT_OCTAVE) {
510 	id = "octave";
511     } else if (vwin->role == EDIT_PYTHON) {
512 	id = "python";
513     } else if (vwin->role == EDIT_JULIA) {
514 	id = "julia";
515     } else if (vwin->role == EDIT_STATA) {
516 	id = "stata";
517     } else if (vwin->role == EDIT_DYNARE) {
518 	id = "cpp";
519     } else if (vwin->role == EDIT_LPSOLVE) {
520 	id = "lpsolve";
521     } else if (vwin->role == EDIT_SPEC) {
522 	id = "gfnspec";
523     } else {
524 	id = "gretl";
525     }
526 
527     lang = gtk_source_language_manager_get_language(lm, id);
528 
529     if (lang == NULL) {
530 	const gchar * const *S = gtk_source_language_manager_get_search_path(lm);
531 
532 	fprintf(stderr, "*** gtksourceview: lang is NULL for id='%s'\n", id);
533 	if (S != NULL) {
534 	    fprintf(stderr, "the gtksourceview search path:\n");
535 	    while (*S != NULL) {
536 		fprintf(stderr, " %s\n", *S);
537 		S++;
538 	    }
539 	} else {
540 	    fprintf(stderr, "the gtksourceview search path is NULL!\n");
541 	}
542     } else {
543 	gtk_source_buffer_set_language(vwin->sbuf, lang);
544     }
545 }
546 
547 #define SV_PRINT_DEBUG 0
548 
549 #if SV_PRINT_DEBUG
550 
show_print_context(GtkPrintContext * context)551 static void show_print_context (GtkPrintContext *context)
552 {
553     gdouble x = gtk_print_context_get_width(context);
554     gdouble y = gtk_print_context_get_height(context);
555     GtkPageSetup *psu;
556 
557     fprintf(stderr, "begin_print: context pixel size: %g x %g\n", x, y);
558     x = gtk_print_context_get_dpi_x(context);
559     y = gtk_print_context_get_dpi_y(context);
560     fprintf(stderr, "  context dpi: %g, %g\n", x, y);
561 
562     psu = gtk_print_context_get_page_setup(context);
563     if (psu != NULL) {
564 	x = gtk_page_setup_get_paper_width(psu, GTK_UNIT_INCH);
565 	y = gtk_page_setup_get_paper_width(psu, GTK_UNIT_POINTS);
566 	fprintf(stderr, "  paper width: %g in, %g points\n", x, y);
567 	x = gtk_page_setup_get_paper_height(psu, GTK_UNIT_INCH);
568 	y = gtk_page_setup_get_paper_height(psu, GTK_UNIT_POINTS);
569 	fprintf(stderr, "  paper height: %g in, %g points\n", x, y);
570     }
571 }
572 
573 #endif
574 
begin_print(GtkPrintOperation * operation,GtkPrintContext * context,gpointer data)575 static void begin_print (GtkPrintOperation *operation,
576                          GtkPrintContext *context,
577                          gpointer data)
578 {
579     GtkSourcePrintCompositor *comp;
580     int n_pages;
581 
582     comp = GTK_SOURCE_PRINT_COMPOSITOR(data);
583 
584 #if SV_PRINT_DEBUG
585     GtkPrintSettings *st = gtk_print_operation_get_print_settings(operation);
586     if (st != NULL) {
587 	int rx = gtk_print_settings_get_resolution_x(st);
588 	int ry = gtk_print_settings_get_resolution_y(st);
589 
590         fprintf(stderr, "settings: resolution %d,%d\n", rx, ry);
591     }
592     show_print_context(context);
593 #endif
594 
595     while (!gtk_source_print_compositor_paginate(comp, context));
596 
597     n_pages = gtk_source_print_compositor_get_n_pages(comp);
598     gtk_print_operation_set_n_pages(operation, n_pages);
599 }
600 
draw_page(GtkPrintOperation * operation,GtkPrintContext * context,gint page_nr,gpointer data)601 static void draw_page (GtkPrintOperation *operation,
602 		       GtkPrintContext *context,
603 		       gint page_nr,
604 		       gpointer data)
605 {
606     GtkSourcePrintCompositor *comp =
607 	GTK_SOURCE_PRINT_COMPOSITOR(data);
608 
609     gtk_source_print_compositor_draw_page(comp, context,
610 					  page_nr);
611 }
612 
sourceview_print(windata_t * vwin)613 void sourceview_print (windata_t *vwin)
614 {
615     GtkSourceView *view = GTK_SOURCE_VIEW(vwin->text);
616     GtkSourcePrintCompositor *comp;
617     GtkPrintOperation *print;
618     GtkPrintOperationResult res;
619     GtkWidget *mainwin;
620     GError *error = NULL;
621 
622     comp = gtk_source_print_compositor_new_from_view(view);
623     print = gtk_print_operation_new();
624 
625 #ifdef G_OS_WIN32
626     /* the units are wacky if we don't set this */
627     gtk_print_operation_set_unit(print, GTK_UNIT_POINTS);
628 #endif
629 
630     gtk_source_print_compositor_set_right_margin(comp, 60, GTK_UNIT_POINTS);
631     gtk_source_print_compositor_set_left_margin(comp, 60, GTK_UNIT_POINTS);
632     gtk_source_print_compositor_set_top_margin(comp, 54, GTK_UNIT_POINTS);
633     gtk_source_print_compositor_set_bottom_margin(comp, 72, GTK_UNIT_POINTS);
634     gtk_source_print_compositor_set_wrap_mode(comp, GTK_WRAP_WORD);
635     gtk_source_print_compositor_set_body_font_name(comp, "Monospace 9");
636 
637     g_signal_connect(G_OBJECT(print), "begin_print", G_CALLBACK(begin_print), comp);
638     g_signal_connect(G_OBJECT(print), "draw_page", G_CALLBACK(draw_page), comp);
639 
640     mainwin = vwin_toplevel(vwin);
641     res = gtk_print_operation_run(print,
642 				  GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
643 				  GTK_WINDOW(mainwin),
644 				  &error);
645 
646     if (res == GTK_PRINT_OPERATION_RESULT_ERROR) {
647 	GtkWidget *dlg;
648 
649 	dlg = gtk_message_dialog_new(GTK_WINDOW(mainwin),
650 				     GTK_DIALOG_DESTROY_WITH_PARENT,
651 				     GTK_MESSAGE_ERROR,
652 				     GTK_BUTTONS_CLOSE,
653 				     "Error printing file:\n%s",
654 				     error->message);
655 	g_signal_connect(G_OBJECT(dlg), "response",
656 			 G_CALLBACK(gtk_widget_destroy), NULL);
657 	gtk_widget_show(dlg);
658 	g_error_free(error);
659     } else if (res == GTK_PRINT_OPERATION_RESULT_APPLY) {
660 	; /* OK: maybe save the settings? */
661     }
662 
663     if (print != NULL) {
664 	g_object_unref(print);
665     }
666 }
667 
sourceview_insert_file(windata_t * vwin,const char * fname)668 void sourceview_insert_file (windata_t *vwin, const char *fname)
669 {
670     sourceview_apply_language(vwin);
671     source_buffer_load_file(vwin->sbuf, vwin->role, fname);
672 }
673 
sourceview_insert_buffer(windata_t * vwin,const char * buf)674 void sourceview_insert_buffer (windata_t *vwin, const char *buf)
675 {
676     sourceview_apply_language(vwin);
677     source_buffer_load_buf(vwin->sbuf, buf);
678 }
679 
set_source_tabs(GtkWidget * w,int cw)680 static void set_source_tabs (GtkWidget *w, int cw)
681 {
682     static PangoTabArray *ta;
683 
684     if (ta == NULL) {
685 	int tabw = tabwidth * cw;
686 	gint i, loc = tabw;
687 
688 	ta = pango_tab_array_new(10, TRUE);
689 	for (i=0; i<10; i++) {
690 	    pango_tab_array_set_tab(ta, i, PANGO_TAB_LEFT, loc);
691 	    loc += tabw;
692 	}
693     }
694 
695     gtk_text_view_set_tabs(GTK_TEXT_VIEW(w), ta);
696 }
697 
698 #define tabkey(k) (k == GDK_Tab || \
699 		   k == GDK_ISO_Left_Tab || \
700 		   k == GDK_KP_Tab)
701 
702 /* Special keystrokes in native script window: Ctrl-Return sends the
703    current line for execution; Ctrl-R sends the whole script for
704    execution (keyboard equivalent of the "execute" button);
705    Ctrl-I does auto-indentation.
706 */
707 
script_key_handler(GtkWidget * w,GdkEvent * event,windata_t * vwin)708 static gint script_key_handler (GtkWidget *w,
709 				GdkEvent *event,
710 				windata_t *vwin)
711 {
712     guint keyval = ((GdkEventKey *) event)->keyval;
713     guint state = ((GdkEventKey *) event)->state;
714     gboolean ret = FALSE;
715 
716 #if KDEBUG
717     fprintf(stderr, "HERE script_key_handler (keyval %u, %s)\n",
718 	    keyval, gdk_keyval_name(keyval));
719 #endif
720 
721     if (state & GDK_CONTROL_MASK) {
722 	if (keyval == GDK_R) {
723 	    run_script_silent(w, vwin);
724 	    ret = TRUE;
725 	} else if (keyval == GDK_r) {
726 	    do_run_script(w, vwin);
727 	    ret = TRUE;
728 	} else if (keyval == GDK_Return) {
729 	    gchar *str = textview_get_current_line_with_newline(w);
730 
731 	    if (str != NULL) {
732 		if (!string_is_blank(str)) {
733 		    run_script_fragment(vwin, str);
734 		}
735 		g_free(str);
736 	    }
737 	    ret = TRUE;
738 	} else if (keyval == GDK_i) {
739 	    auto_indent_script(w, vwin);
740 	    ret = TRUE;
741 	}
742     } else if (keyval == GDK_F1) {
743 	set_window_help_active(vwin);
744 	interactive_script_help(NULL, NULL, vwin);
745 	ret = TRUE;
746     } else if (editing_hansl(vwin->role)) {
747 	if (keyval == GDK_Return) {
748 	    ret = script_electric_enter(vwin);
749 	} else if (tabkey(keyval)) {
750 #if TABDEBUG
751 	    fprintf(stderr, "*** calling script_tab_handler ***\n");
752 #endif
753 	    ret = script_tab_handler(vwin, event);
754 	} else if (script_auto_bracket && lbracket(keyval)) {
755 	    ret = script_bracket_handler(vwin, keyval);
756 	}
757     }
758 
759     return ret;
760 }
761 
762 static gint
foreign_script_key_handler(GtkWidget * w,GdkEvent * event,windata_t * vwin)763 foreign_script_key_handler (GtkWidget *w, GdkEvent *event, windata_t *vwin)
764 {
765     guint keyval = ((GdkEventKey *) event)->keyval;
766     gboolean ret = FALSE;
767 
768     if (((GdkEventKey *) event)->state & GDK_CONTROL_MASK) {
769 	if (keyval == GDK_r)  {
770 	    do_run_script(w, vwin);
771 	    ret = TRUE;
772 	}
773     }
774 
775     return ret;
776 }
777 
778 #ifdef PKGBUILD
779 
780 # ifdef G_OS_WIN32
781 
ensure_utf8_path(gchar * path)782 static gchar *ensure_utf8_path (gchar *path)
783 {
784     if (!g_utf8_validate(path, -1, NULL)) {
785 	gchar *tmp;
786 	gsize bytes;
787 
788 	tmp = g_locale_to_utf8(path, -1, NULL, &bytes, NULL);
789 	if (tmp != NULL) {
790 	    g_free(path);
791 	    path = tmp;
792 	}
793     }
794 
795     return path;
796 }
797 
798 # endif
799 
800 /* Packages for Windows and OS X: gtksourceview needs to
801    be told where to find its language-specs and style
802    files: these live under gtksourceview inside the package.
803 
804    On Windows we need to ensure that the "set_search_path"
805    functions are fed a UTF-8 path, since gtksourceview uses
806    g_open() internally and the Glib filename encoding is
807    always UTF-8 on Windows.
808 */
809 
ensure_sourceview_path(GtkSourceLanguageManager * lm)810 static void ensure_sourceview_path (GtkSourceLanguageManager *lm)
811 {
812     static int done;
813 
814     if (!done && lm == NULL) {
815 	lm = gtk_source_language_manager_get_default();
816     }
817 
818     if (!done && lm != NULL) {
819 	GtkSourceStyleSchemeManager *mgr;
820 	gchar *dirs[2] = {NULL, NULL};
821 
822 	dirs[0] = g_strdup_printf("%sgtksourceview", gretl_home());
823 # ifdef G_OS_WIN32
824 	dirs[0] = ensure_utf8_path(dirs[0]);
825 # endif
826 	gtk_source_language_manager_set_search_path(lm, dirs);
827 
828 	mgr = gtk_source_style_scheme_manager_get_default();
829 	gtk_source_style_scheme_manager_set_search_path(mgr, dirs);
830 	gtk_source_style_scheme_manager_force_rescan(mgr);
831 
832 	g_free(dirs[0]);
833 	done = 1;
834     }
835 }
836 
837 #else /* not PKGBUILD */
838 
839 /* gtksourceview needs to be told to search both its own "native"
840    paths and @prefix/share/gretl/gtksourceview for both language
841    file and style files
842 */
843 
ensure_sourceview_path(GtkSourceLanguageManager * lm)844 static void ensure_sourceview_path (GtkSourceLanguageManager *lm)
845 {
846     static int done;
847 
848     if (!done && lm == NULL) {
849 	lm = gtk_source_language_manager_get_default();
850     }
851 
852     if (!done && lm != NULL) {
853 	GtkSourceStyleSchemeManager *mgr;
854 	gchar *dirs[3] = {NULL, NULL, NULL};
855 
856 	/* languages: we need to set path, can't just append */
857 	dirs[0] = g_strdup_printf("%sgtksourceview", gretl_home());
858 #if GTKSOURCEVIEW_VERSION > 3
859 	dirs[1] = g_strdup_printf("%s/share/gtksourceview-%d/language-specs",
860 				  SVPREFIX, GTKSOURCEVIEW_VERSION);
861 #else
862 	dirs[1] = g_strdup_printf("%s/share/gtksourceview-%d.0/language-specs",
863 				  SVPREFIX, GTKSOURCEVIEW_VERSION);
864 #endif
865 	gtk_source_language_manager_set_search_path(lm, dirs);
866 
867 	/* styles: we can just append to the default path */
868 	mgr = gtk_source_style_scheme_manager_get_default();
869 	gtk_source_style_scheme_manager_append_search_path(mgr, dirs[0]);
870 	gtk_source_style_scheme_manager_force_rescan(mgr);
871 
872 	g_free(dirs[0]);
873 	g_free(dirs[1]);
874 
875 	done = 1;
876     }
877 }
878 
879 #endif
880 
set_console_output_style(GtkSourceBuffer * sbuf,GtkSourceStyleScheme * scheme)881 static void set_console_output_style (GtkSourceBuffer *sbuf,
882 				      GtkSourceStyleScheme *scheme)
883 {
884     GtkSourceStyle *style = NULL;
885     GtkTextTag *tag = NULL;
886     GtkTextTagTable *tt;
887     int done = 0;
888 
889     tt = gtk_text_buffer_get_tag_table(GTK_TEXT_BUFFER(sbuf));
890     if (tt != NULL) {
891 	tag = gtk_text_tag_table_lookup(tt, "output");
892     }
893     if (tag != NULL) {
894 	style = gtk_source_style_scheme_get_style(scheme, "text");
895     }
896     if (style != NULL) {
897 	gchar *fg = NULL, *bg = NULL;
898 
899 	g_object_get(style, "foreground", &fg, "background", &bg, NULL);
900 	if (fg != NULL && bg != NULL) {
901 	    g_object_set(tag, "foreground", fg, "background", bg, NULL);
902 	    done = 1;
903 	}
904 	g_free(fg);
905 	g_free(bg);
906     }
907     if (tag != NULL && !done) {
908 	/* fallback */
909 	g_object_set(tag, "foreground", "black", "background", "white", NULL);
910     }
911 }
912 
set_style_for_buffer(GtkSourceBuffer * sbuf,const char * id,int role)913 static void set_style_for_buffer (GtkSourceBuffer *sbuf,
914 				  const char *id,
915 				  int role)
916 {
917     GtkSourceStyleSchemeManager *mgr;
918     GtkSourceStyleScheme *scheme;
919 
920     if (id == NULL || *id == '\0') {
921 	return;
922     }
923 
924     mgr = gtk_source_style_scheme_manager_get_default();
925     scheme = gtk_source_style_scheme_manager_get_scheme(mgr, id);
926     if (scheme != NULL) {
927 	gtk_source_buffer_set_style_scheme(sbuf, scheme);
928 	if (role == CONSOLE) {
929 	    set_console_output_style(sbuf, scheme);
930 	}
931     }
932 }
933 
set_style_for_textview(GtkWidget * text,const char * id)934 void set_style_for_textview (GtkWidget *text, const char *id)
935 {
936     GtkSourceStyleSchemeManager *mgr;
937     GtkSourceStyleScheme *scheme;
938     GtkTextBuffer *tbuf;
939 
940     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
941     mgr = gtk_source_style_scheme_manager_get_default();
942     scheme = gtk_source_style_scheme_manager_get_scheme(mgr, id);
943     if (scheme != NULL) {
944 	gtk_source_buffer_set_style_scheme(GTK_SOURCE_BUFFER(tbuf), scheme);
945     }
946 }
947 
948 #define gretl_script_role(r) (r == EDIT_HANSL || \
949 			      r == VIEW_SCRIPT || \
950 			      r == EDIT_PKG_CODE || \
951 			      r == EDIT_PKG_SAMPLE || \
952 			      r == VIEW_PKG_SAMPLE)
953 
create_source(windata_t * vwin,int hsize,int vsize,gboolean editable)954 void create_source (windata_t *vwin, int hsize, int vsize,
955 		    gboolean editable)
956 {
957     GtkSourceLanguageManager *lm = NULL;
958     GtkSourceBuffer *sbuf;
959     GtkTextView *view;
960     int cw;
961 
962     if (textview_use_highlighting(vwin->role)) {
963 	lm = gtk_source_language_manager_get_default();
964 	ensure_sourceview_path(lm);
965     }
966 
967     sbuf = GTK_SOURCE_BUFFER(gtk_source_buffer_new(NULL));
968     if (lm != NULL) {
969 	g_object_set_data(G_OBJECT(sbuf), "languages-manager", lm);
970     }
971     gtk_source_buffer_set_highlight_matching_brackets(sbuf, TRUE);
972 
973     vwin->text = gtk_source_view_new_with_buffer(sbuf);
974     vwin->sbuf = sbuf;
975 
976 #ifdef HAVE_GTKSV_COMPLETION
977     if (editing_hansl(vwin->role) || vwin->role == CONSOLE) {
978 	set_sv_completion(vwin);
979     }
980 #endif
981 
982     view = GTK_TEXT_VIEW(vwin->text);
983     gtk_text_view_set_wrap_mode(view, GTK_WRAP_NONE);
984     gtk_text_view_set_left_margin(view, 4);
985     gtk_text_view_set_right_margin(view, 4);
986 
987     gtk_widget_modify_font(GTK_WIDGET(vwin->text), fixed_font);
988 
989     cw = get_char_width(vwin->text);
990     set_source_tabs(vwin->text, cw);
991 
992     if (hsize > 0) {
993 	hsize *= cw;
994 	hsize += 48; /* ?? */
995     }
996 
997     if (!(vwin->flags & VWIN_SWALLOW) && hsize > 0 && vsize > 0) {
998 	GtkWidget *vmain = vwin_toplevel(vwin);
999 
1000 	if (window_is_tab(vwin)) {
1001 	    vsize += 15;
1002 	}
1003 	if (vsize < 0.62 * hsize) {
1004 	    /* approx golden ratio */
1005 	    vsize = 0.62 * hsize;
1006 	}
1007 	gtk_window_set_default_size(GTK_WINDOW(vmain), hsize, vsize);
1008     }
1009 
1010     gtk_text_view_set_editable(view, editable);
1011     gtk_text_view_set_cursor_visible(view, editable);
1012 
1013     gtk_source_view_set_show_line_numbers(GTK_SOURCE_VIEW(vwin->text),
1014 					  script_line_numbers);
1015 
1016     if (lm != NULL) {
1017 	set_style_for_buffer(sbuf, get_sourceview_style(), vwin->role);
1018     }
1019 
1020     if (!(vwin->flags & WVIN_KEY_SIGNAL_SET)) {
1021 	g_signal_connect(G_OBJECT(vwin->text), "key-press-event",
1022 			 G_CALLBACK(catch_viewer_key), vwin);
1023     }
1024 
1025     if (gretl_script_role(vwin->role)) {
1026 	g_signal_connect(G_OBJECT(vwin->text), "key-press-event",
1027 			 G_CALLBACK(script_key_handler), vwin);
1028 	g_signal_connect(G_OBJECT(vwin->text), "button-press-event",
1029 			 G_CALLBACK(script_popup_handler),
1030 			 vwin);
1031 	g_signal_connect(G_OBJECT(vwin->text), "button-release-event",
1032 			 G_CALLBACK(interactive_script_help), vwin);
1033     } else if (foreign_script_role(vwin->role)) {
1034 	g_signal_connect(G_OBJECT(vwin->text), "key-press-event",
1035 			 G_CALLBACK(foreign_script_key_handler), vwin);
1036 	g_signal_connect(G_OBJECT(vwin->text), "button-press-event",
1037 			 G_CALLBACK(script_popup_handler),
1038 			 vwin);
1039     } else if (vwin->role == VIEW_LOG) {
1040 	g_signal_connect(G_OBJECT(vwin->text), "button-release-event",
1041 			 G_CALLBACK(interactive_script_help), vwin);
1042     }
1043 }
1044 
1045 /* Manufacture a little sampler sourceview for use in the
1046    Editor tab of the gretl preferences dialog
1047 */
1048 
create_sample_source(const char * style)1049 GtkWidget *create_sample_source (const char *style)
1050 {
1051     GtkSourceLanguageManager *lm;
1052     GtkSourceLanguage *lang;
1053     GtkSourceBuffer *sbuf;
1054     GtkTextView *view;
1055     GtkWidget *text;
1056 
1057     const gchar *sample =
1058 	"# sample of highlighting style\n"
1059 	"open wages.gdt\n"
1060 	"series l_wage = log(wage)\n"
1061 	"ols l_wage 0 male school exper --robust\n";
1062 
1063     lm = gtk_source_language_manager_get_default();
1064     if (lm == NULL) {
1065 	return NULL;
1066     }
1067 
1068     ensure_sourceview_path(lm);
1069 
1070     lang = gtk_source_language_manager_get_language(lm, "gretl");
1071     if (lang == NULL) {
1072 	return NULL;
1073     }
1074 
1075     sbuf = GTK_SOURCE_BUFFER(gtk_source_buffer_new_with_language(lang));
1076     text = gtk_source_view_new_with_buffer(sbuf);
1077     view = GTK_TEXT_VIEW(text);
1078 
1079     gtk_text_view_set_left_margin(view, 4);
1080     gtk_text_view_set_right_margin(view, 4);
1081     gtk_widget_modify_font(text, fixed_font);
1082     gtk_text_view_set_editable(view, FALSE);
1083     gtk_text_view_set_cursor_visible(view, FALSE);
1084 
1085     gtk_text_buffer_set_text(GTK_TEXT_BUFFER(sbuf), sample, -1);
1086     gtk_source_buffer_set_highlight_syntax(sbuf, TRUE);
1087     set_style_for_buffer(sbuf, style, 0);
1088 
1089     return text;
1090 }
1091 
1092 /* callback after changes made in preferences dialog */
1093 
update_script_editor_options(windata_t * vwin)1094 void update_script_editor_options (windata_t *vwin)
1095 {
1096     ensure_sourceview_path(NULL);
1097 
1098     if (vwin->role != CONSOLE) {
1099 	gtk_source_view_set_show_line_numbers(GTK_SOURCE_VIEW(vwin->text),
1100 					      script_line_numbers);
1101     }
1102     set_style_for_buffer(vwin->sbuf, get_sourceview_style(), vwin->role);
1103 
1104 #ifdef HAVE_GTKSV_COMPLETION
1105     if (vwin->role == CONSOLE || editing_hansl(vwin->role)) {
1106 	set_sv_completion(vwin);
1107     }
1108 #endif
1109 }
1110 
text_change_size(windata_t * vwin,int delta)1111 static int text_change_size (windata_t *vwin, int delta)
1112 {
1113     static PangoFontDescription *hpf;
1114     static int fsize0;
1115     int fsize;
1116 
1117     if (hpf == NULL) {
1118 	hpf = pango_font_description_copy(fixed_font);
1119 	fsize0 = pango_font_description_get_size(hpf) / PANGO_SCALE;
1120     }
1121 
1122     if (vwin == NULL) {
1123 	return fsize0;
1124     }
1125 
1126     fsize = widget_get_int(vwin->text, "fsize");
1127     if (fsize == 0) {
1128 	fsize = fsize0;
1129     }
1130 
1131     fsize += delta;
1132 
1133     pango_font_description_set_size(hpf, fsize * PANGO_SCALE);
1134     gtk_widget_modify_font(vwin->text, hpf);
1135     widget_set_int(vwin->text, "fsize", fsize);
1136 
1137     return fsize;
1138 }
1139 
text_larger(GtkWidget * w,gpointer data)1140 void text_larger (GtkWidget *w, gpointer data)
1141 {
1142     text_change_size((windata_t *) data, 1);
1143 }
1144 
text_smaller(GtkWidget * w,gpointer data)1145 void text_smaller (GtkWidget *w, gpointer data)
1146 {
1147     text_change_size((windata_t *) data, -1);
1148 }
1149 
1150 #ifdef OS_OSX
1151 # define helpfont "Geneva"
1152 #else
1153 # define helpfont "sans"
1154 #endif
1155 
gretl_tags_new(void)1156 static GtkTextTagTable *gretl_tags_new (void)
1157 {
1158     GtkTextTagTable *table;
1159     GtkTextTag *tag;
1160     int bigsize;
1161     int smallsize;
1162 
1163     bigsize = 15 * PANGO_SCALE;
1164     smallsize = 8 * PANGO_SCALE;
1165 
1166     table = gtk_text_tag_table_new();
1167 
1168     tag = gtk_text_tag_new("bluetext");
1169     g_object_set(tag, "foreground", "blue", NULL);
1170     gtk_text_tag_table_add(table, tag);
1171 
1172     tag = gtk_text_tag_new("redtext");
1173     g_object_set(tag, "foreground", "red", NULL);
1174     gtk_text_tag_table_add(table, tag);
1175 
1176     tag = gtk_text_tag_new("greentext");
1177     g_object_set(tag, "foreground", "green", NULL);
1178     gtk_text_tag_table_add(table, tag);
1179 
1180     tag = gtk_text_tag_new("title");
1181     g_object_set(tag, "justification", GTK_JUSTIFY_CENTER,
1182 		 "pixels_above_lines", 15,
1183 		 "family", helpfont,
1184 		 "size", bigsize, NULL);
1185     gtk_text_tag_table_add(table, tag);
1186 
1187     tag = gtk_text_tag_new("heading");
1188     g_object_set(tag, "family", helpfont,
1189 		 "weight", PANGO_WEIGHT_BOLD,
1190 		 NULL);
1191     gtk_text_tag_table_add(table, tag);
1192 
1193     tag = gtk_text_tag_new("grayhead");
1194     g_object_set(tag, "family", helpfont,
1195 		 "foreground", "gray",
1196 		 "pixels_below_lines", 5,
1197 		 NULL);
1198     gtk_text_tag_table_add(table, tag);
1199 
1200     tag = gtk_text_tag_new("italic");
1201     g_object_set(tag, "family", helpfont,
1202 		 "style", PANGO_STYLE_ITALIC,
1203 		 NULL);
1204     gtk_text_tag_table_add(table, tag);
1205 
1206     tag = gtk_text_tag_new("replaceable");
1207     g_object_set(tag, "family", helpfont,
1208 		 "style", PANGO_STYLE_ITALIC,
1209 		 NULL);
1210     gtk_text_tag_table_add(table, tag);
1211 
1212     tag = gtk_text_tag_new("superscript");
1213     g_object_set(tag, "family", helpfont,
1214 		 "style", PANGO_STYLE_NORMAL,
1215 		 "rise", 4 * PANGO_SCALE,
1216 		 "size", smallsize,
1217 		 NULL);
1218     gtk_text_tag_table_add(table, tag);
1219 
1220     tag = gtk_text_tag_new("subscript");
1221     g_object_set(tag, "family", helpfont,
1222 		 "style", PANGO_STYLE_ITALIC,
1223 		 "rise", -3 * PANGO_SCALE,
1224 		 "size", smallsize,
1225 		 NULL);
1226     gtk_text_tag_table_add(table, tag);
1227 
1228     tag = gtk_text_tag_new("subscript-numeral");
1229     g_object_set(tag, "family", helpfont,
1230 		 "style", PANGO_STYLE_NORMAL,
1231 		 "rise", -3 * PANGO_SCALE,
1232 		 "size", smallsize,
1233 		 NULL);
1234     gtk_text_tag_table_add(table, tag);
1235 
1236     tag = gtk_text_tag_new("literal");
1237     g_object_set(tag, "family", "monospace", NULL);
1238     gtk_text_tag_table_add(table, tag);
1239 
1240     tag = gtk_text_tag_new("optflag");
1241     g_object_set(tag, "family", "monospace",
1242 		 "foreground", "#396d60", NULL);
1243     gtk_text_tag_table_add(table, tag);
1244 
1245     tag = gtk_text_tag_new("text");
1246     g_object_set(tag, "family", helpfont, NULL);
1247     gtk_text_tag_table_add(table, tag);
1248 
1249     tag = gtk_text_tag_new("indented");
1250     g_object_set(tag, "left_margin", 16, "indent", -12, NULL);
1251     gtk_text_tag_table_add(table, tag);
1252 
1253     tag = gtk_text_tag_new("code");
1254     g_object_set(tag, "family", "monospace",
1255 		 "paragraph-background", "#e6f3ff",
1256 		 NULL);
1257     gtk_text_tag_table_add(table, tag);
1258 
1259     tag = gtk_text_tag_new("mono");
1260     g_object_set(tag, "family", "monospace", NULL);
1261     gtk_text_tag_table_add(table, tag);
1262 
1263     return table;
1264 }
1265 
gretl_text_buf_new(void)1266 GtkTextBuffer *gretl_text_buf_new (void)
1267 {
1268     static GtkTextTagTable *tags = NULL;
1269     GtkTextBuffer *tbuf;
1270 
1271     if (tags == NULL) {
1272 	tags = gretl_tags_new();
1273     }
1274 
1275     tbuf = gtk_text_buffer_new(tags);
1276 
1277     return tbuf;
1278 }
1279 
1280 static void
real_textview_add_colorized(GtkWidget * view,const char * buf,int append,int trim)1281 real_textview_add_colorized (GtkWidget *view, const char *buf,
1282 			     int append, int trim)
1283 {
1284     GtkTextBuffer *tbuf;
1285     GtkTextIter iter;
1286     int nextcolor, thiscolor = PLAIN_TEXT;
1287     int in_comment = 0;
1288     char readbuf[4096];
1289     int i = 0;
1290 
1291     g_return_if_fail(GTK_IS_TEXT_VIEW(view));
1292 
1293     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
1294 
1295     if (append) {
1296 	gtk_text_buffer_get_end_iter(tbuf, &iter);
1297     } else {
1298 	gtk_text_buffer_get_iter_at_offset(tbuf, &iter, 0);
1299     }
1300 
1301     bufgets_init(buf);
1302 
1303     while (bufgets(readbuf, sizeof readbuf, buf)) {
1304 	if (trim && i++ < 2) {
1305 	    continue;
1306 	}
1307 
1308 	if (ends_with_backslash(readbuf)) {
1309 	    nextcolor = thiscolor;
1310 	} else {
1311 	    nextcolor = PLAIN_TEXT;
1312 	}
1313 
1314 	if (*readbuf == '#' || *readbuf == '?' ||
1315 	    *readbuf == '>' || in_comment) {
1316 	    thiscolor = BLUE_TEXT;
1317 	} else if (!strncmp(readbuf, "/*", 2)) {
1318 	    in_comment = 1;
1319 	    thiscolor = nextcolor = BLUE_TEXT;
1320 	}
1321 
1322 	if (strstr(readbuf, "*/")) {
1323 	    in_comment = 0;
1324 	    nextcolor = PLAIN_TEXT;
1325 	}
1326 
1327 	if (thiscolor == BLUE_TEXT) {
1328 	    gtk_text_buffer_insert_with_tags_by_name(tbuf, &iter,
1329 						     readbuf, -1,
1330 						     "bluetext", NULL);
1331 	} else {
1332 	    gtk_text_buffer_insert(tbuf, &iter, readbuf, -1);
1333 	}
1334 
1335 	thiscolor = nextcolor;
1336     }
1337 
1338     bufgets_finalize(buf);
1339 }
1340 
textview_set_text_colorized(GtkWidget * view,const char * buf)1341 void textview_set_text_colorized (GtkWidget *view, const char *buf)
1342 {
1343     real_textview_add_colorized(view, buf, 0, 0);
1344 }
1345 
textview_append_text_colorized(GtkWidget * view,const char * buf,int trim)1346 void textview_append_text_colorized (GtkWidget *view, const char *buf, int trim)
1347 {
1348     real_textview_add_colorized(view, buf, 1, trim);
1349 }
1350 
textview_set_text_report(GtkWidget * view,const char * buf)1351 void textview_set_text_report (GtkWidget *view, const char *buf)
1352 {
1353     GtkTextBuffer *tbuf;
1354     GtkTextIter iter;
1355     const char *p;
1356 
1357     /* plain text except for "<@ok>" represented as "OK" in
1358        green and "<@fail>" as "failed" in red
1359     */
1360 
1361     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
1362     gtk_text_buffer_get_iter_at_offset(tbuf, &iter, 0);
1363 
1364     while ((p = strstr(buf, "<@"))) {
1365 	gtk_text_buffer_insert(tbuf, &iter, buf, p - buf);
1366 	if (!strncmp(p, "<@ok>", 5)) {
1367 	    gtk_text_buffer_insert_with_tags_by_name(tbuf, &iter, _("OK"),
1368 						     -1, "greentext", NULL);
1369 	    buf = p + 5;
1370 	} else if (!strncmp(p, "<@fail>", 7)) {
1371 	    gtk_text_buffer_insert_with_tags_by_name(tbuf, &iter, _("failed"),
1372 						     -1, "redtext", NULL);
1373 	    buf = p + 7;
1374 	}
1375     }
1376 
1377     gtk_text_buffer_insert(tbuf, &iter, buf, -1);
1378 }
1379 
semicolon_pos(const char * s)1380 static const char *semicolon_pos (const char *s)
1381 {
1382     while (*s != '\0' && *s != '"') {
1383 	if (*s == ';') {
1384 	    return s;
1385 	}
1386 	s++;
1387     }
1388     return NULL;
1389 }
1390 
textview_set_text_dbsearch(windata_t * vwin,const char * buf)1391 void textview_set_text_dbsearch (windata_t *vwin, const char *buf)
1392 {
1393     GtkTextBuffer *tbuf;
1394     GtkTextTagTable *tab;
1395     GtkTextIter iter;
1396     GtkTextTag *tag;
1397     gchar *dsname = NULL;
1398     gchar *show = NULL;
1399     int page;
1400     const char *p, *q;
1401 
1402     /* plain text except for "<@dbn>" tags for links */
1403 
1404     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
1405     tab = gtk_text_buffer_get_tag_table(tbuf);
1406     gtk_text_buffer_get_iter_at_offset(tbuf, &iter, 0);
1407 
1408     while ((p = strstr(buf, "<@dbn"))) {
1409 	gtk_text_buffer_insert(tbuf, &iter, buf, p - buf);
1410 	p += 7;
1411 	q = semicolon_pos(p);
1412 	if (q != NULL) {
1413 	    /* should be show;tagname */
1414 	    page = DBS_PAGE;
1415 	    show = g_strndup(p, q-p);
1416 	    p = q + 1;
1417 	    q = strchr(p, '"');
1418 	    dsname = g_strndup(p, q-p);
1419 	} else {
1420 	    q = strchr(p, '"');
1421 	    dsname = g_strndup(p, q-p);
1422 	    if (!strcmp(dsname, "_NEXT_")) {
1423 		page = NEXT_PAGE;
1424 		show = g_strdup(_("Next results"));
1425 	    } else {
1426 		page = DBN_PAGE;
1427 		show = NULL;
1428 	    }
1429 	}
1430 	tag = gtk_text_tag_table_lookup(tab, dsname);
1431 	if (tag == NULL) {
1432 	    tag = gtk_text_buffer_create_tag(tbuf, dsname, "foreground", "blue",
1433 					     NULL);
1434 	    g_object_set_data(G_OBJECT(tag), "page", GINT_TO_POINTER(page));
1435 	}
1436 	gtk_text_buffer_insert_with_tags(tbuf, &iter,
1437 					 show == NULL ? dsname : show,
1438 					 -1, tag, NULL);
1439 	g_free(dsname);
1440 	if (show != NULL) {
1441 	    g_free(show);
1442 	}
1443 	buf = q + 2;
1444     }
1445 
1446     gtk_text_buffer_insert(tbuf, &iter, buf, -1);
1447 
1448     connect_link_signals(vwin);
1449 }
1450 
textview_delete_processing_message(GtkWidget * view)1451 void textview_delete_processing_message (GtkWidget *view)
1452 {
1453     GtkTextBuffer *tbuf;
1454     GtkTextMark *m0, *m1;
1455     GtkTextIter i0, i1;
1456 
1457     g_return_if_fail(GTK_IS_TEXT_VIEW(view));
1458 
1459     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
1460     m0 = gtk_text_buffer_get_mark(tbuf, "pstart");
1461     m1 = gtk_text_buffer_get_mark(tbuf, "pstop");
1462     if (m0 != NULL && m1 != NULL) {
1463 	gtk_text_buffer_get_iter_at_mark(tbuf, &i0, m0);
1464 	gtk_text_buffer_get_iter_at_mark(tbuf, &i1, m1);
1465 	gtk_text_buffer_delete(tbuf, &i0, &i1);
1466 	gtk_text_buffer_delete_mark(tbuf, m0);
1467 	gtk_text_buffer_delete_mark(tbuf, m1);
1468     }
1469 }
1470 
textview_add_processing_message(GtkWidget * view)1471 void textview_add_processing_message (GtkWidget *view)
1472 {
1473     const char *msg = N_("processing...\n");
1474     GtkTextBuffer *tbuf;
1475     GtkTextIter iter;
1476     GtkTextMark *mstop;
1477 
1478     g_return_if_fail(GTK_IS_TEXT_VIEW(view));
1479 
1480     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
1481     gtk_text_buffer_get_end_iter(tbuf, &iter);
1482     gtk_text_buffer_create_mark(tbuf, "pstart", &iter, TRUE);
1483     gtk_text_buffer_insert_with_tags_by_name(tbuf, &iter,
1484 					     _(msg), -1,
1485 					     "redtext", NULL);
1486     mstop = gtk_text_buffer_create_mark(tbuf, "pstop", &iter, TRUE);
1487     gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(view), mstop, 0.0,
1488 				 FALSE, 0, 0);
1489 }
1490 
textview_append_text(GtkWidget * view,const char * text)1491 void textview_append_text (GtkWidget *view, const char *text)
1492 {
1493     GtkTextBuffer *tbuf;
1494     GtkTextIter iter;
1495 
1496     g_return_if_fail(GTK_IS_TEXT_VIEW(view));
1497 
1498     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
1499     gtk_text_buffer_get_end_iter(tbuf, &iter);
1500     gtk_text_buffer_insert(tbuf, &iter, text, -1);
1501 }
1502 
textview_insert_text(GtkWidget * view,const char * text)1503 void textview_insert_text (GtkWidget *view, const char *text)
1504 {
1505     GtkTextBuffer *tbuf;
1506 
1507     g_return_if_fail(GTK_IS_TEXT_VIEW(view));
1508 
1509     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
1510     gtk_text_buffer_insert_at_cursor(tbuf, text, -1);
1511 }
1512 
textview_clear_text(GtkWidget * view)1513 void textview_clear_text (GtkWidget *view)
1514 {
1515     GtkTextBuffer *tbuf;
1516 
1517     g_return_if_fail(GTK_IS_TEXT_VIEW(view));
1518 
1519     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
1520     gtk_text_buffer_set_text (tbuf, "", -1);
1521 }
1522 
textview_insert_file(windata_t * vwin,const char * fname)1523 void textview_insert_file (windata_t *vwin, const char *fname)
1524 {
1525     FILE *fp;
1526     GtkTextBuffer *tbuf;
1527     GtkTextIter iter;
1528     int thiscolor, nextcolor;
1529     char fline[MAXSTR], *chunk;
1530     int links = 0;
1531     int i = 0;
1532 
1533     g_return_if_fail(GTK_IS_TEXT_VIEW(vwin->text));
1534 
1535     fp = gretl_fopen(fname, "r");
1536     if (fp == NULL) {
1537 	file_read_errbox(fname);
1538 	return;
1539     }
1540 
1541     thiscolor = nextcolor = PLAIN_TEXT;
1542 
1543     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
1544     gtk_text_buffer_get_iter_at_offset(tbuf, &iter, 0);
1545 
1546     memset(fline, 0, sizeof fline);
1547 
1548     while (fgets(fline, sizeof fline, fp)) {
1549 	if (!g_utf8_validate(fline, -1, NULL)) {
1550 	    if (i == 0) {
1551 		chunk = my_locale_to_utf8(fline);
1552 		i++;
1553 	    } else {
1554 		chunk = my_locale_to_utf8_next(fline);
1555 	    }
1556 	    if (chunk == NULL) {
1557 		continue;
1558 	    }
1559 	} else {
1560 	    chunk = fline;
1561 	}
1562 
1563 	nextcolor = PLAIN_TEXT;
1564 
1565 	if (vwin->role == VIEW_DOC && strchr(chunk, '<')) {
1566 	    if (!links) {
1567 		links = insert_text_with_markup(tbuf, &iter, chunk, vwin->role);
1568 	    } else {
1569 		insert_text_with_markup(tbuf, &iter, chunk, vwin->role);
1570 	    }
1571 	} else {
1572 	    if (vwin->role == SCRIPT_OUT && ends_with_backslash(chunk)) {
1573 		nextcolor = BLUE_TEXT;
1574 	    }
1575 
1576 	    if (*chunk == '?') {
1577 		thiscolor = (vwin->role == CONSOLE)? RED_TEXT : BLUE_TEXT;
1578 	    } else if (*chunk == '#') {
1579 		thiscolor = BLUE_TEXT;
1580 	    }
1581 
1582 	    switch (thiscolor) {
1583 	    case PLAIN_TEXT:
1584 		gtk_text_buffer_insert(tbuf, &iter, chunk, -1);
1585 		break;
1586 	    case BLUE_TEXT:
1587 		gtk_text_buffer_insert_with_tags_by_name(tbuf, &iter,
1588 							 chunk, -1,
1589 							 "bluetext", NULL);
1590 		break;
1591 	    case RED_TEXT:
1592 		gtk_text_buffer_insert_with_tags_by_name(tbuf, &iter,
1593 							 chunk, -1,
1594 							 "redtext", NULL);
1595 		break;
1596 	    }
1597 	}
1598 
1599 	if (chunk != fline) {
1600 	    g_free(chunk);
1601 	}
1602 
1603 	thiscolor = nextcolor;
1604 	memset(fline, 0, sizeof fline);
1605     }
1606 
1607     fclose(fp);
1608 
1609     if (links) {
1610 	connect_link_signals(vwin);
1611     }
1612 }
1613 
get_mnu_string(const char * key)1614 static gchar *get_mnu_string (const char *key)
1615 {
1616     const char *s;
1617     gchar *ret;
1618 
1619     if (!strcmp(key, "LocalGfn")) {
1620 	s = _("On _local machine...");
1621     } else if (!strcmp(key, "RemoteGfn")) {
1622 	s = _("On _server...");
1623     } else if (!strcmp(key, "Pkgbook")) {
1624 	s = _("_Function package guide");
1625     } else if (!strcmp(key, "SFAddons")) {
1626 	s = _("Check for _addons");
1627     } else if (!strcmp(key, "Registry")) {
1628 	s = _("Package registry");
1629     } else if (!strcmp(key, "gretlMPI")) {
1630 	s = _("gretl + MPI");
1631     } else if (!strcmp(key, "gretlSVM")) {
1632 	s = _("gretl + SVM");
1633     } else if (!strcmp(key, "SetSeed")) {
1634 	s = _("_Seed for random numbers");
1635     } else if (!strcmp(key, "gretlDBN")) {
1636 	s = _("dbnomics for gretl");
1637     } else if (!strcmp(key, "GeoplotDoc")) {
1638 	s = _("Creating maps");
1639     } else if (!strcmp(key, "LpsolveDoc")) {
1640 	s = _("Linear Programs");
1641     } else {
1642 	s = key;
1643     }
1644 
1645     ret = g_strdup(s);
1646 
1647     gretl_delchar('_', ret);
1648     gretl_delchar('.', ret);
1649 
1650     return ret;
1651 }
1652 
1653 #define TAGLEN 128
1654 
insert_link(GtkTextBuffer * tbuf,GtkTextIter * iter,const char * text,gint page,const char * indent)1655 static gboolean insert_link (GtkTextBuffer *tbuf, GtkTextIter *iter,
1656 			     const char *text, gint page,
1657 			     const char *indent)
1658 {
1659     GtkTextTagTable *tab = gtk_text_buffer_get_tag_table(tbuf);
1660     GtkTextTag *tag;
1661     gchar *show = NULL;
1662     gchar tagname[TAGLEN];
1663 
1664     if (page == GUIDE_PAGE) {
1665 	char *p = strrchr(text, '#');
1666 
1667 	if (p != NULL) {
1668 	    show = g_strndup(text, p - text);
1669 	    strcpy(tagname, p + 1);
1670 	} else {
1671 	    strcpy(tagname, "tag:guide");
1672 	}
1673     } else if (page == MNU_PAGE) {
1674 	show = get_mnu_string(text);
1675 	strcpy(tagname, text);
1676     } else if (page == SCRIPT_PAGE || page == EXT_PAGE) {
1677 	*tagname = '\0';
1678 	strncat(tagname, text, TAGLEN-1);
1679     } else if (page == PDF_PAGE) {
1680 	const char *p = path_last_slash_const(text);
1681 
1682 	*tagname = '\0';
1683 	strncat(tagname, text, TAGLEN-1);
1684 	if (p != NULL) {
1685 	    show = g_strdup(p + 1);
1686 	} else {
1687 	    show = g_strdup(text); /* OK? */
1688 	}
1689     } else if (page == BIB_PAGE) {
1690 	char *p = strrchr(text, ';');
1691 
1692 	if (p != NULL) {
1693 	    strcpy(tagname, p + 1);
1694 	    show = g_strndup(text, p - text);
1695 	} else {
1696 	    strcpy(tagname, text);
1697 	}
1698     } else {
1699 	sprintf(tagname, "tag:p%d", page);
1700     }
1701 
1702     tag = gtk_text_tag_table_lookup(tab, tagname);
1703 
1704     if (tag == NULL) {
1705 	if (page == GUIDE_PAGE || page == BIB_PAGE || page == MNU_PAGE) {
1706 	    tag = gtk_text_buffer_create_tag(tbuf, tagname, "foreground", "blue",
1707 					     "family", helpfont, NULL);
1708 	} else if (page == SCRIPT_PAGE || page == EXT_PAGE ||
1709 		   page == PDF_PAGE) {
1710 	    tag = gtk_text_buffer_create_tag(tbuf, tagname, "foreground", "blue",
1711 					     "family", "monospace", NULL);
1712 	} else if (indent != NULL) {
1713 	    tag = gtk_text_buffer_create_tag(tbuf, tagname, "foreground", "blue",
1714 					     "left_margin", 30, NULL);
1715 	} else {
1716 	    tag = gtk_text_buffer_create_tag(tbuf, tagname, "foreground", "blue", NULL);
1717 	}
1718 	g_object_set_data(G_OBJECT(tag), "page", GINT_TO_POINTER(page));
1719     }
1720 
1721     if (show != NULL) {
1722 	gtk_text_buffer_insert_with_tags(tbuf, iter, show, -1, tag, NULL);
1723 	g_free(show);
1724     } else {
1725 	gtk_text_buffer_insert_with_tags(tbuf, iter, text, -1, tag, NULL);
1726     }
1727 
1728     return TRUE;
1729 }
1730 
insert_xlink(GtkTextBuffer * tbuf,GtkTextIter * iter,const char * text,gint page,const char * indent)1731 static gboolean insert_xlink (GtkTextBuffer *tbuf, GtkTextIter *iter,
1732 			      const char *text, gint page,
1733 			      const char *indent)
1734 {
1735     GtkTextTagTable *tab = gtk_text_buffer_get_tag_table(tbuf);
1736     GtkTextTag *tag;
1737     int gfr = 0;
1738     gchar tagname[32];
1739 
1740     if (page == GFR_PAGE) {
1741 	strcpy(tagname, "tag:gfr");
1742 	gfr = 1;
1743 	page = 0;
1744     } else {
1745 	sprintf(tagname, "xtag:p%d", page);
1746     }
1747 
1748     tag = gtk_text_tag_table_lookup(tab, tagname);
1749 
1750     if (tag == NULL) {
1751 	/* the required tag is not already in the table */
1752 	if (gfr) {
1753 	    tag = gtk_text_buffer_create_tag(tbuf, tagname, "foreground", "blue",
1754 					     "family", helpfont, NULL);
1755 	} else if (indent != NULL) {
1756 	    tag = gtk_text_buffer_create_tag(tbuf, tagname, "foreground", "blue",
1757 					     "left_margin", 30, NULL);
1758 	} else {
1759 	    tag = gtk_text_buffer_create_tag(tbuf, tagname, "foreground", "blue", NULL);
1760 	}
1761 	g_object_set_data(G_OBJECT(tag), "page", GINT_TO_POINTER(page));
1762 	g_object_set_data(G_OBJECT(tag), "xref", GINT_TO_POINTER(1));
1763     }
1764 
1765     gtk_text_buffer_insert_with_tags(tbuf, iter, text, -1, tag, NULL);
1766 
1767     return TRUE;
1768 }
1769 
open_script_link(GtkTextTag * tag)1770 static void open_script_link (GtkTextTag *tag)
1771 {
1772     char fullname[MAXLEN] = {0};
1773     gchar *fname = NULL;
1774     int err;
1775 
1776     g_object_get(G_OBJECT(tag), "name", &fname, NULL);
1777     err = get_full_filename(fname, fullname, OPT_S);
1778     if (err) {
1779 	errbox_printf(_("Couldn't find %s"), fname);
1780     } else {
1781 	err = gretl_test_fopen(fullname, "r");
1782 	if (err) {
1783 	    errbox_printf(_("Couldn't read %s"), fullname);
1784 	}
1785     }
1786     g_free(fname);
1787 
1788     if (!err) {
1789 	view_script(fullname, 0, VIEW_SCRIPT);
1790     }
1791 }
1792 
make_bibitem_window(const char * buf,GtkWidget * tview)1793 static void make_bibitem_window (const char *buf,
1794 				 GtkWidget *tview)
1795 {
1796     windata_t *vwin;
1797     GtkWidget *top, *vmain;
1798 
1799     vwin = view_formatted_text_buffer(NULL, buf, 64, 100, VIEW_BIBITEM);
1800     vmain = vwin_toplevel(vwin);
1801     top = gtk_widget_get_toplevel(tview);
1802     gtk_window_set_transient_for(GTK_WINDOW(vmain), GTK_WINDOW(top));
1803     gtk_window_set_destroy_with_parent(GTK_WINDOW(vmain), TRUE);
1804     gtk_window_set_position(GTK_WINDOW(vmain),
1805 			    GTK_WIN_POS_CENTER_ON_PARENT);
1806     gtk_widget_show(vmain);
1807 }
1808 
open_bibitem_link(GtkTextTag * tag,GtkWidget * tview)1809 static void open_bibitem_link (GtkTextTag *tag, GtkWidget *tview)
1810 {
1811     const char *gretldir = gretl_home();
1812     gchar *key = NULL;
1813     char fullname[MAXLEN];
1814     FILE *fp;
1815 
1816     g_object_get(G_OBJECT(tag), "name", &key, NULL);
1817     sprintf(fullname, "%sgretlhelp.refs", gretldir);
1818     fp = gretl_fopen(fullname, "r");
1819 
1820     if (fp != NULL) {
1821 	char *buf, line[4096];
1822 	gchar *p, *modbuf;
1823 	int n = strlen(key);
1824 
1825 	while (fgets(line, sizeof line, fp)) {
1826 	    if (!strncmp(line, "<@key=\"", 7)) {
1827 		if (!strncmp(line + 7, key, n)) {
1828 		    p = strchr(line + 7, '>');
1829 		    if (p != NULL) {
1830 			buf = p + 1;
1831 			if ((p = strstr(buf, "<@url")) != NULL) {
1832 			    /* put bibitem URL on new line */
1833 			    n = p - buf;
1834 			    modbuf = g_strdup_printf("%.*s\n%s", n, buf, p);
1835 			    make_bibitem_window(modbuf, tview);
1836 			    g_free(modbuf);
1837 			} else {
1838 			    make_bibitem_window(buf, tview);
1839 			}
1840 		    }
1841 		    break;
1842 		}
1843 	    }
1844 	}
1845 
1846 	fclose(fp);
1847     }
1848 
1849     g_free(key);
1850 }
1851 
object_get_int(gpointer p,const char * key)1852 static int object_get_int (gpointer p, const char *key)
1853 {
1854     return GPOINTER_TO_INT(g_object_get_data(G_OBJECT(p), key));
1855 }
1856 
open_external_link(GtkTextTag * tag)1857 static void open_external_link (GtkTextTag *tag)
1858 {
1859     gchar *name = NULL;
1860 
1861     g_object_get(G_OBJECT(tag), "name", &name, NULL);
1862 
1863     if (name != NULL) {
1864 	if (strncmp(name, "http://", 7) &&
1865 	    strncmp(name, "https://", 8)) {
1866 	    gchar *url = g_strdup_printf("http://%s", name);
1867 
1868 	    browser_open(url);
1869 	    g_free(url);
1870 	} else {
1871 	    browser_open(name);
1872 	}
1873 	g_free(name);
1874     }
1875 }
1876 
open_menu_item(GtkTextTag * tag)1877 static void open_menu_item (GtkTextTag *tag)
1878 {
1879     gchar *name = NULL;
1880 
1881     g_object_get(G_OBJECT(tag), "name", &name, NULL);
1882 
1883     if (name != NULL) {
1884 	if (!strcmp(name, "RemoteGfn")) {
1885 	    display_files(REMOTE_FUNC_FILES, NULL);
1886 	} else if (!strcmp(name, "LocalGfn")) {
1887 	    display_files(FUNC_FILES, NULL);
1888 	} else if (!strcmp(name, "SFAddons")) {
1889 	    display_files(REMOTE_ADDONS, NULL);
1890 	} else if (!strcmp(name, "Registry")) {
1891 	    display_files(PKG_REGISTRY, NULL);
1892 	} else if (!strcmp(name, "SetSeed")) {
1893 	    rand_seed_dialog();
1894 	} else {
1895 	    /* should be a PDF help file */
1896 	    static GtkAction *action;
1897 
1898 	    if (action == NULL) {
1899 		action = gtk_action_new(name, NULL, NULL, NULL);
1900 	    }
1901 	    display_pdf_help(action);
1902 	}
1903 	g_free(name);
1904     }
1905 }
1906 
1907 /* opening a series-listing window, coming from a dbnomics
1908    dataset window */
1909 
open_dbn_link(GtkTextTag * tag)1910 static void open_dbn_link (GtkTextTag *tag)
1911 {
1912     gchar *name = NULL;
1913 
1914     g_object_get(G_OBJECT(tag), "name", &name, NULL);
1915 
1916     if (name != NULL) {
1917 	display_files(DBNOMICS_SERIES, name);
1918 	g_free(name);
1919     }
1920 }
1921 
1922 /* opening a specific series info window, coming from a
1923    dbnomics dataset-search window */
1924 
open_dbs_link(GtkTextTag * tag)1925 static void open_dbs_link (GtkTextTag *tag)
1926 {
1927     gchar *name = NULL;
1928 
1929     g_object_get(G_OBJECT(tag), "name", &name, NULL);
1930 
1931     if (name != NULL) {
1932 	dbnomics_get_series_call(name);
1933 	g_free(name);
1934     }
1935 }
1936 
1937 /* opening next "page" pf dbnomics search results */
1938 
open_next_link(GtkTextTag * tag,GtkWidget * tview)1939 static void open_next_link (GtkTextTag *tag, GtkWidget *tview)
1940 {
1941     windata_t *vwin;
1942 
1943     vwin = g_object_get_data(G_OBJECT(tview), "vwin");
1944     if (vwin != NULL) {
1945 	dbnomics_search(NULL, vwin);
1946     }
1947 }
1948 
open_pdf_file(GtkTextTag * tag)1949 static void open_pdf_file (GtkTextTag *tag)
1950 {
1951     gchar *name = NULL;
1952 
1953     g_object_get(G_OBJECT(tag), "name", &name, NULL);
1954 
1955     if (name != NULL) {
1956 	int warn = 0;
1957 
1958 	if (strchr(name, '/') == NULL && strchr(name, '\\') == NULL) {
1959 	    char *path = get_addon_pdf_path(name);
1960 
1961 	    if (path != NULL) {
1962 		gretl_show_pdf(path, NULL);
1963 		free(path);
1964 	    } else {
1965 		/* not an addon file */
1966 		char fname[FILENAME_MAX] = {0};
1967 
1968 		warn = get_pdf_path(name, fname);
1969 		if (!warn) {
1970 		    gretl_show_pdf(fname, NULL);
1971 		}
1972 	    }
1973 	} else if (gretl_stat(name, NULL) == 0) {
1974 	    gretl_show_pdf(name, NULL);
1975 	} else {
1976 	    warn = 1;
1977 	}
1978 	if (warn) {
1979 	    warnbox_printf(_("Couldn't open %s"), name);
1980 	}
1981 	g_free(name);
1982     }
1983 }
1984 
follow_if_link(GtkWidget * tview,GtkTextIter * iter,gpointer en_ptr)1985 static void follow_if_link (GtkWidget *tview, GtkTextIter *iter,
1986 			    gpointer en_ptr)
1987 {
1988     GSList *tags = NULL, *tagp = NULL;
1989 
1990     tags = gtk_text_iter_get_tags(iter);
1991 
1992     for (tagp = tags; tagp != NULL; tagp = tagp->next) {
1993 	GtkTextTag *tag = tagp->data;
1994 	gint page = object_get_int(tag, "page");
1995 	gint xref = object_get_int(tag, "xref");
1996 	gint en = GPOINTER_TO_INT(en_ptr);
1997 
1998 	if (page != 0 || xref != 0) {
1999 	    if (page == GUIDE_PAGE) {
2000 		gchar *name = NULL;
2001 
2002 		g_object_get(tag, "name", &name, NULL);
2003 		if (name != NULL && strstr(name, "chap:")) {
2004 		    display_guide_chapter(name);
2005 		} else {
2006 		    display_pdf_help(NULL);
2007 		}
2008 		g_free(name);
2009 	    } else if (page == SCRIPT_PAGE) {
2010 		open_script_link(tag);
2011 	    } else if (page == BIB_PAGE) {
2012 		open_bibitem_link(tag, tview);
2013 	    } else if (page == EXT_PAGE) {
2014 		open_external_link(tag);
2015 	    } else if (page == PDF_PAGE) {
2016 		open_pdf_file(tag);
2017 	    } else if (page == MNU_PAGE) {
2018 		open_menu_item(tag);
2019 	    } else if (page == DBN_PAGE) {
2020 		open_dbn_link(tag);
2021 	    } else if (page == DBS_PAGE) {
2022 		open_dbs_link(tag);
2023 	    } else if (page == NEXT_PAGE) {
2024 		open_next_link(tag, tview);
2025 	    } else {
2026 		int role = object_get_int(tview, "role");
2027 
2028 		if (function_help(role)) {
2029 		    if (xref) {
2030 			command_help_callback(page, en);
2031 		    } else {
2032 			function_help_callback(page, en);
2033 		    }
2034 		} else {
2035 		    /* commands help */
2036 		    if (xref) {
2037 			function_help_callback(page, en);
2038 		    } else {
2039 			command_help_callback(page, en);
2040 		    }
2041 		}
2042 	    }
2043 	    break;
2044 	}
2045     }
2046 
2047     if (tags) {
2048 	g_slist_free(tags);
2049     }
2050 }
2051 
2052 /* Help links can be activated by pressing Enter */
2053 
cmdref_key_press(GtkWidget * tview,GdkEventKey * ev,gpointer en_ptr)2054 static gboolean cmdref_key_press (GtkWidget *tview, GdkEventKey *ev,
2055 				  gpointer en_ptr)
2056 {
2057     GtkTextIter iter;
2058     GtkTextBuffer *tbuf;
2059 
2060     switch (ev->keyval) {
2061     case GDK_Return:
2062     case GDK_KP_Enter:
2063 	tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tview));
2064 	gtk_text_buffer_get_iter_at_mark(tbuf, &iter,
2065 					 gtk_text_buffer_get_insert(tbuf));
2066 	follow_if_link(tview, &iter, en_ptr);
2067 	break;
2068     default:
2069 	break;
2070     }
2071 
2072     return FALSE;
2073 }
2074 
2075 /* Help links can be activated by clicking */
2076 
cmdref_event_after(GtkWidget * w,GdkEvent * ev,gpointer en_ptr)2077 static gboolean cmdref_event_after (GtkWidget *w, GdkEvent *ev,
2078 				    gpointer en_ptr)
2079 {
2080     GtkTextIter start, end, iter;
2081     GtkTextView *view;
2082     GtkTextBuffer *buffer;
2083     GdkEventButton *event;
2084     gint x, y;
2085 
2086     if (ev->type != GDK_BUTTON_RELEASE) {
2087 	return FALSE;
2088     }
2089 
2090     event = (GdkEventButton *) ev;
2091 
2092     if (event->button != 1) {
2093 	return FALSE;
2094     }
2095 
2096     view = GTK_TEXT_VIEW(w);
2097     buffer = gtk_text_view_get_buffer(view);
2098 
2099     /* don't follow a link if the user has selected something */
2100     gtk_text_buffer_get_selection_bounds(buffer, &start, &end);
2101     if (gtk_text_iter_get_offset(&start) != gtk_text_iter_get_offset(&end))
2102 	return FALSE;
2103 
2104     gtk_text_view_window_to_buffer_coords(view, GTK_TEXT_WINDOW_WIDGET,
2105 					  event->x, event->y, &x, &y);
2106 
2107     gtk_text_view_get_iter_at_location(view, &iter, x, y);
2108 
2109     follow_if_link(w, &iter, en_ptr);
2110 
2111     return FALSE;
2112 }
2113 
2114 static GdkCursor *hand_cursor = NULL;
2115 static GdkCursor *regular_cursor = NULL;
2116 
ensure_text_cursors(void)2117 static void ensure_text_cursors (void)
2118 {
2119     if (hand_cursor == NULL) {
2120 	hand_cursor = gdk_cursor_new(GDK_HAND2);
2121     }
2122     if (regular_cursor == NULL) {
2123 	regular_cursor = gdk_cursor_new(GDK_XTERM);
2124     }
2125 }
2126 
2127 static void
set_cursor_if_appropriate(GtkTextView * view,gint x,gint y)2128 set_cursor_if_appropriate (GtkTextView *view, gint x, gint y)
2129 {
2130     static gboolean hovering_over_link = FALSE;
2131     GSList *tags = NULL, *tagp = NULL;
2132     GtkTextIter iter;
2133     gboolean hovering = FALSE;
2134 
2135     gtk_text_view_get_iter_at_location(view, &iter, x, y);
2136     tags = gtk_text_iter_get_tags(&iter);
2137 
2138     for (tagp = tags; tagp != NULL; tagp = tagp->next) {
2139 	GtkTextTag *tag = tagp->data;
2140 	gint page = object_get_int(tag, "page");
2141 	gint xref = object_get_int(tag, "xref");
2142 
2143 	if (page != 0 || xref != 0) {
2144 	    hovering = TRUE;
2145 	    break;
2146         }
2147     }
2148 
2149     if (hovering != hovering_over_link) {
2150 	hovering_over_link = hovering;
2151 	if (hovering_over_link) {
2152 	    gdk_window_set_cursor(gtk_text_view_get_window(view, GTK_TEXT_WINDOW_TEXT),
2153 				  hand_cursor);
2154 	} else {
2155 	    gdk_window_set_cursor(gtk_text_view_get_window(view, GTK_TEXT_WINDOW_TEXT),
2156 				  regular_cursor);
2157 	}
2158     }
2159 
2160     if (tags) {
2161 	g_slist_free(tags);
2162     }
2163 }
2164 
2165 static gboolean
cmdref_motion_notify(GtkWidget * w,GdkEventMotion * event)2166 cmdref_motion_notify (GtkWidget *w, GdkEventMotion *event)
2167 {
2168     GtkTextView *view = GTK_TEXT_VIEW(w);
2169     gint x, y;
2170 
2171     gtk_text_view_window_to_buffer_coords(view, GTK_TEXT_WINDOW_WIDGET,
2172 					  event->x, event->y, &x, &y);
2173     set_cursor_if_appropriate(view, x, y);
2174 
2175     return FALSE;
2176 }
2177 
2178 static gboolean
cmdref_visibility_notify(GtkWidget * w,GdkEventVisibility * e)2179 cmdref_visibility_notify (GtkWidget *w,  GdkEventVisibility *e)
2180 {
2181     GtkTextView *view = GTK_TEXT_VIEW(w);
2182     gint wx, wy, bx, by;
2183 
2184     widget_get_pointer_info(w, &wx, &wy, NULL);
2185     gtk_text_view_window_to_buffer_coords(view, GTK_TEXT_WINDOW_WIDGET,
2186 					  wx, wy, &bx, &by);
2187     set_cursor_if_appropriate(view, bx, by);
2188 
2189     return FALSE;
2190 }
2191 
connect_link_signals(windata_t * vwin)2192 static void connect_link_signals (windata_t *vwin)
2193 {
2194     ensure_text_cursors();
2195     g_signal_connect(G_OBJECT(vwin->text), "key-press-event",
2196 		     G_CALLBACK(cmdref_key_press), NULL);
2197     g_signal_connect(G_OBJECT(vwin->text), "event-after",
2198 		     G_CALLBACK(cmdref_event_after), NULL);
2199     g_signal_connect(G_OBJECT(vwin->text), "motion-notify-event",
2200 		     G_CALLBACK(cmdref_motion_notify), NULL);
2201     g_signal_connect(G_OBJECT(vwin->text), "visibility-notify-event",
2202 		     G_CALLBACK(cmdref_visibility_notify), NULL);
2203 }
2204 
maybe_connect_help_signals(windata_t * hwin,int en)2205 static void maybe_connect_help_signals (windata_t *hwin, int en)
2206 {
2207     int done = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(hwin->text),
2208 						 "sigs_connected"));
2209 
2210     ensure_text_cursors();
2211 
2212     if (!done) {
2213 	gpointer en_ptr = GINT_TO_POINTER(en);
2214 
2215 	g_signal_connect(G_OBJECT(hwin->text), "key-press-event",
2216 			 G_CALLBACK(cmdref_key_press), en_ptr);
2217 	g_signal_connect(G_OBJECT(hwin->text), "event-after",
2218 			 G_CALLBACK(cmdref_event_after), en_ptr);
2219 	g_signal_connect(G_OBJECT(hwin->text), "motion-notify-event",
2220 			 G_CALLBACK(cmdref_motion_notify), NULL);
2221 	g_signal_connect(G_OBJECT(hwin->text), "visibility-notify-event",
2222 			 G_CALLBACK(cmdref_visibility_notify), NULL);
2223 	g_object_set_data(G_OBJECT(hwin->text), "sigs_connected",
2224 			  GINT_TO_POINTER(1));
2225     }
2226 }
2227 
maybe_set_help_tabs(windata_t * hwin)2228 static void maybe_set_help_tabs (windata_t *hwin)
2229 {
2230     int done = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(hwin->text),
2231 						 "tabs_set"));
2232 
2233     if (!done) {
2234 	PangoTabArray *tabs;
2235 
2236 	tabs = pango_tab_array_new(1, TRUE);
2237 	pango_tab_array_set_tab(tabs, 0, PANGO_TAB_LEFT, 50);
2238 	gtk_text_view_set_tabs(GTK_TEXT_VIEW(hwin->text), tabs);
2239 	pango_tab_array_free(tabs);
2240 	g_object_set_data(G_OBJECT(hwin->text), "tabs_set", GINT_TO_POINTER(1));
2241     }
2242 }
2243 
2244 /* Construct the index page for the gretl command reference.
2245    Note: we assume here that the maximum length of a gretl
2246    command word is 8 characters.
2247 */
2248 
cmdref_index_page(windata_t * hwin,GtkTextBuffer * tbuf,int en)2249 static void cmdref_index_page (windata_t *hwin, GtkTextBuffer *tbuf, int en)
2250 {
2251     const char *header = N_("Gretl Command Reference");
2252     const gchar *s = (const gchar *) hwin->data;
2253     GtkTextIter iter;
2254     char word[10];
2255     int llen, llen_max = 6;
2256     int idx, j, n;
2257 
2258     gtk_text_buffer_get_iter_at_offset(tbuf, &iter, 0);
2259     gtk_text_buffer_insert_with_tags_by_name(tbuf, &iter,
2260 					     (en)? header : _(header), -1,
2261 					     "title", NULL);
2262     gtk_text_buffer_insert(tbuf, &iter, "\n\n", -1);
2263 
2264     llen = 0;
2265 
2266     while (*s) {
2267 	if (*s == '\n' && *(s+1) == '#' && *(s+2) != '\0') {
2268 	    if (sscanf(s + 2, "%8s", word)) {
2269 		idx = gretl_command_number(word);
2270 		insert_link(tbuf, &iter, word, idx, NULL);
2271 		if (++llen == llen_max) {
2272 		    gtk_text_buffer_insert(tbuf, &iter, "\n", -1);
2273 		    llen = 0;
2274 		} else {
2275 		    n = 10 - strlen(word);
2276 		    for (j=0; j<n; j++) {
2277 			gtk_text_buffer_insert(tbuf, &iter, " ", -1);
2278 		    }
2279 		}
2280 	    }
2281 	}
2282 	s++;
2283     }
2284 
2285     gtk_text_view_set_buffer(GTK_TEXT_VIEW(hwin->text), tbuf);
2286 
2287     maybe_connect_help_signals(hwin, en);
2288     maybe_set_help_tabs(hwin);
2289 }
2290 
2291 /* construct the index page for the gretl function reference */
2292 
funcref_index_page(windata_t * hwin,GtkTextBuffer * tbuf,int en)2293 static void funcref_index_page (windata_t *hwin, GtkTextBuffer *tbuf, int en)
2294 {
2295     const char *header = N_("Gretl Function Reference");
2296     const char *heads[] = {
2297 	N_("Accessors"),
2298 	N_("Built-in strings"),
2299 	N_("Functions proper")
2300     };
2301     const gchar *s = (const gchar *) hwin->data;
2302     gchar *hstr;
2303     GtkTextIter iter;
2304     char word[12];
2305     int llen, llen_max = 5;
2306     int i, j, k, n;
2307 
2308     gtk_text_buffer_get_iter_at_offset(tbuf, &iter, 0);
2309     gtk_text_buffer_insert_with_tags_by_name(tbuf, &iter,
2310 					     (en)? header : _(header), -1,
2311 					     "title", NULL);
2312     gtk_text_buffer_insert(tbuf, &iter, "\n\n", -1);
2313 
2314     i = 1;
2315     k = 0;
2316     llen = 0;
2317 
2318     while (*s) {
2319 	if (*s == '\n' && s[1] == '#' && s[2] != '\0') {
2320 	    if (s[2] == '#') {
2321 		/* insert section heading */
2322 		if (i > 1) {
2323 		    gtk_text_buffer_insert(tbuf, &iter, "\n\n", -1);
2324 		}
2325 		hstr = g_strdup_printf("%s\n", en ? heads[k] : _(heads[k]));
2326 		gtk_text_buffer_insert_with_tags_by_name(tbuf, &iter,
2327 							 hstr, -1,
2328 							 "grayhead", NULL);
2329 		g_free(hstr);
2330 		llen = 0;
2331 		s += 2;
2332 		k++;
2333 	    } else if (sscanf(s + 2, "%10s", word)) {
2334 		/* got a function name */
2335 		insert_link(tbuf, &iter, word, i, NULL);
2336 		if (++llen == llen_max) {
2337 		    gtk_text_buffer_insert(tbuf, &iter, "\n", -1);
2338 		    llen = 0;
2339 		} else {
2340 		    n = 12 - strlen(word);
2341 		    for (j=0; j<n; j++) {
2342 			gtk_text_buffer_insert(tbuf, &iter, " ", -1);
2343 		    }
2344 		}
2345 		i++;
2346 	    }
2347 	}
2348 	s++;
2349     }
2350 
2351     gtk_text_view_set_buffer(GTK_TEXT_VIEW(hwin->text), tbuf);
2352 
2353     maybe_connect_help_signals(hwin, en);
2354     maybe_set_help_tabs(hwin);
2355 }
2356 
2357 /* apparatus to support the 'Back' popup menu item */
2358 
push_backpage(GtkWidget * w,int pg)2359 static void push_backpage (GtkWidget *w, int pg)
2360 {
2361     gpointer p = GINT_TO_POINTER(pg);
2362 
2363     g_object_set_data(G_OBJECT(w), "backpage", p);
2364 }
2365 
pop_backpage(GtkWidget * w)2366 static int pop_backpage (GtkWidget *w)
2367 {
2368     gpointer p = g_object_get_data(G_OBJECT(w), "backpage");
2369 
2370     return GPOINTER_TO_INT(p);
2371 }
2372 
help_popup_click(GtkWidget * w,gpointer p)2373 static gint help_popup_click (GtkWidget *w, gpointer p)
2374 {
2375     windata_t *hwin = (windata_t *) p;
2376     int action = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "action"));
2377     int en = (hwin->role == CMD_HELP_EN || hwin->role == FUNC_HELP_EN);
2378     int page = 0;
2379 
2380     if (action == 2) {
2381 	page = pop_backpage(hwin->text);
2382     }
2383 
2384     if (function_help(hwin->role)) {
2385 	function_help_callback(page, en);
2386     } else {
2387 	command_help_callback(page, en);
2388     }
2389 
2390     return FALSE;
2391 }
2392 
build_help_popup(windata_t * hwin)2393 static GtkWidget *build_help_popup (windata_t *hwin)
2394 {
2395     const char *items[] = {
2396 	N_("Index"),
2397 	N_("Back")
2398     };
2399     GtkWidget *pmenu = gtk_menu_new();
2400     GtkWidget *item;
2401     int i, imin = 0, imax = 2;
2402 
2403     if (hwin->active_var == 0) {
2404 	/* don't offer "Index" if we're in the index */
2405 	imin = 1;
2406     }
2407 
2408     if (pop_backpage(hwin->text) == 0) {
2409 	/* don't offer "Back" if we haven't been anywhere */
2410 	imax = 1;
2411     }
2412 
2413     for (i=imin; i<imax; i++) {
2414 	item = gtk_menu_item_new_with_label(_(items[i]));
2415 	g_object_set_data(G_OBJECT(item), "action", GINT_TO_POINTER(i+1));
2416 	g_signal_connect(G_OBJECT(item), "activate",
2417 			 G_CALLBACK(help_popup_click),
2418 			 hwin);
2419 	gtk_widget_show(item);
2420 	gtk_menu_shell_append(GTK_MENU_SHELL(pmenu), item);
2421     }
2422 
2423     return pmenu;
2424 }
2425 
2426 gboolean
help_popup_handler(GtkWidget * w,GdkEventButton * event,gpointer p)2427 help_popup_handler (GtkWidget *w, GdkEventButton *event, gpointer p)
2428 {
2429     if (right_click(event)) {
2430 	windata_t *hwin = (windata_t *) p;
2431 
2432 	if (hwin->active_var == 0 && pop_backpage(w) == 0) {
2433 	    return TRUE;
2434 	}
2435 
2436 	if (hwin->popup) {
2437 	    gtk_widget_destroy(hwin->popup);
2438 	    hwin->popup = NULL;
2439 	}
2440 
2441 	hwin->popup = build_help_popup(hwin);
2442 
2443 	if (hwin->popup != NULL) {
2444 	    gtk_menu_popup(GTK_MENU(hwin->popup), NULL, NULL, NULL, NULL,
2445 			   event->button, event->time);
2446 	    g_signal_connect(G_OBJECT(hwin->popup), "destroy",
2447 			     G_CALLBACK(gtk_widget_destroyed),
2448 			     &hwin->popup);
2449 	}
2450 
2451 	return TRUE;
2452     }
2453 
2454     return FALSE;
2455 }
2456 
reformat_para(char * buf,int maxlen)2457 static void reformat_para (char *buf, int maxlen)
2458 {
2459     char *p = buf;
2460     char *line;
2461     int i, n;
2462 
2463     g_strchomp(g_strchug(buf));
2464 
2465     /* normalize spaces, removing any existing line breaks */
2466 
2467     while (*p) {
2468 	if (*p == ' ' || *p == '\n' || *p == '\t') {
2469 	    *p = ' ';
2470 	    n = strspn(p + 1, " \t\n");
2471 	    if (n > 0) {
2472 		g_strchug(p + 1);
2473 		p += n;
2474 	    }
2475 	}
2476 	p++;
2477     }
2478 
2479     line = p = buf;
2480     n = 0;
2481 
2482     /* insert line breaks to give lines of length up
2483        to @maxlen */
2484 
2485     while (*p++) {
2486 	n++;
2487 	if (n > maxlen) {
2488 	    /* back up to first available break-point */
2489 	    for (i=n-1; i>0; i--) {
2490 		if (line[i] == ' ') {
2491 		    line[i] = '\n';
2492 		    p = line = &line[i+1];
2493 		    n = 0;
2494 		}
2495 		if (n == 0) {
2496 		    break;
2497 		}
2498 	    }
2499 	}
2500     }
2501 }
2502 
prev_double_nl(GtkTextIter * pos,GtkTextIter * start)2503 static gboolean prev_double_nl (GtkTextIter *pos,
2504 				GtkTextIter *start)
2505 {
2506     GtkTextIter cpos = *pos;
2507     int nlcount = 0;
2508     gunichar c;
2509     gboolean ret = 0;
2510 
2511     if (gtk_text_iter_get_char(pos) == '\n') {
2512 	nlcount = 1;
2513     }
2514 
2515     while (gtk_text_iter_backward_char(&cpos)) {
2516 	c = gtk_text_iter_get_char(&cpos);
2517 	if (c == '\n') {
2518 	    if (++nlcount == 2) {
2519 		*start = cpos;
2520 		gtk_text_iter_forward_chars(start, 2);
2521 		ret = 1;
2522 		break;
2523 	    }
2524 	} else if (!isspace(c)) {
2525 	    nlcount = 0;
2526 	}
2527     }
2528 
2529     return ret;
2530 }
2531 
next_double_nl(GtkTextIter * pos,GtkTextIter * end)2532 static gboolean next_double_nl (GtkTextIter *pos,
2533 				GtkTextIter *end)
2534 {
2535     GtkTextIter cpos = *pos;
2536     int nlcount = 0;
2537     gunichar c;
2538     gboolean ret = 0;
2539 
2540     if (gtk_text_iter_get_char(pos) == '\n') {
2541 	nlcount = 1;
2542     }
2543 
2544     while (gtk_text_iter_forward_char(&cpos)) {
2545 	c = gtk_text_iter_get_char(&cpos);
2546 	if (c == '\n') {
2547 	    if (++nlcount == 2) {
2548 		*end = cpos;
2549 		gtk_text_iter_backward_char(end);
2550 		ret = 1;
2551 		break;
2552 	    }
2553 	} else if (!isspace(c)) {
2554 	    nlcount = 0;
2555 	}
2556     }
2557 
2558     return ret;
2559 }
2560 
not_in_para(GtkTextBuffer * buf,GtkTextIter * pos)2561 static int not_in_para (GtkTextBuffer *buf,
2562 			GtkTextIter *pos)
2563 {
2564     GtkTextIter cpos = *pos;
2565     int got_text = 0;
2566     gunichar c;
2567 
2568     /* We're "not in a paragraph" if the current
2569        cursor position is bracketed by newlines,
2570        with no non-space character intervening.
2571     */
2572 
2573     c = gtk_text_iter_get_char(&cpos);
2574 
2575     if (!isspace(c)) {
2576 	got_text = 1;
2577     } else if (c == '\n') {
2578 	/* crawl backwards to newline or text */
2579 	while (gtk_text_iter_backward_char(&cpos)) {
2580 	    c = gtk_text_iter_get_char(&cpos);
2581 	    if (c == '\n') {
2582 		break;
2583 	    } else if (!isspace(c)) {
2584 		got_text = 1;
2585 		break;
2586 	    }
2587 	}
2588     } else if (isspace(c)) {
2589 	/* crawl forward to newline or text */
2590 	while (gtk_text_iter_forward_char(&cpos)) {
2591 	    c = gtk_text_iter_get_char(&cpos);
2592 	    if (c == '\n') {
2593 		break;
2594 	    } else if (!isspace(c)) {
2595 		got_text = 1;
2596 		break;
2597 	    }
2598 	}
2599 	if (!got_text) {
2600 	    /* OK, try backwards */
2601 	    cpos = *pos;
2602 	    while (gtk_text_iter_backward_char(&cpos)) {
2603 		c = gtk_text_iter_get_char(&cpos);
2604 		if (c == '\n') {
2605 		    break;
2606 		} else if (!isspace(c)) {
2607 		    got_text = 1;
2608 		    break;
2609 		}
2610 	    }
2611 	}
2612     }
2613 
2614     return !got_text;
2615 }
2616 
textbuf_get_para_limits(GtkTextBuffer * buf,GtkTextIter * pos,GtkTextIter * start,GtkTextIter * end)2617 static gboolean textbuf_get_para_limits (GtkTextBuffer *buf,
2618 					 GtkTextIter *pos,
2619 					 GtkTextIter *start,
2620 					 GtkTextIter *end)
2621 {
2622     if (not_in_para(buf, pos)) {
2623 	return FALSE;
2624     }
2625 
2626     if (!prev_double_nl(pos, start)) {
2627 	gtk_text_buffer_get_start_iter(buf, start);
2628     }
2629 
2630     if (!next_double_nl(pos, end)) {
2631 	gtk_text_buffer_get_end_iter(buf, end);
2632     }
2633 
2634     return TRUE;
2635 }
2636 
textview_format_paragraph(GtkWidget * view)2637 void textview_format_paragraph (GtkWidget *view)
2638 {
2639     GtkTextBuffer *buf;
2640     GtkTextIter pos, start, end;
2641     gchar *para = NULL;
2642 
2643     buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
2644 
2645     /* find where the cursor is */
2646     gtk_text_buffer_get_iter_at_mark(buf, &pos,
2647 				     gtk_text_buffer_get_insert(buf));
2648 
2649     /* find start and end of paragraph, if we're in one */
2650     if (!textbuf_get_para_limits(buf, &pos, &start, &end)) {
2651 	return;
2652     }
2653 
2654     /* grab the para text */
2655     para = gtk_text_buffer_get_text(buf, &start, &end, FALSE);
2656 
2657     if (para != NULL && !string_is_blank(para)) {
2658 	reformat_para(para, 72);
2659 	gtk_text_buffer_begin_user_action(buf);
2660 	gtk_text_buffer_delete(buf, &start, &end);
2661 	gtk_text_buffer_insert(buf, &start, para, -1);
2662 	gtk_text_buffer_end_user_action(buf);
2663 	g_free(para);
2664 
2665     }
2666 }
2667 
textview_get_current_line(GtkWidget * view,int allow_blank)2668 static gchar *textview_get_current_line (GtkWidget *view, int allow_blank)
2669 {
2670     GtkTextBuffer *buf;
2671     GtkTextIter start, end;
2672     gchar *ret = NULL;
2673 
2674     buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
2675     gtk_text_buffer_get_iter_at_mark(buf, &start,
2676 				     gtk_text_buffer_get_insert(buf));
2677     gtk_text_iter_set_line_offset(&start, 0);
2678     gtk_text_buffer_get_iter_at_mark(buf, &end,
2679 				     gtk_text_buffer_get_insert(buf));
2680     if (!gtk_text_iter_ends_line(&end)) {
2681 	/* N.B. don't skip on to the end of the _next_ line */
2682 	gtk_text_iter_forward_to_line_end(&end);
2683     }
2684 
2685     ret = gtk_text_buffer_get_text(buf, &start, &end, FALSE);
2686 
2687     if (!allow_blank && string_is_blank(ret)) {
2688 	g_free(ret);
2689 	ret = NULL;
2690     }
2691 
2692     return ret;
2693 }
2694 
textview_get_current_line_with_newline(GtkWidget * view)2695 static gchar *textview_get_current_line_with_newline (GtkWidget *view)
2696 {
2697     gchar *s = textview_get_current_line(view, 0);
2698 
2699     if (s != NULL && *s != '\0' && s[strlen(s)-1] != '\n') {
2700 	gchar *tmp = g_strdup_printf("%s\n", s);
2701 
2702 	g_free(s);
2703 	s = tmp;
2704     }
2705 
2706     return s;
2707 }
2708 
2709 /* Determine whether or not any of the lines in a chunk of text
2710    are indented, via spaces or tabs.
2711 */
2712 
text_is_indented(const gchar * s)2713 static int text_is_indented (const gchar *s)
2714 {
2715     int leading = 1;
2716 
2717     if (s == NULL) {
2718 	return 0;
2719     }
2720 
2721     while (*s) {
2722 	if (*s == '\n') {
2723 	    leading = 1;
2724 	} else if (*s != ' ' && *s != '\t') {
2725 	    leading = 0;
2726 	}
2727 	if (leading && (*s == ' ' || *s == '\t')) {
2728 	    return 1;
2729 	}
2730 	s++;
2731     }
2732 
2733     return 0;
2734 }
2735 
2736 /* Determine whether or not a chunk of text is commented, in the form
2737    of each line beginning with '#' (with possible leading white
2738    space).  If some lines are commented and others are not, return -1,
2739    which blocks the comment/uncomment menu items.
2740 */
2741 
text_is_commented(const gchar * s)2742 static int text_is_commented (const gchar *s)
2743 {
2744     int gotc = 0, comm = 0;
2745     int lines = 1;
2746 
2747     if (s == NULL) {
2748 	return -1;
2749     }
2750 
2751     while (*s) {
2752 	if (!gotc) {
2753 	    if (*s == '#') {
2754 		comm++;
2755 		gotc = 1;
2756 	    } else if (!isspace(*s)) {
2757 		gotc = 1;
2758 	    }
2759 	} else if (*s == '\n') {
2760 	    gotc = 0;
2761 	    if (*(s+1)) {
2762 		lines++;
2763 	    }
2764 	}
2765 	s++;
2766     }
2767 
2768     if (comm > 0 && comm < lines) {
2769 	/* mixed */
2770 	comm = -1;
2771     }
2772 
2773     return comm;
2774 }
2775 
2776 struct textbit {
2777     windata_t *vwin;
2778     GtkTextBuffer *buf;
2779     GtkTextIter start;
2780     GtkTextIter end;
2781     gchar *chunk;
2782     int commented;
2783     int selected;
2784 };
2785 
2786 /* either insert or remove '#' comment markers at the start of the
2787    line(s) of a chunk of text
2788 */
2789 
comment_or_uncomment_text(GtkWidget * w,gpointer p)2790 static void comment_or_uncomment_text (GtkWidget *w, gpointer p)
2791 {
2792     struct textbit *tb = (struct textbit *) p;
2793     gchar *s;
2794 
2795     gtk_text_buffer_delete(tb->buf, &tb->start, &tb->end);
2796 
2797     if (tb->selected) {
2798 	char line[1024];
2799 
2800 	bufgets_init(tb->chunk);
2801 	while (bufgets(line, sizeof line, tb->chunk)) {
2802 	    if (tb->commented) {
2803 		s = strchr(line, '#');
2804 		if (s != NULL) {
2805 		    s++;
2806 		    if (*s == ' ') s++;
2807 		    gtk_text_buffer_insert(tb->buf, &tb->start, s, -1);
2808 		}
2809 	    } else {
2810 		gtk_text_buffer_insert(tb->buf, &tb->start, "# ", -1);
2811 		gtk_text_buffer_insert(tb->buf, &tb->start, line, -1);
2812 	    }
2813 	}
2814 	bufgets_finalize(tb->chunk);
2815     } else {
2816 	if (tb->commented) {
2817 	    s = strchr(tb->chunk, '#');
2818 	    if (s != NULL) {
2819 		s++;
2820 		if (*s == ' ') s++;
2821 		gtk_text_buffer_insert(tb->buf, &tb->start, s, -1);
2822 	    }
2823 	} else {
2824 	    gtk_text_buffer_insert(tb->buf, &tb->start, "# ", -1);
2825 	    gtk_text_buffer_insert(tb->buf, &tb->start, tb->chunk, -1);
2826 	}
2827     }
2828 }
2829 
2830 enum {
2831     TAB_NEXT,
2832     TAB_PREV
2833 };
2834 
spaces_to_tab_stop(const char * s,int targ)2835 static int spaces_to_tab_stop (const char *s, int targ)
2836 {
2837     int ret, n = 0;
2838 
2839     while (*s) {
2840 	if (*s == ' ') {
2841 	    n++;
2842 	} else if (*s == '\t') {
2843 	    n += tabwidth;
2844 	} else {
2845 	    break;
2846 	}
2847 	s++;
2848     }
2849 
2850     if (targ == TAB_NEXT) {
2851 	ret = tabwidth - (n % tabwidth);
2852     } else {
2853 	if (n % tabwidth == 0) {
2854 	    ret = n - tabwidth;
2855 	    if (ret < 0) ret = 0;
2856 	} else {
2857 	    ret = (n / tabwidth) * tabwidth;
2858 	}
2859     }
2860 
2861     return ret;
2862 }
2863 
textbuf_get_cmdword(const char * s,char * word)2864 static void textbuf_get_cmdword (const char *s, char *word)
2865 {
2866     if (!strncmp(s, "catch ", 6)) {
2867 	s += 6;
2868     }
2869 
2870     if (sscanf(s, "%*s <- %8s", word) != 1) {
2871 	sscanf(s, "%8s", word);
2872     }
2873 }
2874 
2875 #define bare_quote(p,s)   (*p == '"' && (p-s==0 || *(p-1) != '\\'))
2876 #define starts_comment(p) (*p == '/' && *(p+1) == '*')
2877 #define ends_comment(p)   (*p == '*' && *(p+1) == '/')
2878 
check_for_comment(const char * s,int * incomm)2879 static void check_for_comment (const char *s, int *incomm)
2880 {
2881     int commbak = *incomm;
2882     const char *p = s;
2883     int quoted = 0;
2884 
2885     while (*p) {
2886 	if (!quoted && !*incomm && *p == '#') {
2887 	    break;
2888 	}
2889 	if (!*incomm && bare_quote(p, s)) {
2890 	    quoted = !quoted;
2891 	}
2892 	if (!quoted) {
2893 	    if (starts_comment(p)) {
2894 		*incomm = 1;
2895 		p += 2;
2896 	    } else if (ends_comment(p)) {
2897 		*incomm = 0;
2898 		p += 2;
2899 		p += strspn(p, " ");
2900 	    }
2901 	}
2902 	if (*p) {
2903 	    p++;
2904 	}
2905     }
2906 
2907     if (*incomm && commbak) {
2908 	/* on the second or subsequent line of a multiline
2909 	   comment */
2910 	*incomm = 2;
2911     }
2912 }
2913 
2914 /* determine whether a given line is subject to
2915    continuation (i.e. ends with backslash, comma
2916    or semicolon, other than in a comment)
2917 */
2918 
line_broken(const char * s)2919 static int line_broken (const char *s)
2920 {
2921     int ret = 0;
2922 
2923     if (*s != '\0') {
2924 	int i, n = strlen(s);
2925 
2926 	for (i=n-1; i>=0; i--) {
2927 	    if (s[i] == '\\' || s[i] == ',') {
2928 		ret = 1;
2929 	    } else if (!ret && !isspace(s[i])) {
2930 		break;
2931 	    } else if (ret && s[i] == '#') {
2932 		ret = 0;
2933 		break;
2934 	    }
2935 	}
2936     }
2937 
2938     return ret;
2939 }
2940 
strip_trailing_whitespace(char * s)2941 static void strip_trailing_whitespace (char *s)
2942 {
2943     int i, n = strlen(s);
2944 
2945     for (i=n-1; i>=0; i--) {
2946 	if (s[i] == '\n' || s[i] == ' ' || s[i] == '\t') {
2947 	    s[i] = '\0';
2948 	} else {
2949 	    break;
2950 	}
2951     }
2952 
2953     strcat(s, "\n");
2954 }
2955 
2956 /* determine position of unmatched left parenthesis,
2957    when applicable, if @s starts a function definition
2958 */
2959 
left_paren_offset(const char * s)2960 static int left_paren_offset (const char *s)
2961 {
2962     const char *p = strchr(s, '(');
2963 
2964     if (p != NULL && strchr(p, ')') == NULL) {
2965 	return p - s;
2966     } else {
2967 	return 0;
2968     }
2969 }
2970 
normalize_indent(GtkTextBuffer * tbuf,const gchar * buf,GtkTextIter * start,GtkTextIter * end)2971 static void normalize_indent (GtkTextBuffer *tbuf,
2972 			      const gchar *buf,
2973 			      GtkTextIter *start,
2974 			      GtkTextIter *end)
2975 {
2976     int this_indent = 0;
2977     int next_indent = 0;
2978     char word[9], line[1024];
2979     char lastline[1024];
2980     const char *ins;
2981     int incomment = 0;
2982     int inforeign = 0;
2983     int lp_pos = 0;
2984     int lp_zero = 0;
2985     int i, nsp;
2986 
2987     if (buf == NULL) {
2988 	return;
2989     }
2990 
2991     gtk_text_buffer_delete(tbuf, start, end);
2992 
2993     lastline[0] = '\0';
2994     bufgets_init(buf);
2995 
2996     while (bufgets(line, sizeof line, buf)) {
2997 	int handled = 0;
2998 
2999 	strip_trailing_whitespace(line);
3000 
3001 	if (string_is_blank(line)) {
3002 	    gtk_text_buffer_insert(tbuf, start, line, -1);
3003 	    continue;
3004 	}
3005 	check_for_comment(line, &incomment);
3006 #if 0
3007 	if (incomment) {
3008 	    /* in multiline comment */
3009 	    gtk_text_buffer_insert(tbuf, start, line, -1);
3010 	    continue;
3011 	}
3012 #endif
3013 	ins = line + strspn(line, " \t");
3014 	if (!incomment) {
3015 	    *word = '\0';
3016 	    textbuf_get_cmdword(ins, word);
3017 	    if (!strcmp(word, "foreign")) {
3018 		inforeign = 1;
3019 	    } else if (inforeign) {
3020 		if (!strncmp(ins, "end foreign", 11)) {
3021 		    inforeign = 0;
3022 		} else {
3023 		    gtk_text_buffer_insert(tbuf, start, line, -1);
3024 		    handled = 1;
3025 		}
3026 	    } else {
3027 		if (!strcmp(word, "function")) {
3028 		    lp_pos = left_paren_offset(ins);
3029 		} else if (lp_pos > 0 && strchr(ins, ')') != NULL) {
3030 		    lp_zero = 1;
3031 		}
3032 		if (!strcmp(word, "outfile")) {
3033 		    /* handle legacy syntax */
3034 		    adjust_indent(ins, &this_indent, &next_indent);
3035 		} else {
3036 		    adjust_indent(word, &this_indent, &next_indent);
3037 		}
3038 	    }
3039 	}
3040 	if (!handled) {
3041 	    nsp = this_indent * tabwidth;
3042 	    if (incomment == 2) {
3043 		nsp += 3;
3044 	    } else if (line_broken(lastline)) {
3045 		if (lp_pos > 0) {
3046 		    nsp = lp_pos + 1;
3047 		} else {
3048 		    nsp += 2;
3049 		}
3050 	    }
3051 	    for (i=0; i<nsp; i++) {
3052 		gtk_text_buffer_insert(tbuf, start, " ", -1);
3053 	    }
3054 	    gtk_text_buffer_insert(tbuf, start, ins, -1);
3055 	}
3056 	strcpy(lastline, line);
3057 	if (lp_zero) {
3058 	    lp_zero = lp_pos = 0;
3059 	}
3060     }
3061 
3062     bufgets_finalize(buf);
3063 }
3064 
in_foreign_land(GtkWidget * text_widget)3065 static int in_foreign_land (GtkWidget *text_widget)
3066 {
3067     GtkTextBuffer *tbuf;
3068     GtkTextIter start, end;
3069     gchar *buf;
3070     char *s, line[1024];
3071     int inforeign = 0;
3072 
3073     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget));
3074     gtk_text_buffer_get_start_iter(tbuf, &start);
3075     gtk_text_buffer_get_iter_at_mark(tbuf, &end,
3076 				     gtk_text_buffer_get_insert(tbuf));
3077     buf = gtk_text_buffer_get_text(tbuf, &start, &end, FALSE);
3078 
3079     bufgets_init(buf);
3080 
3081     while (bufgets(line, sizeof line, buf)) {
3082 	s = line + strspn(line, " \t");
3083 	if (!strncmp(s, "foreign ", 8)) {
3084 	    inforeign = 1;
3085 	} else if (!strncmp(s, "end foreign", 11)) {
3086 	    inforeign = 0;
3087 	}
3088     }
3089 
3090     bufgets_finalize(buf);
3091     g_free(buf);
3092 
3093     return inforeign;
3094 }
3095 
auto_indent_script(GtkWidget * w,windata_t * vwin)3096 static void auto_indent_script (GtkWidget *w, windata_t *vwin)
3097 {
3098     GtkAdjustment *adj;
3099     GtkTextBuffer *tbuf;
3100     GtkTextMark *mark;
3101     GtkTextIter here, start, end;
3102     gchar *buf;
3103     gint line, offset;
3104     gdouble pos;
3105 
3106     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
3107 
3108     /* record scrolling position */
3109     adj = gtk_text_view_get_vadjustment(GTK_TEXT_VIEW(vwin->text));
3110     pos = gtk_adjustment_get_value(adj);
3111 
3112     /* record cursor position (line, offset) */
3113     mark = gtk_text_buffer_get_insert(tbuf);
3114     gtk_text_buffer_get_iter_at_mark(tbuf, &here, mark);
3115     line = gtk_text_iter_get_line(&here);
3116     offset = gtk_text_iter_get_line_offset(&here);
3117 
3118     /* grab and revise the text */
3119     gtk_text_buffer_get_start_iter(tbuf, &start);
3120     gtk_text_buffer_get_end_iter(tbuf, &end);
3121     buf = gtk_text_buffer_get_text(tbuf, &start, &end, FALSE);
3122     normalize_indent(tbuf, buf, &start, &end);
3123     g_free(buf);
3124 
3125     /* restore cursor position */
3126     gtk_text_buffer_get_iter_at_line(tbuf, &here, line);
3127     gtk_text_iter_set_line_offset(&here, offset);
3128     gtk_text_buffer_place_cursor(tbuf, &here);
3129 
3130     /* restore scrolling position */
3131     gtk_adjustment_set_value(adj, pos);
3132     gtk_adjustment_value_changed(adj);
3133 }
3134 
indent_region(GtkWidget * w,gpointer p)3135 static void indent_region (GtkWidget *w, gpointer p)
3136 {
3137     struct textbit *tb = (struct textbit *) p;
3138 
3139     if (smarttab) {
3140 	normalize_indent(tb->buf, tb->chunk, &tb->start, &tb->end);
3141     } else {
3142 	char line[1024];
3143 	int i, n;
3144 
3145 	gtk_text_buffer_delete(tb->buf, &tb->start, &tb->end);
3146 
3147 	bufgets_init(tb->chunk);
3148 
3149 	while (bufgets(line, sizeof line, tb->chunk)) {
3150 	    n = spaces_to_tab_stop(line, TAB_NEXT);
3151 	    for (i=0; i<n; i++) {
3152 		gtk_text_buffer_insert(tb->buf, &tb->start, " ", -1);
3153 	    }
3154 	    gtk_text_buffer_insert(tb->buf, &tb->start, line, -1);
3155 	}
3156 
3157 	bufgets_finalize(tb->chunk);
3158     }
3159 }
3160 
indent_hansl(GtkWidget * w,windata_t * vwin)3161 void indent_hansl (GtkWidget *w, windata_t *vwin)
3162 {
3163     auto_indent_script(w, vwin);
3164 }
3165 
unindent_region(GtkWidget * w,gpointer p)3166 static void unindent_region (GtkWidget *w, gpointer p)
3167 {
3168     struct textbit *tb = (struct textbit *) p;
3169     char line[1024];
3170     char *ins;
3171     int i, n;
3172 
3173     gtk_text_buffer_delete(tb->buf, &tb->start, &tb->end);
3174 
3175     bufgets_init(tb->chunk);
3176 
3177     while (bufgets(line, sizeof line, tb->chunk)) {
3178 	n = spaces_to_tab_stop(line, TAB_PREV);
3179 	ins = line + strspn(line, " \t");
3180 	for (i=0; i<n; i++) {
3181 	    gtk_text_buffer_insert(tb->buf, &tb->start, " ", -1);
3182 	}
3183 	gtk_text_buffer_insert(tb->buf, &tb->start, ins, -1);
3184     }
3185 
3186     bufgets_finalize(tb->chunk);
3187 }
3188 
exec_script_text(GtkWidget * w,gpointer p)3189 static void exec_script_text (GtkWidget *w, gpointer p)
3190 {
3191     struct textbit *tb = (struct textbit *) p;
3192 
3193     run_script_fragment(tb->vwin, tb->chunk);
3194     tb->chunk = NULL; /* will be freed already */
3195 }
3196 
3197 enum {
3198     AUTO_SELECT_NONE,
3199     AUTO_SELECT_LINE
3200 };
3201 
vwin_get_textbit(windata_t * vwin,int mode)3202 static struct textbit *vwin_get_textbit (windata_t *vwin, int mode)
3203 {
3204     GtkTextBuffer *tbuf;
3205     GtkTextIter start, end;
3206     int selected = 0;
3207     struct textbit *tb;
3208 
3209     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
3210     if (gtk_text_buffer_get_selection_bounds(tbuf, &start, &end)) {
3211 	selected = 1;
3212     }
3213 
3214     if (!selected && mode != AUTO_SELECT_LINE) {
3215 	return NULL;
3216     }
3217 
3218     tb = malloc(sizeof *tb);
3219     if (tb == NULL) {
3220 	return NULL;
3221     }
3222 
3223     tb->vwin = vwin;
3224     tb->buf = tbuf;
3225     tb->start = start;
3226     tb->end = end;
3227     tb->selected = selected;
3228     tb->commented = 0;
3229     tb->chunk = NULL;
3230 
3231     if (selected) {
3232 	int endpos;
3233 
3234 	gtk_text_iter_set_line_offset(&tb->start, 0);
3235 	endpos = gtk_text_iter_get_line_offset(&tb->end);
3236 	if (endpos > 0 && !gtk_text_iter_ends_line(&tb->end)) {
3237 	    gtk_text_iter_forward_to_line_end(&tb->end);
3238 	}
3239 	tb->chunk = gtk_text_buffer_get_text(tb->buf, &tb->start, &tb->end, FALSE);
3240     } else {
3241 	gtk_text_buffer_get_iter_at_mark(tb->buf, &tb->start,
3242 					 gtk_text_buffer_get_insert(tb->buf));
3243 	gtk_text_iter_set_line_offset(&tb->start, 0);
3244 	gtk_text_buffer_get_iter_at_mark(tb->buf, &tb->end,
3245 					 gtk_text_buffer_get_insert(tb->buf));
3246 	gtk_text_iter_forward_to_line_end(&tb->end);
3247 	tb->chunk = gtk_text_buffer_get_text(tb->buf, &tb->start, &tb->end, FALSE);
3248     }
3249 
3250     return tb;
3251 }
3252 
count_leading_spaces(const char * s)3253 static int count_leading_spaces (const char *s)
3254 {
3255     int n = 0;
3256 
3257     while (*s) {
3258 	if (*s == ' ') {
3259 	    n++;
3260 	} else if (*s == '\t') {
3261 	    n += tabwidth;
3262 	} else {
3263 	    break;
3264 	}
3265 	s++;
3266     }
3267 
3268     return n;
3269 }
3270 
3271 /* Given what's presumed to be a start-of-line iter, find how many
3272    leading spaces are on the line, counting tabs as multiple spaces.
3273 */
3274 
leading_spaces_at_iter(GtkTextBuffer * tbuf,GtkTextIter * start,const char * word)3275 static int leading_spaces_at_iter (GtkTextBuffer *tbuf,
3276 				   GtkTextIter *start,
3277 				   const char *word)
3278 {
3279     GtkTextIter end = *start;
3280     gchar *s;
3281     int n = 0;
3282 
3283     gtk_text_iter_forward_to_line_end(&end);
3284     s = gtk_text_buffer_get_text(tbuf, start, &end, FALSE);
3285     if (s != NULL) {
3286 	if (!strcmp(word, "function")) {
3287 	    n = left_paren_offset(s);
3288 	    if (n > 0) {
3289 		n--;
3290 	    } else {
3291 		n = count_leading_spaces(s);
3292 	    }
3293 	} else {
3294 	    n = count_leading_spaces(s);
3295 	}
3296 	g_free(s);
3297     }
3298 
3299     return n;
3300 }
3301 
incremental_leading_spaces(const char * prevword,const char * thisword)3302 static int incremental_leading_spaces (const char *prevword,
3303 				       const char *thisword)
3304 {
3305     int this_indent = 0;
3306     int next_indent = 0;
3307 
3308     if (*prevword != '\0') {
3309 	int prev_indent = 0;
3310 
3311 	adjust_indent(prevword, &this_indent, &next_indent);
3312 #if TABDEBUG > 1
3313 	fprintf(stderr, "adjust_indent 1: prevword='%s', this=%d, next=%d\n",
3314 		prevword, this_indent, next_indent);
3315 #endif
3316 	prev_indent = this_indent;
3317 	if (*thisword != '\0') {
3318 	    adjust_indent(thisword, &this_indent, &next_indent);
3319 #if TABDEBUG > 1
3320 	    fprintf(stderr, "adjust_indent 2: thisword='%s', this=%d\n",
3321 		    thisword, this_indent);
3322 #endif
3323 	    this_indent -= prev_indent;
3324 #if TABDEBUG > 1
3325 	    fprintf(stderr, "adjust_indent 3: this=%d\n", this_indent);
3326 #endif
3327 	} else {
3328 	    this_indent = next_indent - this_indent;
3329 	}
3330     }
3331 
3332 #if TABDEBUG > 1
3333     fprintf(stderr, "incremental_leading_spaces: returning %d*%d = %d\n",
3334 	    this_indent, tabwidth, this_indent * tabwidth);
3335 #endif
3336 
3337     return this_indent * tabwidth;
3338 }
3339 
line_continues(const gchar * s)3340 static int line_continues (const gchar *s)
3341 {
3342     int i, n = strlen(s);
3343 
3344     for (i=n-1; i>=0; i--) {
3345 	if (s[i] != ' ' && s[i] != '\t') {
3346 	    return (s[i] == '\\' || s[i] == ',');
3347 	}
3348     }
3349 
3350     return 0;
3351 }
3352 
get_word_and_cont(const char * s,char * word,int * contd)3353 static int get_word_and_cont (const char *s, char *word, int *contd)
3354 {
3355     /* don't move onto next line */
3356     if (*s != '\n' && *s != '\r') {
3357 	s += strspn(s, " \t");
3358 	if (sscanf(s, "%*s <- %8s", word) != 1) {
3359 	    sscanf(s, "%8s", word);
3360 	}
3361 	if (*word != '#' && contd != NULL) {
3362 	    *contd = line_continues(s);
3363 	}
3364     }
3365 
3366     return *word != '\0';
3367 }
3368 
line_continues_previous(GtkTextBuffer * tbuf,GtkTextIter iter)3369 static int line_continues_previous (GtkTextBuffer *tbuf,
3370 				    GtkTextIter iter)
3371 {
3372     GtkTextIter end, prev = iter;
3373     gchar *s;
3374     int ret = 0;
3375 
3376     if (gtk_text_iter_backward_line(&prev)) {
3377 	end = prev;
3378 	if (gtk_text_iter_forward_to_line_end(&end)) {
3379 	    s = gtk_text_buffer_get_text(tbuf, &prev, &end, FALSE);
3380 	    if (s != NULL) {
3381 		if (*s != '\n' && *s != '\r') {
3382 		    ret = line_continues(s);
3383 		}
3384 		g_free(s);
3385 	    }
3386 	}
3387     }
3388 
3389     return ret;
3390 }
3391 
3392 /* get "command word", max 8 characters: work backwards up script
3393    to find this */
3394 
get_previous_line_start_word(char * word,GtkTextBuffer * tbuf,GtkTextIter iter,int * leadspace,int * contd)3395 static char *get_previous_line_start_word (char *word,
3396 					   GtkTextBuffer *tbuf,
3397 					   GtkTextIter iter,
3398 					   int *leadspace,
3399 					   int *contd)
3400 {
3401     GtkTextIter end, prev = iter;
3402     int *pcont = contd;
3403     int rparen = 0;
3404     int i = 0;
3405     gchar *s;
3406 
3407     *word = '\0';
3408 
3409     while (*word == '\0' && gtk_text_iter_backward_line(&prev)) {
3410 	end = prev;
3411 	if (gtk_text_iter_forward_to_line_end(&end)) {
3412 	    s = gtk_text_buffer_get_text(tbuf, &prev, &end, FALSE);
3413 	    if (s != NULL) {
3414 		if (i == 0 && s[strlen(s)-1] == ')') {
3415 		    rparen = 1;
3416 		}
3417 		if (get_word_and_cont(s, word, pcont)) {
3418 		    pcont = NULL;
3419 		}
3420 		g_free(s);
3421 	    }
3422 	}
3423 
3424 	if (line_continues_previous(tbuf, prev)) {
3425 	    /* back up one line further */
3426 	    *word = '\0';
3427 	}
3428 
3429 	if (*word != '\0' && leadspace != NULL) {
3430 	    *leadspace = leading_spaces_at_iter(tbuf, &prev, word);
3431 	}
3432 	i++;
3433     }
3434 
3435     if (rparen && !strcmp(word, "function")) {
3436 	/* Revise our judgement: looks like we must be on the
3437 	   first line following a function signature, so we do
3438 	   not want the deep indent suitable for a continuation
3439 	   signature line.
3440 	*/
3441 	*leadspace = 0;
3442     }
3443 
3444     return word;
3445 }
3446 
3447 /* Is the insertion point at the start of a line, or in a white-space
3448    field to the left of any non-space characters?  If so, we'll trying
3449    inserting a "smart" soft tab in response to the Tab key. If not,
3450    and @comp_ok is non-NULL, we'll check whether gtksourceview
3451    completion seems possible at the current insertion point.
3452 */
3453 
maybe_insert_smart_tab(windata_t * vwin,int * comp_ok)3454 static int maybe_insert_smart_tab (windata_t *vwin,
3455 				   int *comp_ok)
3456 {
3457     GtkTextBuffer *tbuf;
3458     GtkTextMark *mark;
3459     GtkTextIter start, end;
3460     int curr_nsp = 0;
3461     int pos, ret = 0;
3462 
3463     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
3464 
3465     if (gtk_text_buffer_get_selection_bounds(tbuf, &start, &end)) {
3466 	/* don't do this if there's a selection in place */
3467 	return 0;
3468     }
3469 
3470     /* find @pos, the line-index of the insertion point */
3471     mark = gtk_text_buffer_get_insert(tbuf);
3472     gtk_text_buffer_get_iter_at_mark(tbuf, &end, mark);
3473     pos = gtk_text_iter_get_line_offset(&end);
3474 
3475     if (pos == 0) {
3476 	/* we're at the left margin, OK */
3477 	ret = 1;
3478     } else {
3479 	gchar *chunk;
3480 
3481 	start = end;
3482 	gtk_text_iter_set_line_offset(&start, 0);
3483 	/* grab the text between line start and insertion point */
3484 	chunk = gtk_text_buffer_get_text(tbuf, &start, &end, FALSE);
3485 	if (strspn(chunk, " \t") == pos) {
3486 	    /* set @ret if this chunk is just white space */
3487 	    ret = 1;
3488 	} else if (comp_ok != NULL && pos > 1) {
3489 	    /* follow-up: is the context OK for completion? */
3490 	    *comp_ok = !isspace(chunk[pos-1]) && !isspace(chunk[pos-2]);
3491 	}
3492 	g_free(chunk);
3493     }
3494 
3495     if (ret) {
3496 	/* OK, let's insert a smart tab */
3497 	GtkTextIter prev = start;
3498 	char *s, thisword[9] = {0};
3499 	char prevword[9];
3500 	int contd = 0;
3501 	int i, nsp = 0;
3502 
3503 	s = textview_get_current_line(vwin->text, 1);
3504 	if (s != NULL) {
3505 #if TABDEBUG > 1
3506 	    fprintf(stderr, "*** maybe_insert_smart_tab: "
3507 		    "current line = '%s'\n", s);
3508 #endif
3509 	    sscanf(s, "%8s", thisword);
3510 	    curr_nsp = strspn(s, " \t");
3511 	    g_free(s);
3512 	}
3513 
3514 	get_previous_line_start_word(prevword, tbuf, prev, &nsp, &contd);
3515 
3516 	if (contd) {
3517 	    nsp += 2;
3518 	} else {
3519 	    nsp += incremental_leading_spaces(prevword, thisword);
3520 #if TABDEBUG > 1
3521 	    fprintf(stderr, "    leading spaces: nsp + incr = %d, curr_nsp %d\n",
3522 		    nsp, curr_nsp);
3523 #endif
3524 	}
3525 
3526 	if (curr_nsp > 0) {
3527 	    end = start;
3528 	    gtk_text_iter_set_line_offset(&end, curr_nsp);
3529 	    gtk_text_buffer_delete(tbuf, &start, &end);
3530 	}
3531 	gtk_text_iter_set_line_offset(&start, 0);
3532 	for (i=0; i<nsp; i++) {
3533 	    gtk_text_buffer_insert(tbuf, &start, " ", -1);
3534 	}
3535     }
3536 
3537     return ret;
3538 }
3539 
3540 #ifdef HAVE_GTKSV_COMPLETION
3541 
3542 /* Is the insertion point directly preceded by at least two
3543    non-space characters? If so we have a possible candidate for
3544    completion via gtksourceview. We come here only if "smart
3545    tab" is not in force; otherwise this check is rolled into
3546    the check for inserting a smart tab.
3547 
3548    This function is public because it's called from console.c
3549    as well as internally.
3550 */
3551 
maybe_try_completion(windata_t * vwin)3552 int maybe_try_completion (windata_t *vwin)
3553 {
3554     GtkTextBuffer *tbuf;
3555     GtkTextMark *mark;
3556     GtkTextIter start, end;
3557     int pos, ret = 0;
3558 
3559     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
3560 
3561     if (gtk_text_buffer_get_selection_bounds(tbuf, &start, &end)) {
3562 	/* don't do this if there's a selection in place? */
3563 	return 0;
3564     }
3565 
3566     /* find @pos, the line-index of the insertion point */
3567     mark = gtk_text_buffer_get_insert(tbuf);
3568     gtk_text_buffer_get_iter_at_mark(tbuf, &end, mark);
3569     pos = gtk_text_iter_get_line_offset(&end);
3570 
3571     if (pos > 1) {
3572 	const char *test;
3573 	gchar *chunk;
3574 
3575 	start = end;
3576 	gtk_text_iter_set_line_offset(&start, 0);
3577 	test = chunk = gtk_text_buffer_get_text(tbuf, &start, &end, FALSE);
3578 	if (vwin->role == CONSOLE && !strncmp(chunk, "? ", 2)) {
3579 	    test += 2;
3580 	    pos -= 2;
3581 	}
3582 	ret = !isspace(test[pos-1]) && !isspace(test[pos-2]);
3583 	g_free(chunk);
3584     }
3585 
3586     return ret;
3587 }
3588 
3589 #endif /* HAVE_GTKSV_COMPLETION */
3590 
leftchar(guint k)3591 static char leftchar (guint k)
3592 {
3593     return k == GDK_parenleft ? '(' :
3594 	k == GDK_bracketleft ? '[' : '{';
3595 }
3596 
rightchar(guint k)3597 static char rightchar (guint k)
3598 {
3599     return k == GDK_parenleft ? ')' :
3600 	k == GDK_bracketleft ? ']' : '}';
3601 }
3602 
3603 /* Is the insertion point at the end of a line? If so, we'll
3604    auto-insert a matching right bracket and move the cursor
3605    back before it.
3606 */
3607 
maybe_insert_auto_bracket(windata_t * vwin,guint keyval)3608 static int maybe_insert_auto_bracket (windata_t *vwin,
3609 				      guint keyval)
3610 {
3611     GtkTextBuffer *tbuf;
3612     GtkTextMark *mark;
3613     GtkTextIter start;
3614     gchar *chunk = NULL;
3615     int ret = 0;
3616 
3617     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
3618 
3619     if (gtk_text_buffer_get_has_selection(tbuf)) {
3620 	return 0;
3621     }
3622 
3623     mark = gtk_text_buffer_get_insert(tbuf);
3624     gtk_text_buffer_get_iter_at_mark(tbuf, &start, mark);
3625 
3626     if (gtk_text_iter_ends_line(&start)) {
3627 	ret = 1;
3628     } else {
3629 	GtkTextIter end = start;
3630 
3631 	gtk_text_iter_forward_to_line_end(&end);
3632 	chunk = gtk_text_buffer_get_text(tbuf, &start, &end, FALSE);
3633 	if (chunk != NULL) {
3634 	    ret = strspn(chunk, " \t\n") == strlen(chunk);
3635 	    g_free(chunk);
3636 	}
3637     }
3638 
3639     if (ret) {
3640 	char s[2] = {0};
3641 
3642 	s[0] = leftchar(keyval);
3643 	gtk_text_buffer_insert(tbuf, &start, s, -1);
3644 	s[0] = rightchar(keyval);
3645 	gtk_text_buffer_insert(tbuf, &start, s, -1);
3646 	gtk_text_iter_backward_char(&start);
3647 	gtk_text_buffer_place_cursor(tbuf, &start);
3648     }
3649 
3650     return ret;
3651 }
3652 
3653 /* On "Enter" in script editing, try to compute the correct indent
3654    level for the current line, and make an adjustment if it's not
3655    already right. We also attempt to place the cursor at the
3656    appropriate indent on the next, new line.
3657 */
3658 
script_electric_enter(windata_t * vwin)3659 static gboolean script_electric_enter (windata_t *vwin)
3660 {
3661     char *s = NULL;
3662     int targsp = 0;
3663     gboolean ret = FALSE;
3664 
3665     if (!smarttab || in_foreign_land(vwin->text)) {
3666 	return FALSE;
3667     }
3668 
3669 #if TABDEBUG
3670     fprintf(stderr, "*** script_electric_enter\n");
3671 #endif
3672 
3673     s = textview_get_current_line(vwin->text, 0);
3674 
3675     if (s != NULL && *s != '\0') {
3676 	/* work on the line that starts with @thisword */
3677 	GtkTextBuffer *tbuf;
3678 	GtkTextMark *mark;
3679 	GtkTextIter start, end;
3680 	char thisword[9];
3681 	char prevword[9];
3682 	int i, diff, nsp, incr;
3683 	int ipos, k, contd = 0;
3684 
3685 	tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
3686 	mark = gtk_text_buffer_get_insert(tbuf);
3687 	gtk_text_buffer_get_iter_at_mark(tbuf, &start, mark);
3688 	ipos = gtk_text_iter_get_line_offset(&start);
3689 	gtk_text_iter_set_line_offset(&start, 0);
3690 
3691 	*thisword = '\0';
3692 	sscanf(s, "%8s", thisword);
3693 	nsp = count_leading_spaces(s);
3694 	get_previous_line_start_word(prevword, tbuf, start, &targsp, &contd);
3695 
3696 #if TABDEBUG
3697 	if (contd) {
3698 	    fprintf(stderr, "prevword='%s', leading space = %d\n",
3699 		    prevword, targsp);
3700 	    fprintf(stderr, "got line continuation\n");
3701 	} else {
3702 	    fprintf(stderr, "thisword='%s', leading space = %d\n",
3703 		    thisword, nsp);
3704 	    fprintf(stderr, "prevword='%s', leading space = %d\n",
3705 		    prevword, targsp);
3706 	}
3707 #endif
3708 
3709 	if (contd) {
3710 	    incr = 2;
3711 	} else {
3712 #if TABDEBUG > 1
3713 	    fprintf(stderr, "getting leading spaces ('%s', '%s')\n",
3714 		    prevword, thisword);
3715 #endif
3716 	    incr = incremental_leading_spaces(prevword, thisword);
3717 	}
3718 
3719 	targsp += incr;
3720 	if (targsp < 0) {
3721 	    /* indentation messed up? */
3722 	    targsp = 0;
3723 	}
3724 
3725 	diff = nsp - targsp;
3726 
3727 #if TABDEBUG
3728 	fprintf(stderr, "incr = %d: after increment targsp = %d, diff = %d\n",
3729 		incr, targsp, diff);
3730 #endif
3731 	if (diff > 0) {
3732 	    end = start;
3733 	    gtk_text_iter_forward_chars(&end, diff);
3734 	    gtk_text_buffer_delete(tbuf, &start, &end);
3735 	} else if (diff < 0) {
3736 	    diff = -diff;
3737 	    for (i=0; i<diff; i++) {
3738 		gtk_text_buffer_insert(tbuf, &start, " ", -1);
3739 	    }
3740 	}
3741 
3742 	/* try to arrange correct indent on the new line? */
3743 	if (ipos == 0) {
3744 	    k = targsp + incremental_leading_spaces(prevword, "");
3745 	} else {
3746 	    k = targsp + incremental_leading_spaces(thisword, "");
3747 	}
3748 #if TABDEBUG
3749 	fprintf(stderr, "new line indent: k = %d\n", k);
3750 #endif
3751 	gtk_text_buffer_begin_user_action(tbuf);
3752 	gtk_text_buffer_get_iter_at_mark(tbuf, &start, mark);
3753 	gtk_text_buffer_insert(tbuf, &start, "\n", -1);
3754 	for (i=0; i<k; i++) {
3755 	    gtk_text_buffer_insert(tbuf, &start, " ", -1);
3756 	}
3757 	gtk_text_buffer_place_cursor(tbuf, &start);
3758 	gtk_text_buffer_end_user_action(tbuf);
3759 	mark = gtk_text_buffer_get_insert(tbuf);
3760 	gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(vwin->text), mark);
3761 	ret = TRUE;
3762     }
3763 
3764     g_free(s);
3765 
3766     return ret;
3767 }
3768 
3769 /* Handler for the Tab key when editing a gretl script: we
3770    may want to insert a "smart tab", or take Tab as a
3771    request for gtksourceview completion.
3772 */
3773 
3774 #ifdef HAVE_GTKSV_COMPLETION
3775 
script_tab_handler(windata_t * vwin,GdkEvent * event)3776 static gboolean script_tab_handler (windata_t *vwin, GdkEvent *event)
3777 {
3778     int ucomp;
3779 
3780     g_return_val_if_fail(GTK_IS_TEXT_VIEW(vwin->text), FALSE);
3781 
3782     if (in_foreign_land(vwin->text)) {
3783 	return FALSE;
3784     } else if (((GdkEventKey *) event)->state & GDK_SHIFT_MASK) {
3785 	return FALSE;
3786     }
3787 
3788     ucomp = (hansl_completion == COMPLETE_USER);
3789 
3790     if (smarttab) {
3791 	int comp_ok = 0;
3792 	int *ptr = ucomp ? &comp_ok : NULL;
3793 
3794 	if (maybe_insert_smart_tab(vwin, ptr)) {
3795 	    return TRUE;
3796 	} else if (comp_ok) {
3797 	    call_user_completion(vwin->text);
3798 	    return TRUE;
3799 	}
3800     } else if (ucomp && maybe_try_completion(vwin)) {
3801 	call_user_completion(vwin->text);
3802 	return TRUE;
3803     }
3804 
3805     return FALSE;
3806 }
3807 
3808 #else
3809 
script_tab_handler(windata_t * vwin,GdkEvent * event)3810 static gboolean script_tab_handler (windata_t *vwin, GdkEvent *event)
3811 {
3812     g_return_val_if_fail(GTK_IS_TEXT_VIEW(vwin->text), FALSE);
3813 
3814     if (in_foreign_land(vwin->text)) {
3815 	return FALSE;
3816     } else if (((GdkEventKey *) event)->state & GDK_SHIFT_MASK) {
3817 	return FALSE;
3818     }
3819 
3820     if (smarttab && maybe_insert_smart_tab(vwin, NULL)) {
3821 	return TRUE;
3822     }
3823 
3824     return FALSE;
3825 }
3826 
3827 #endif /* HAVE_GTKSV_COMPLETION or not */
3828 
script_bracket_handler(windata_t * vwin,guint keyval)3829 gboolean script_bracket_handler (windata_t *vwin, guint keyval)
3830 {
3831     if (maybe_insert_auto_bracket(vwin, keyval)) {
3832 	return TRUE;
3833     } else {
3834 	return FALSE;
3835     }
3836 }
3837 
3838 /* Return a listing of the available gtksourceview style
3839    ids for use in the gretl preferences dialog.
3840 */
3841 
get_sourceview_style_ids(int * n)3842 const char **get_sourceview_style_ids (int *n)
3843 {
3844     GtkSourceStyleSchemeManager *mgr;
3845     const gchar * const *ids = NULL;
3846 
3847     ensure_sourceview_path(NULL);
3848 
3849     *n = 0;
3850 
3851     mgr = gtk_source_style_scheme_manager_get_default();
3852     if (mgr != NULL) {
3853 	int i = 0;
3854 
3855 	ids = gtk_source_style_scheme_manager_get_scheme_ids(mgr);
3856 	if (ids != NULL) {
3857 	    while (ids[i] != NULL) i++;
3858 	    *n = i;
3859 	}
3860     }
3861 
3862     return (const char **) ids;
3863 }
3864 
get_graph_theme_ids(int * n)3865 const char **get_graph_theme_ids (int *n)
3866 {
3867     static char **S = NULL;
3868     static int n_found;
3869 
3870     if (S != NULL) {
3871 	*n = n_found;
3872     } else {
3873 	gchar *path;
3874 	GDir *dir;
3875 
3876 	*n = 0;
3877 	path = g_build_filename(gretl_home(), "data", "gnuplot", NULL);
3878 	dir = gretl_opendir(path);
3879 
3880 	S = strings_array_new(1);
3881 	S[0] = gretl_strdup("classic");
3882 	*n = 1;
3883 
3884 	if (dir != NULL) {
3885 	    const gchar *fname;
3886 	    gchar *tmp, *p;
3887 	    int err = 0;
3888 
3889 	    while (!err && (fname = g_dir_read_name(dir))) {
3890 		if (!strncmp(fname, "default.", 8) ||
3891 		    !strncmp(fname, "classic.", 8)) {
3892 		    continue;
3893 		}
3894 		if (has_suffix(fname, ".gpsty")) {
3895 		    tmp = g_strdup(fname);
3896 		    p = strstr(tmp, ".gpsty");
3897 		    *p = '\0';
3898 		    err = strings_array_add(&S, n, tmp);
3899 		    g_free(tmp);
3900 		}
3901 	    }
3902 	    g_dir_close(dir);
3903 	}
3904 	n_found = *n;
3905 	g_free(path);
3906     }
3907 
3908     return (const char **) S;
3909 }
3910 
call_prefs_dialog(GtkWidget * w,windata_t * vwin)3911 static void call_prefs_dialog (GtkWidget *w, windata_t *vwin)
3912 {
3913     preferences_dialog(TAB_EDITOR, NULL, vwin_toplevel(vwin));
3914 }
3915 
3916 static GtkWidget *
build_script_popup(windata_t * vwin,struct textbit ** ptb)3917 build_script_popup (windata_t *vwin, struct textbit **ptb)
3918 {
3919     const char *items[] = {
3920 	N_("Comment line"),
3921 	N_("Uncomment line"),
3922 	N_("Comment region"),
3923 	N_("Uncomment region"),
3924     };
3925     GtkWidget *pmenu = NULL;
3926     struct textbit *tb = NULL;
3927     GtkWidget *item;
3928 
3929     g_return_val_if_fail(GTK_IS_TEXT_VIEW(vwin->text), NULL);
3930 
3931     /* "generic" text window menu -- we may add to this */
3932     pmenu = build_text_popup(vwin);
3933 
3934     if (foreign_script_role(vwin->role)) {
3935 	*ptb = NULL;
3936 	goto dock_undock;
3937     }
3938 
3939     tb = vwin_get_textbit(vwin, AUTO_SELECT_LINE);
3940     if (tb == NULL) {
3941 	*ptb = NULL;
3942 	return pmenu;
3943     }
3944 
3945     tb->commented = text_is_commented(tb->chunk);
3946 
3947     if (tb->commented > 0 && !editing_hansl(vwin->role)) {
3948 	g_free(tb->chunk);
3949 	free(tb);
3950 	*ptb = NULL;
3951 	goto dock_undock;
3952     }
3953 
3954     *ptb = tb;
3955 
3956     if (tb->commented <= 0 && vwin->role != EDIT_PKG_CODE) {
3957 	/* we have some uncommented material: allow exec option */
3958 	if (tb->selected) {
3959 	    item = gtk_menu_item_new_with_label(_("Execute region"));
3960 	} else {
3961 	    item = gtk_menu_item_new_with_label(_("Execute line"));
3962 	}
3963 	g_signal_connect(G_OBJECT(item), "activate",
3964 			 G_CALLBACK(exec_script_text),
3965 			 *ptb);
3966 	gtk_widget_show(item);
3967 	gtk_menu_shell_append(GTK_MENU_SHELL(pmenu), item);
3968     }
3969 
3970     if (editing_hansl(vwin->role) && tb->commented >= 0) {
3971 	/* material is either all commented or all uncommented:
3972 	   allow comment/uncomment option
3973 	*/
3974 	int i = (tb->selected && !tb->commented)? 2 :
3975 	    (tb->selected && tb->commented)? 3 :
3976 	    (!tb->selected && !tb->commented)? 0 : 1;
3977 
3978 	item = gtk_menu_item_new_with_label(_(items[i]));
3979 	g_signal_connect(G_OBJECT(item), "activate",
3980 			 G_CALLBACK(comment_or_uncomment_text),
3981 			 *ptb);
3982 	gtk_widget_show(item);
3983 	gtk_menu_shell_append(GTK_MENU_SHELL(pmenu), item);
3984     }
3985 
3986     if (editing_hansl(vwin->role)) {
3987 	if (tb->selected) {
3988 	    item = gtk_menu_item_new_with_label(smarttab?
3989 						_("Auto-indent region") :
3990 						_("Indent region"));
3991 	    g_signal_connect(G_OBJECT(item), "activate",
3992 			     G_CALLBACK(indent_region),
3993 			     *ptb);
3994 	    gtk_widget_show(item);
3995 	    gtk_menu_shell_append(GTK_MENU_SHELL(pmenu), item);
3996 	}
3997 
3998 	if (!smarttab && tb->selected && text_is_indented(tb->chunk)) {
3999 	    item = gtk_menu_item_new_with_label(_("Unindent region"));
4000 	    g_signal_connect(G_OBJECT(item), "activate",
4001 			     G_CALLBACK(unindent_region),
4002 			     *ptb);
4003 	    gtk_widget_show(item);
4004 	    gtk_menu_shell_append(GTK_MENU_SHELL(pmenu), item);
4005 	}
4006 
4007 	item = gtk_menu_item_new_with_label(_("Auto-indent script (Ctrl+I)"));
4008 	g_signal_connect(G_OBJECT(item), "activate",
4009 			 G_CALLBACK(auto_indent_script),
4010 			 vwin);
4011 	gtk_widget_show(item);
4012 	gtk_menu_shell_append(GTK_MENU_SHELL(pmenu), item);
4013     }
4014 
4015  dock_undock:
4016 
4017     if (GTK_IS_SOURCE_VIEW(vwin->text)) {
4018 	item = gtk_menu_item_new_with_label(_("Preferences..."));
4019 	g_signal_connect(G_OBJECT(item), "activate",
4020 			 G_CALLBACK(call_prefs_dialog),
4021 			 vwin);
4022 	gtk_widget_show(item);
4023 	gtk_menu_shell_append(GTK_MENU_SHELL(pmenu), item);
4024     }
4025 
4026     if (window_is_undockable(vwin)) {
4027 	add_undock_popup_item(pmenu, vwin);
4028     } else if (window_is_dockable(vwin)) {
4029 	add_dock_popup_item(pmenu, vwin);
4030     }
4031 
4032     return pmenu;
4033 }
4034 
destroy_textbit(GtkWidget ** pw,struct textbit * tc)4035 static gboolean destroy_textbit (GtkWidget **pw, struct textbit *tc)
4036 {
4037     if (tc != NULL) {
4038 	tc->vwin->popup = NULL;
4039 	g_free(tc->chunk);
4040 	free(tc);
4041     }
4042 
4043     return FALSE;
4044 }
4045 
4046 static gboolean
script_popup_handler(GtkWidget * w,GdkEventButton * event,gpointer p)4047 script_popup_handler (GtkWidget *w, GdkEventButton *event, gpointer p)
4048 {
4049     if (right_click(event)) {
4050 	windata_t *vwin = (windata_t *) p;
4051 	struct textbit *tc = NULL;
4052 
4053 	if (vwin->popup) {
4054 	    gtk_widget_destroy(vwin->popup);
4055 	    vwin->popup = NULL;
4056 	}
4057 
4058 	vwin->popup = build_script_popup(vwin, &tc);
4059 
4060 	if (vwin->popup != NULL) {
4061 	    gtk_menu_popup(GTK_MENU(vwin->popup), NULL, NULL, NULL, NULL,
4062 			   event->button, event->time);
4063 	    g_signal_connect(G_OBJECT(vwin->popup), "destroy",
4064 			     G_CALLBACK(destroy_textbit),
4065 			     tc);
4066 	}
4067 
4068 	return TRUE;
4069     }
4070 
4071     return FALSE;
4072 }
4073 
4074 enum {
4075     INSERT_NONE,
4076     INSERT_REF,
4077     INSERT_XREF,
4078     INSERT_FIG,
4079     INSERT_REPL,
4080     INSERT_LIT,
4081     INSERT_URL,
4082     INSERT_OPT,
4083     INSERT_ITAL,
4084     INSERT_SUP,
4085     INSERT_SUB,
4086     INSERT_TEXT,
4087     INSERT_MATH,
4088     INSERT_GUGLINK,
4089     INSERT_INPLINK,
4090     INSERT_GFRLINK,
4091     INSERT_BIBLINK,
4092     INSERT_ADBLINK,
4093     INSERT_MNULINK,
4094     INSERT_BOLD
4095 };
4096 
insert_help_figure(GtkTextBuffer * tbuf,GtkTextIter * iter,const char * fig)4097 static void insert_help_figure (GtkTextBuffer *tbuf, GtkTextIter *iter,
4098 				const char *fig)
4099 {
4100     char figfile[FILENAME_MAX];
4101     GdkPixbuf *pixbuf;
4102 
4103     sprintf(figfile, "%shelpfigs%c%s.png", gretl_home(),
4104 	    SLASH, fig);
4105 
4106     pixbuf = gdk_pixbuf_new_from_file(figfile, NULL);
4107 
4108     if (pixbuf != NULL) {
4109 	gtk_text_buffer_insert_pixbuf(tbuf, iter, pixbuf);
4110 	g_object_unref(pixbuf);
4111     }
4112 }
4113 
insert_math_content(GtkTextBuffer * tbuf,GtkTextIter * iter,const char * s,const char * indent)4114 static void insert_math_content (GtkTextBuffer *tbuf, GtkTextIter *iter,
4115 				 const char *s, const char *indent)
4116 {
4117     static char minus[4];
4118     gchar ubuf[6];
4119     gunichar c;
4120     int i, n;
4121 
4122     if (*minus == '\0') {
4123 	/* find the best representation of minus */
4124 	PangoFontDescription *pfd;
4125 
4126 	pfd = pango_font_description_from_string(helpfont);
4127 
4128 	if (font_has_symbol(pfd, 0x2212)) {
4129 	    /* preferred: unicode minus */
4130 	    minus[0] = 0xE2;
4131 	    minus[1] = 0x88;
4132 	    minus[2] = 0x92;
4133 	} else if (font_has_symbol(pfd, 0x2013)) {
4134 	    /* fallback: unicode endash */
4135 	    minus[0] = 0xE2;
4136 	    minus[1] = 0x80;
4137 	    minus[2] = 0x93;
4138 	} else {
4139 	    /* otherwise: plain old dash */
4140 	    minus[0] = '-';
4141 	}
4142 	pango_font_description_free(pfd);
4143     }
4144 
4145     n = g_utf8_strlen(s, -1);
4146 
4147     for (i=0; i<n; i++) {
4148 	c = g_utf8_get_char(s);
4149 	if (*s == '-') {
4150 	    gtk_text_buffer_insert(tbuf, iter, minus, -1);
4151 	} else {
4152 	    memset(ubuf, 0, sizeof ubuf);
4153 	    g_unichar_to_utf8(c, ubuf);
4154 	    if (g_unichar_isalpha(c)) {
4155 		gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, ubuf, -1,
4156 							 "italic", indent, NULL);
4157 	    } else {
4158 		gtk_text_buffer_insert(tbuf, iter, ubuf, -1);
4159 	    }
4160 	}
4161 	if (i < n-1) {
4162 	    s = g_utf8_find_next_char(s, NULL);
4163 	}
4164     }
4165 }
4166 
insert_tagged_text(GtkTextBuffer * tbuf,GtkTextIter * iter,const char * s,int ins,const char * indent)4167 static void insert_tagged_text (GtkTextBuffer *tbuf, GtkTextIter *iter,
4168 				const char *s, int ins, const char *indent)
4169 {
4170     const char *ftag = NULL;
4171 
4172     switch (ins) {
4173     case INSERT_ITAL:
4174     case INSERT_MATH: /* FIXME */
4175 	ftag = "italic";
4176 	break;
4177     case INSERT_REPL:
4178 	ftag = "replaceable";
4179 	break;
4180     case INSERT_LIT:
4181 	ftag = "literal";
4182 	break;
4183     case INSERT_OPT:
4184 	ftag = "optflag";
4185 	break;
4186     case INSERT_SUP:
4187 	ftag = "superscript";
4188 	break;
4189     case INSERT_SUB:
4190 	if (integer_string(s)) {
4191 	    ftag = "subscript-numeral";
4192 	} else {
4193 	    ftag = "subscript";
4194 	}
4195 	break;
4196     case INSERT_BOLD:
4197 	ftag = "heading";
4198 	break;
4199     default:
4200 	break;
4201     }
4202 
4203 #ifdef G_OS_WIN32
4204     if (ins == INSERT_OPT) {
4205 	/* Unicode word joiner not supported? Try zero width
4206 	   non breaking space instead */
4207 	char tmp[32];
4208 
4209 	strcpy(tmp, s);
4210 	tmp[2] = 0xEF;
4211 	tmp[3] = 0xBB;
4212 	tmp[4] = 0xBF;
4213 
4214 	gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, tmp, -1,
4215 						 ftag, indent, NULL);
4216 	return;
4217     }
4218 #endif
4219 
4220     if (ftag != NULL) {
4221 	gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, s, -1,
4222 						 ftag, indent, NULL);
4223     }
4224 }
4225 
get_string_and_instruction(const char * p,int * ins)4226 static gchar *get_string_and_instruction (const char *p, int *ins)
4227 {
4228     gchar *str = NULL;
4229 
4230     *ins = INSERT_NONE;
4231 
4232     if (!strncmp(p, "ref", 3)) {
4233 	*ins = INSERT_REF;
4234     } else if (!strncmp(p, "xrf", 3)) {
4235 	*ins = INSERT_XREF;
4236     } else if (!strncmp(p, "fig", 3)) {
4237 	*ins = INSERT_FIG;
4238     } else if (!strncmp(p, "itl", 3)) {
4239 	*ins = INSERT_ITAL;
4240     } else if (!strncmp(p, "var", 3)) {
4241 	*ins = INSERT_REPL;
4242     } else if (!strncmp(p, "lit", 3)) {
4243 	*ins = INSERT_LIT;
4244     } else if (!strncmp(p, "url", 3)) {
4245 	*ins = INSERT_URL;
4246     } else if (!strncmp(p, "opt", 3)) {
4247 	*ins = INSERT_OPT;
4248     } else if (!strncmp(p, "sup", 3)) {
4249 	*ins = INSERT_SUP;
4250     } else if (!strncmp(p, "sub", 3)) {
4251 	*ins = INSERT_SUB;
4252     } else if (!strncmp(p, "mth", 3)) {
4253 	*ins = INSERT_MATH;
4254     } else if (!strncmp(p, "pdf", 3)) {
4255 	*ins = INSERT_GUGLINK;
4256     } else if (!strncmp(p, "inp", 3)) {
4257 	*ins = INSERT_INPLINK;
4258     } else if (!strncmp(p, "gfr", 3)) {
4259 	*ins = INSERT_GFRLINK;
4260     } else if (!strncmp(p, "bib", 3)) {
4261 	*ins = INSERT_BIBLINK;
4262     } else if (!strncmp(p, "adb", 3)) {
4263 	*ins = INSERT_ADBLINK;
4264     } else if (!strncmp(p, "mnu", 3)) {
4265 	*ins = INSERT_MNULINK;
4266     } else if (!strncmp(p, "hd1", 3)) {
4267 	*ins = INSERT_BOLD;
4268     }
4269 
4270     if (*ins != INSERT_NONE) {
4271 	int i;
4272 
4273 	p += 5; /* skip 'tag="' */
4274 	for (i=0; p[i]; i++) {
4275 	    if (p[i] == '"' && p[i+1] == '>') {
4276 		break;
4277 	    }
4278 	}
4279 	str = g_strndup(p, i);
4280     }
4281 
4282     return str;
4283 }
4284 
get_code_skip(const char * s)4285 static int get_code_skip (const char *s)
4286 {
4287     int skip = 5;
4288 
4289     while (*s) {
4290 	if (*s == '\n') {
4291 	    skip++;
4292 	    break;
4293 	} else if (isspace(*s)) {
4294 	    skip++;
4295 	}
4296 	s++;
4297     }
4298 
4299     return skip;
4300 }
4301 
command_word_index(char * s)4302 static int command_word_index (char *s)
4303 {
4304     int i = gretl_command_number(s);
4305 
4306     if (i == 0) {
4307 	i = extra_command_number(s);
4308 	if (i < 0) {
4309 	    i = 0;
4310 	} else {
4311 	    /* e.g. "MIDAS_list" -> "MIDAS list", for
4312 	       display purposes */
4313 	    gretl_charsub(s, '_', ' ');
4314 	}
4315     }
4316 
4317     return i;
4318 }
4319 
4320 /* return non-zero if we inserted any hyperlinks */
4321 
4322 static gboolean
insert_text_with_markup(GtkTextBuffer * tbuf,GtkTextIter * iter,const char * s,int role)4323 insert_text_with_markup (GtkTextBuffer *tbuf, GtkTextIter *iter,
4324 			 const char *s, int role)
4325 {
4326     gboolean ret = FALSE;
4327     gchar *targ = NULL;
4328     const char *indent = NULL;
4329     const char *p;
4330     int code = 0;
4331     int mono = 0;
4332     int itarg, ins;
4333 
4334     while ((p = strstr(s, "<"))) {
4335 	int skip = 0;
4336 
4337 	if (code) {
4338 	    gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, s, p - s,
4339 						     "code", indent, NULL);
4340 	} else if (mono) {
4341 	    gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, s, p - s,
4342 						     "mono", indent, NULL);
4343 	} else {
4344 	    gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, s, p - s,
4345 						     "text", indent, NULL);
4346 	}
4347 
4348 	p++;
4349 
4350 	if (*p == '@') {
4351 	    /* "atomic" markup */
4352 	    targ = get_string_and_instruction(p + 1, &ins);
4353 	    if (ins == INSERT_REF) {
4354 		if (function_help(role)) {
4355 		    /* FIXME? */
4356 		    itarg = function_help_index_from_word(targ, role);
4357 		} else {
4358 		    itarg = command_word_index(targ);
4359 		}
4360 		ret = insert_link(tbuf, iter, targ, itarg, indent);
4361 	    } else if (ins == INSERT_XREF) {
4362 		if (function_help(role)) {
4363 		    itarg = command_word_index(targ);
4364 		} else {
4365 		    itarg = function_help_index_from_word(targ, FUNC_HELP);
4366 		}
4367 		ret = insert_xlink(tbuf, iter, targ, itarg, indent);
4368 	    } else if (ins == INSERT_URL) {
4369 		ret = insert_link(tbuf, iter, targ, EXT_PAGE, indent);
4370 	    } else if (ins == INSERT_GUGLINK) {
4371 		ret = insert_link(tbuf, iter, targ, GUIDE_PAGE, indent);
4372 	    } else if (ins == INSERT_INPLINK) {
4373 		ret = insert_link(tbuf, iter, targ, SCRIPT_PAGE, indent);
4374 	    } else if (ins == INSERT_BIBLINK) {
4375 		ret = insert_link(tbuf, iter, targ, BIB_PAGE, indent);
4376 	    } else if (ins == INSERT_GFRLINK) {
4377 		ret = insert_xlink(tbuf, iter, targ, GFR_PAGE, indent);
4378 	    } else if (ins == INSERT_ADBLINK) {
4379 		ret = insert_link(tbuf, iter, targ, PDF_PAGE, indent);
4380 	    } else if (ins == INSERT_MNULINK) {
4381 		ret = insert_link(tbuf, iter, targ, MNU_PAGE, indent);
4382 	    } else if (ins == INSERT_FIG) {
4383 		insert_help_figure(tbuf, iter, targ);
4384 	    } else if (ins == INSERT_MATH) {
4385 		insert_math_content(tbuf, iter, targ, indent);
4386 	    } else if (ins != INSERT_NONE) {
4387 		insert_tagged_text(tbuf, iter, targ, ins, indent);
4388 	    }
4389 	    skip = 8 + strlen(targ);
4390 	} else if (!strncmp(p, "indent", 6)) {
4391 	    indent = "indented";
4392 	    skip = 7 + (*(p+7) == '\n');
4393 	} else if (!strncmp(p, "/indent", 7)) {
4394 	    indent = NULL;
4395 	    skip = 8 + (*(p+8) == '\n');
4396 	} else if (!strncmp(p, "code", 4)) {
4397 	    code = 1;
4398 	    skip = get_code_skip(p + 5);
4399 	} else if (!strncmp(p, "/code", 5)) {
4400 	    code = 0;
4401 	    skip = 6 + (*(p+6) == '\n');
4402 	} else if (!strncmp(p, "mono", 4)) {
4403 	    mono = 1;
4404 	    skip = get_code_skip(p + 5);
4405 	} else if (!strncmp(p, "/mono", 5)) {
4406 	    mono = 0;
4407 	    skip = 6 + (*(p+6) == '\n');
4408 	} else {
4409 	    /* literal "<" */
4410 	    gtk_text_buffer_insert(tbuf, iter, "<", 1);
4411 	}
4412 
4413 	if (targ != NULL) {
4414 	    g_free(targ);
4415 	    targ = NULL;
4416 	}
4417 	s = p + skip;
4418     }
4419 
4420     if (code) {
4421 	gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, s, -1,
4422 						 "code", indent, NULL);
4423     } else if (mono) {
4424 	gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, s, -1,
4425 						 "mono", indent, NULL);
4426     } else {
4427 	gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, s, -1,
4428 						 "text", indent, NULL);
4429     }
4430 
4431     return ret;
4432 }
4433 
grab_topic_buffer(const char * s)4434 static char *grab_topic_buffer (const char *s)
4435 {
4436     const char *p = strstr(s, "\n#");
4437     char *buf;
4438 
4439     if (p != NULL) {
4440 	buf = g_strndup(s, p - s);
4441     } else {
4442 	buf = g_strdup(s);
4443     }
4444 
4445     return buf;
4446 }
4447 
4448 /* Pull the appropriate chunk of help text out of the buffer attached
4449    to the help viewer and display it, if possible.  Return >= 0
4450    if we did OK, < 0 on failure.
4451 */
4452 
set_help_topic_buffer(windata_t * hwin,int pos,int en)4453 int set_help_topic_buffer (windata_t *hwin, int pos, int en)
4454 {
4455     GtkTextBuffer *textb;
4456     GtkTextIter iter;
4457     char line[256];
4458     gchar *hbuf;
4459     char *buf;
4460 
4461     push_backpage(hwin->text, hwin->active_var);
4462 
4463     textb = gretl_text_buf_new();
4464 
4465     if (pos == 0) {
4466 	/* no topic selected */
4467 	if (function_help(hwin->role)) {
4468 	    funcref_index_page(hwin, textb, en);
4469 	} else {
4470 	    cmdref_index_page(hwin, textb, en);
4471 	}
4472 	cursor_to_top(hwin);
4473 	return 0;
4474     }
4475 
4476     /* OK, so pos is non-zero */
4477 
4478     maybe_connect_help_signals(hwin, en);
4479     maybe_set_help_tabs(hwin);
4480 
4481     gtk_text_buffer_get_iter_at_offset(textb, &iter, 0);
4482 
4483     hbuf = (gchar *) hwin->data + pos;
4484 
4485     bufgets_init(hbuf);
4486     buf = bufgets(line, sizeof line, hbuf);
4487     bufgets_finalize(hbuf);
4488 
4489     if (buf == NULL) {
4490 	return -1;
4491     }
4492 
4493     tailstrip(line);
4494 
4495     if (gui_help(hwin->role)) {
4496 	/* topic heading: descriptive string */
4497 	gchar *p = quoted_help_string(line);
4498 
4499 	gtk_text_buffer_insert_with_tags_by_name(textb, &iter,
4500 						 p, -1,
4501 						 "heading", NULL);
4502 	free(p);
4503     } else {
4504 	/* topic heading: plain command word */
4505 	char hword[12];
4506 
4507 	sscanf(line + 2, "%11s", hword);
4508 	gtk_text_buffer_insert_with_tags_by_name(textb, &iter,
4509 						 hword, -1,
4510 						 "redtext", NULL);
4511     }
4512 
4513     if (function_help(hwin->role)) {
4514 	gtk_text_buffer_insert(textb, &iter, "\n\n", 2);
4515     } else {
4516 	gtk_text_buffer_insert(textb, &iter, "\n", 1);
4517     }
4518 
4519     buf = grab_topic_buffer(hbuf + strlen(line) + 1);
4520     if (buf == NULL) {
4521 	return -1;
4522     }
4523 
4524     insert_text_with_markup(textb, &iter, buf, hwin->role);
4525     free(buf);
4526 
4527     gtk_text_view_set_buffer(GTK_TEXT_VIEW(hwin->text), textb);
4528     maybe_connect_help_signals(hwin, en);
4529     cursor_to_top(hwin);
4530 
4531     return 1;
4532 }
4533 
gretl_viewer_set_formatted_buffer(windata_t * vwin,const char * buf)4534 void gretl_viewer_set_formatted_buffer (windata_t *vwin, const char *buf)
4535 {
4536     GtkTextBuffer *textb;
4537     GtkTextIter iter;
4538     gboolean links;
4539 
4540     textb = gretl_text_buf_new();
4541     gtk_text_buffer_get_iter_at_offset(textb, &iter, 0);
4542     links = insert_text_with_markup(textb, &iter, buf, FUNC_HELP);
4543 
4544     gtk_text_view_set_buffer(GTK_TEXT_VIEW(vwin->text), textb);
4545     cursor_to_top(vwin);
4546 
4547     if (links) {
4548 	connect_link_signals(vwin);
4549     }
4550 }
4551 
get_screen_height(void)4552 int get_screen_height (void)
4553 {
4554     static int screen_height;
4555 
4556     if (screen_height == 0) {
4557 	GdkScreen *s = gdk_screen_get_default();
4558 
4559 	if (s != NULL) {
4560 	    screen_height = gdk_screen_get_height(s);
4561 	}
4562     }
4563 
4564     return screen_height;
4565 }
4566 
set_max_text_width(windata_t * vwin,int width,int height)4567 static void set_max_text_width (windata_t *vwin,
4568 				int width,
4569 				int height)
4570 {
4571     GdkGeometry hints = {0};
4572 
4573     hints.max_width = width;
4574     hints.max_height = height * 3;
4575 
4576     gtk_window_set_geometry_hints(GTK_WINDOW(vwin->main),
4577 				  GTK_WIDGET(vwin->main),
4578 				  &hints,
4579 				  GDK_HINT_MAX_SIZE);
4580 }
4581 
4582 #define HDEBUG 0
4583 #define HELP_WRAP 1
4584 
create_text(windata_t * vwin,int hsize,int vsize,int nlines,gboolean editable)4585 void create_text (windata_t *vwin, int hsize, int vsize,
4586 		  int nlines, gboolean editable)
4587 {
4588     GtkTextBuffer *tbuf = gretl_text_buf_new();
4589     GtkWidget *w = gtk_text_view_new_with_buffer(tbuf);
4590     int role = vwin->role;
4591 
4592     vwin->text = w;
4593 
4594     /* in which cases should we do text wrapping? */
4595 
4596     if (help_role(role) || role == VIEW_PKG_INFO ||
4597 	role == VIEW_BIBITEM || role == VIEW_CODEBOOK ||
4598 #if HELP_WRAP
4599 	role == EDIT_PKG_HELP || role == EDIT_PKG_GHLP ||
4600 #endif
4601 	role == VIEW_DBNOMICS || role == IMPORT ||
4602 	role == VIEW_DBSEARCH) {
4603 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(w), GTK_WRAP_WORD);
4604     } else {
4605 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(w), GTK_WRAP_NONE);
4606     }
4607 
4608     if (role == VIEW_DBSEARCH) {
4609 	/* make @vwin discoverable via @w */
4610 	g_object_set_data(G_OBJECT(w), "vwin", vwin);
4611     }
4612 
4613     gtk_text_view_set_left_margin(GTK_TEXT_VIEW(w), 4);
4614     gtk_text_view_set_right_margin(GTK_TEXT_VIEW(w), 4);
4615 
4616     gtk_widget_modify_font(GTK_WIDGET(w), fixed_font);
4617 
4618 #if HDEBUG
4619     /* the incoming @hsize is expressed in characters */
4620     fprintf(stderr, "create_text: initial hsize = %d\n", hsize);
4621 #endif
4622 
4623     if (hsize > 0 || nlines > 0) {
4624 	int px, py;
4625 
4626 	get_char_width_and_height(w, &px, &py);
4627 	if (hsize > 0) {
4628 	    hsize *= px;
4629 	    hsize += 48;
4630 	}
4631 #if HDEBUG
4632 	fprintf(stderr, " px = %d, py = %d; hsize now = %d, nlines = %d\n",
4633 		px, py, hsize, nlines);
4634 #endif
4635 	if (nlines > 0) {
4636 	    /* Perhaps adjust how tall the window is? */
4637 	    int v1 = (nlines + 2) * py;
4638 	    int sv = get_screen_height();
4639 
4640 	    if (v1 > 0.85 * vsize && v1 <= 0.7 * sv) {
4641 		vsize = v1;
4642 	    } else if (v1 > 0.7 * sv) {
4643 		vsize = 0.7 * sv;
4644 	    }
4645 	} else if (role != VIEW_BIBITEM && vsize < 0.62 * hsize) {
4646 	    vsize = 0.62 * hsize;
4647 	}
4648     }
4649 
4650     if (hsize > 0 && vsize > 0) {
4651 	GtkWidget *vmain = vwin_toplevel(vwin);
4652 
4653 	if (window_is_tab(vwin)) {
4654 	    vsize += 15;
4655 	}
4656 #if HDEBUG
4657 	fprintf(stderr, " setting default size (%d, %d)\n", hsize, vsize);
4658 #endif
4659 	gtk_window_set_default_size(GTK_WINDOW(vmain), hsize, vsize);
4660 	if (role == EDIT_PKG_HELP || role == EDIT_PKG_GHLP) {
4661 	    /* for editing help files: limit the width of the window
4662 	       to discourage use of excessively long lines
4663 	    */
4664 	    set_max_text_width(vwin, hsize, vsize);
4665 	}
4666     }
4667 
4668     gtk_text_view_set_editable(GTK_TEXT_VIEW(w), editable);
4669     gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(w), editable);
4670 }
4671 
gretl_console_tags_new(void)4672 static GtkTextTagTable *gretl_console_tags_new (void)
4673 {
4674     GtkTextTagTable *table;
4675     GtkTextTag *tag;
4676 
4677     table = gtk_text_tag_table_new();
4678 
4679     tag = gtk_text_tag_new("prompt");
4680     g_object_set(tag, "foreground", "red", NULL);
4681     gtk_text_tag_table_add(table, tag);
4682 
4683     tag = gtk_text_tag_new("output");
4684     g_object_set(tag, "foreground", "black",
4685 		 "weight", PANGO_WEIGHT_NORMAL, NULL);
4686     gtk_text_tag_table_add(table, tag);
4687 
4688     return table;
4689 }
4690 
create_console(windata_t * vwin,int hsize,int vsize)4691 void create_console (windata_t *vwin, int hsize, int vsize)
4692 {
4693     static GtkTextTagTable *console_tags = NULL;
4694     GtkSourceLanguageManager *lm = NULL;
4695     GtkSourceBuffer *sbuf;
4696     GtkTextView *view;
4697     int cw;
4698 
4699     if (console_tags == NULL) {
4700 	console_tags = gretl_console_tags_new();
4701     }
4702 
4703     /* since 2018-06-09: use syntax highlighting */
4704     lm = gtk_source_language_manager_get_default();
4705     ensure_sourceview_path(lm);
4706 
4707     sbuf = gtk_source_buffer_new(console_tags);
4708     gtk_source_buffer_set_highlight_matching_brackets(sbuf, TRUE);
4709     if (lm != NULL) {
4710 	g_object_set_data(G_OBJECT(sbuf), "languages-manager", lm);
4711 	set_style_for_buffer(sbuf, get_sourceview_style(), CONSOLE);
4712     }
4713 
4714     vwin->text = gtk_source_view_new_with_buffer(sbuf);
4715     vwin->sbuf = sbuf;
4716 
4717     view = GTK_TEXT_VIEW(vwin->text);
4718     gtk_text_view_set_wrap_mode(view, GTK_WRAP_NONE);
4719     gtk_text_view_set_left_margin(view, 4);
4720     gtk_text_view_set_right_margin(view, 4);
4721 
4722     gtk_widget_modify_font(GTK_WIDGET(vwin->text), fixed_font);
4723 
4724     cw = get_char_width(vwin->text);
4725     set_source_tabs(vwin->text, cw);
4726 
4727 #ifdef HAVE_GTKSV_COMPLETION
4728     if (console_completion) {
4729 	/* since 2021-06-11 */
4730 	set_sv_completion(vwin);
4731     }
4732 #endif
4733 
4734     if (hsize > 0) {
4735 	hsize *= cw;
4736 	hsize += 48; /* ?? */
4737     }
4738     if (vsize < 0.62 * hsize) {
4739 	vsize = 0.62 * hsize;
4740     }
4741 
4742     if (vwin->main != vwin->vbox && hsize > 0 && vsize > 0) {
4743 	GtkWidget *vmain = vwin_toplevel(vwin);
4744 
4745 	gtk_window_set_default_size(GTK_WINDOW(vmain), hsize, vsize);
4746     }
4747 
4748     sourceview_apply_language(vwin);
4749     gtk_text_view_set_editable(view, TRUE);
4750     gtk_text_view_set_cursor_visible(view, TRUE);
4751 }
4752 
text_set_word_wrap(GtkWidget * w,gboolean wrap)4753 void text_set_word_wrap (GtkWidget *w, gboolean wrap)
4754 {
4755     if (wrap) {
4756 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(w), GTK_WRAP_WORD);
4757     } else {
4758 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(w), GTK_WRAP_NONE);
4759     }
4760 }
4761 
text_table_setup(GtkWidget * vbox,GtkWidget * w)4762 void text_table_setup (GtkWidget *vbox, GtkWidget *w)
4763 {
4764     GtkWidget *sw;
4765 
4766     sw = gtk_scrolled_window_new(NULL, NULL);
4767     gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, FALSE);
4768     g_object_set_data(G_OBJECT(vbox), "sw", sw);
4769 
4770     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
4771 				   GTK_POLICY_AUTOMATIC,
4772 				   GTK_POLICY_AUTOMATIC);
4773     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
4774 					GTK_SHADOW_IN);
4775     gtk_container_add(GTK_CONTAINER(sw), w);
4776     gtk_widget_show(w);
4777     gtk_widget_show(sw);
4778 }
4779 
set_pane_text_properties(GtkTextView * w2,GtkTextView * w1)4780 static void set_pane_text_properties (GtkTextView *w2,
4781 				      GtkTextView *w1)
4782 {
4783     gtk_text_view_set_wrap_mode(w2, 0);
4784     gtk_text_view_set_left_margin(w2, 4);
4785     gtk_text_view_set_right_margin(w2, 4);
4786 
4787     gtk_widget_modify_font(GTK_WIDGET(w2), fixed_font);
4788 
4789     if (gtk_text_view_get_editable(w1)) {
4790 	gtk_text_view_set_editable(w2, TRUE);
4791 	gtk_text_view_set_cursor_visible(w2, TRUE);
4792     } else {
4793 	gtk_text_view_set_editable(w2, FALSE);
4794 	gtk_text_view_set_cursor_visible(w2, FALSE);
4795     }
4796 }
4797 
4798 /* divide a text window into two panes */
4799 
viewer_split_pane(windata_t * vwin,int vertical)4800 void viewer_split_pane (windata_t *vwin, int vertical)
4801 {
4802     GtkWidget *vbox = vwin->vbox;
4803     GtkWidget *view1 = vwin->text;
4804     GtkWidget *sw, *paned, *view2;
4805     GtkWidget *vmain;
4806     GtkTextBuffer *tbuf;
4807     gint width, height;
4808 
4809     sw = g_object_get_data(G_OBJECT(vbox), "sw");
4810 
4811     vmain = vwin_toplevel(vwin);
4812     gtk_window_get_size(GTK_WINDOW(vmain), &width, &height);
4813 
4814     g_object_ref(sw);
4815     gtk_container_remove(GTK_CONTAINER(vwin->vbox), sw);
4816 
4817     if (vertical) {
4818 	paned = gtk_hpaned_new();
4819     } else {
4820 	paned = gtk_vpaned_new();
4821     }
4822 
4823     gtk_container_set_border_width(GTK_CONTAINER(paned), 0);
4824     gtk_container_add(GTK_CONTAINER(vbox), paned);
4825 
4826     g_object_set_data(G_OBJECT(vwin->vbox), "paned", paned);
4827     g_object_set_data(G_OBJECT(vwin->vbox), "sw", NULL);
4828 
4829     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view1));
4830 
4831     if (GTK_IS_SOURCE_VIEW(view1)) {
4832 	view2 = gtk_source_view_new_with_buffer(GTK_SOURCE_BUFFER(tbuf));
4833     } else {
4834 	view2 = gtk_text_view_new_with_buffer(tbuf);
4835     }
4836 
4837     set_pane_text_properties(GTK_TEXT_VIEW(view2),
4838 			     GTK_TEXT_VIEW(view1));
4839 
4840     g_signal_connect(G_OBJECT(view2), "button-press-event",
4841 		     G_CALLBACK(text_popup_handler), vwin);
4842 
4843     gtk_paned_add1(GTK_PANED(paned), sw);
4844     g_object_unref(sw);
4845 
4846     sw = gtk_scrolled_window_new(NULL, NULL);
4847     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
4848 				   GTK_POLICY_AUTOMATIC,
4849 				   GTK_POLICY_AUTOMATIC);
4850     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
4851 					GTK_SHADOW_IN);
4852     gtk_paned_add2(GTK_PANED(paned), sw);
4853     gtk_container_add(GTK_CONTAINER(sw), view2);
4854 
4855     if (vertical) {
4856 	gtk_paned_set_position(GTK_PANED(paned), width / 2 - 5);
4857     } else {
4858 	gtk_paned_set_position(GTK_PANED(paned), height / 2 - 24);
4859     }
4860 
4861     gtk_widget_show_all(paned);
4862 }
4863 
4864 /* script output window: revert to a single pane */
4865 
viewer_close_pane(windata_t * vwin)4866 void viewer_close_pane (windata_t *vwin)
4867 {
4868     GtkWidget *sw, *paned;
4869 
4870     paned = g_object_get_data(G_OBJECT(vwin->vbox), "paned");
4871 
4872     /* grab the first child and reference it */
4873     sw = gtk_paned_get_child1(GTK_PANED(paned));
4874     g_object_ref(sw);
4875     gtk_container_remove(GTK_CONTAINER(paned), sw);
4876 
4877     /* remove the "paned" widget */
4878     gtk_widget_destroy(paned);
4879     g_object_set_data(G_OBJECT(vwin->vbox), "paned", NULL);
4880 
4881     /* and repack the scrolled window */
4882     gtk_container_add(GTK_CONTAINER(vwin->vbox), sw);
4883     g_object_set_data(G_OBJECT(vwin->vbox), "sw", sw);
4884     gtk_widget_show(sw);
4885     g_object_unref(sw);
4886 }
4887