"Fossies" - the Fresh Open Source Software Archive

Member "gretl-2020e/gui/textbuf.c" (22 Sep 2020, 114327 Bytes) of package /linux/misc/gretl-2020e.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "textbuf.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 2020d_vs_2020e.

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