/* * Copyright (c) by Allin Cottrell * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "gretl.h" #include "textbuf.h" #include #include #include #define GUIDE_PAGE 999 #define SCRIPT_PAGE 998 enum { PLAIN_TEXT, BLUE_TEXT, RED_TEXT }; #define gui_help(r) (r == GUI_HELP || r == GUI_HELP_EN) void text_set_cursor (GtkWidget *w, GdkCursorType cspec) { GdkWindow *win = gtk_text_view_get_window(GTK_TEXT_VIEW(w), GTK_TEXT_WINDOW_TEXT); if (cspec == 0) { gdk_window_set_cursor(win, NULL); } else { GdkCursor *cursor = gdk_cursor_new(cspec); gdk_window_set_cursor(win, cursor); gdk_cursor_destroy(cursor); } } void cursor_to_top (windata_t *vwin) { GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->w)); GtkTextIter start; GtkTextMark *mark; gtk_text_buffer_get_start_iter(buf, &start); gtk_text_buffer_place_cursor(buf, &start); mark = gtk_text_buffer_create_mark(buf, NULL, &start, FALSE); gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(vwin->w), mark, 0.0, FALSE, 0, 0); } gint get_char_width (GtkWidget *widget) { PangoLayout *pl; PangoContext *pc; GtkRcStyle *style; int width; pc = gtk_widget_get_pango_context(widget); style = gtk_widget_get_modifier_style(widget); pango_context_set_font_description(pc, style->font_desc); pl = pango_layout_new(pc); pango_layout_set_text(pl, "X", 1); pango_layout_get_pixel_size(pl, &width, NULL); g_object_unref(G_OBJECT(pl)); return width; } gchar *textview_get_text (GtkWidget *view) { GtkTextBuffer *tbuf; GtkTextIter start, end; g_return_val_if_fail(GTK_IS_TEXT_VIEW(view), NULL); tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)); gtk_text_buffer_get_start_iter(tbuf, &start); gtk_text_buffer_get_end_iter(tbuf, &end); return gtk_text_buffer_get_text(tbuf, &start, &end, FALSE); } gchar *textview_get_selection_or_all (GtkWidget *view, int *sel) { GtkTextBuffer *tbuf; GtkTextIter start, end; g_return_val_if_fail(GTK_IS_TEXT_VIEW(view), NULL); tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)); if (gtk_text_buffer_get_selection_bounds(tbuf, &start, &end)) { *sel = 1; return gtk_text_buffer_get_text(tbuf, &start, &end, FALSE); } else { *sel = 0; gtk_text_buffer_get_start_iter(tbuf, &start); gtk_text_buffer_get_end_iter(tbuf, &end); return gtk_text_buffer_get_text(tbuf, &start, &end, FALSE); } } int textview_set_text (GtkWidget *view, const gchar *text) { GtkTextBuffer *tbuf; g_return_val_if_fail(GTK_IS_TEXT_VIEW(view), 1); tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)); g_return_val_if_fail(tbuf != NULL, 1); if (text != NULL) { gtk_text_buffer_set_text(tbuf, text, -1); } else { gtk_text_buffer_set_text(tbuf, "", -1); } return 0; } int textview_set_cursor_at_line (GtkWidget *view, int line) { GtkTextBuffer *tbuf; GtkTextIter iter; g_return_val_if_fail(GTK_IS_TEXT_VIEW(view), 1); tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)); g_return_val_if_fail(tbuf != NULL, 1); gtk_text_buffer_get_iter_at_line(tbuf, &iter, line); gtk_text_buffer_place_cursor(tbuf, &iter); return 0; } int viewer_char_count (windata_t *vwin) { GtkTextBuffer *tbuf; tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->w)); return gtk_text_buffer_get_char_count(tbuf); } void text_paste (windata_t *vwin, guint u, GtkWidget *widget) { gchar *undo_buf = textview_get_text(vwin->w); gchar *old; old = g_object_get_data(G_OBJECT(vwin->w), "undo"); g_free(old); g_object_set_data(G_OBJECT(vwin->w), "undo", undo_buf); gtk_text_buffer_paste_clipboard(gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->w)), gtk_clipboard_get(GDK_NONE), NULL, TRUE); } void text_undo (windata_t *vwin, guint u, GtkWidget *widget) { gchar *old = NULL; if (vwin->sbuf != NULL) { if (gtk_source_buffer_can_undo(vwin->sbuf)) { gtk_source_buffer_undo(vwin->sbuf); } else { errbox(_("No undo information available")); } return; } old = g_object_steal_data(G_OBJECT(vwin->w), "undo"); if (old == NULL) { errbox(_("No undo information available")); } else { GtkTextBuffer *buf; GtkTextIter start, end; GtkTextMark *ins; buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->w)); ins = gtk_text_buffer_get_insert(buf); gtk_text_buffer_get_start_iter(buf, &start); gtk_text_buffer_get_end_iter(buf, &end); gtk_text_buffer_delete(buf, &start, &end); gtk_text_buffer_insert(buf, &start, old, strlen(old)); gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(vwin->w), ins, 0.0, TRUE, 0.1, 0.0); g_free(old); } } static int source_buffer_load_file (GtkSourceBuffer *sbuf, int role, FILE *fp) { char readbuf[MAXSTR], *chunk = NULL; GtkTextIter iter; int i = 0; gtk_source_buffer_begin_not_undoable_action(sbuf); gtk_text_buffer_set_text(GTK_TEXT_BUFFER(sbuf), "", 0); gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(sbuf), &iter, 0); memset(readbuf, 0, sizeof readbuf); while (fgets(readbuf, sizeof readbuf, fp)) { int len; #ifdef ENABLE_NLS if (!g_utf8_validate(readbuf, -1, NULL)) { if (i == 0) { if (role == GR_PLOT) { chunk = gp_locale_to_utf8(readbuf); } else { chunk = my_locale_to_utf8(readbuf); } i++; } else { if (role == GR_PLOT) { chunk = gp_locale_to_utf8_next(readbuf); } else { chunk = my_locale_to_utf8_next(readbuf); } } if (chunk == NULL) { continue; } } else { chunk = readbuf; } #else chunk = readbuf; #endif /* ENABLE_NLS */ len = strlen(chunk); if (len >= 2 && chunk[len - 2] == '\r') { chunk[len - 2] = '\n'; chunk[len - 1] = '\0'; } gtk_text_buffer_insert(GTK_TEXT_BUFFER(sbuf), &iter, chunk, -1); memset(readbuf, 0, sizeof readbuf); if (chunk != readbuf) { g_free(chunk); chunk = NULL; } } gtk_source_buffer_end_not_undoable_action(sbuf); gtk_text_buffer_set_modified(GTK_TEXT_BUFFER(sbuf), FALSE); /* move cursor to the beginning */ gtk_text_buffer_get_start_iter(GTK_TEXT_BUFFER(sbuf), &iter); gtk_text_buffer_place_cursor(GTK_TEXT_BUFFER(sbuf), &iter); return 0; } static int source_buffer_load_buf (GtkSourceBuffer *sbuf, const char *buf) { char line[MAXLINE]; GtkTextIter iter; gtk_source_buffer_begin_not_undoable_action(sbuf); gtk_text_buffer_set_text(GTK_TEXT_BUFFER(sbuf), "", 0); gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(sbuf), &iter, 0); bufgets_init(buf); while (bufgets(line, sizeof line, buf)) { gtk_text_buffer_insert(GTK_TEXT_BUFFER(sbuf), &iter, line, -1); gtk_text_buffer_insert(GTK_TEXT_BUFFER(sbuf), &iter, "\n", 1); } bufgets_finalize(buf); gtk_source_buffer_end_not_undoable_action(sbuf); gtk_text_buffer_set_modified(GTK_TEXT_BUFFER(sbuf), FALSE); /* move cursor to the beginning */ gtk_text_buffer_get_start_iter(GTK_TEXT_BUFFER(sbuf), &iter); gtk_text_buffer_place_cursor(GTK_TEXT_BUFFER(sbuf), &iter); return 0; } static void real_sourceview_insert (windata_t *vwin, const char *fname, const char *buf) { GtkSourceLanguagesManager *manager; GtkSourceLanguage *language = NULL; FILE *fp = NULL; if (fname != NULL) { fp = gretl_fopen(fname, "rb"); if (fp == NULL) { errbox(_("Couldn't open %s"), fname); return; } } manager = g_object_get_data(G_OBJECT(vwin->sbuf), "languages-manager"); if (vwin->role == GR_PLOT) { language = gtk_source_languages_manager_get_language_from_mime_type (manager, "application/x-gnuplot"); } else { language = gtk_source_languages_manager_get_language_from_mime_type (manager, "application/x-gretlsession"); } if (language == NULL) { g_object_set(G_OBJECT(vwin->sbuf), "highlight", FALSE, NULL); } else { g_object_set(G_OBJECT(vwin->sbuf), "highlight", TRUE, NULL); gtk_source_buffer_set_language(vwin->sbuf, language); } if (fp != NULL) { source_buffer_load_file(vwin->sbuf, vwin->role, fp); fclose(fp); } else { source_buffer_load_buf(vwin->sbuf, buf); } } void sourceview_insert_file (windata_t *vwin, const char *fname) { real_sourceview_insert(vwin, fname, NULL); } void sourceview_insert_buffer (windata_t *vwin, const char *buf) { real_sourceview_insert(vwin, NULL, buf); } void create_source (windata_t *vwin, int hsize, int vsize, gboolean editable) { GtkSourceLanguagesManager *lm; GtkSourceBuffer *sbuf; GtkSourceTagStyle *tagstyle; GdkColormap *cmap; GdkColor blue; /* set up paren-matching in blue */ cmap = gdk_colormap_get_system(); gdk_color_parse("blue", &blue); gdk_colormap_alloc_color(cmap, &blue, FALSE, TRUE); lm = gtk_source_languages_manager_new(); tagstyle = gtk_source_tag_style_new(); sbuf = GTK_SOURCE_BUFFER(gtk_source_buffer_new(NULL)); g_object_ref(lm); g_object_set_data_full(G_OBJECT(sbuf), "languages-manager", lm, (GDestroyNotify) g_object_unref); g_object_unref(lm); tagstyle->mask = GTK_SOURCE_TAG_STYLE_USE_FOREGROUND; tagstyle->foreground = blue; g_object_set_data_full(G_OBJECT(sbuf), "tag-style", tagstyle, (GDestroyNotify) gtk_source_tag_style_free); gtk_source_buffer_set_bracket_match_style(sbuf, tagstyle); gtk_source_buffer_set_check_brackets(sbuf, TRUE); vwin->w = gtk_source_view_new_with_buffer(sbuf); vwin->sbuf = sbuf; gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(vwin->w), GTK_WRAP_NONE); gtk_text_view_set_left_margin(GTK_TEXT_VIEW(vwin->w), 4); gtk_text_view_set_right_margin(GTK_TEXT_VIEW(vwin->w), 4); gtk_widget_modify_font(GTK_WIDGET(vwin->w), fixed_font); hsize *= get_char_width(vwin->w); hsize += 48; gtk_window_set_default_size(GTK_WINDOW(vwin->dialog), hsize, vsize); gtk_text_view_set_editable(GTK_TEXT_VIEW(vwin->w), editable); gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(vwin->w), editable); g_object_unref(cmap); } static gchar *my_utf_string (char *t) { static gchar *s = NULL; GError *error = NULL; gsize r_bytes, w_bytes; unsigned char *c; const char *fc; const char *smb; gchar *from_codeset = NULL; if (t == NULL || *t == '\0') return t; if (g_utf8_validate(t, -1, NULL)) return t; /* so we got a non-UTF-8 */ smb = getenv("SMB_CODESET"); if (smb != NULL && *smb != '\0') { from_codeset = g_strdup(smb); } else { g_get_charset(&fc); if (fc) { from_codeset = g_strdup(fc); } else { from_codeset = g_strdup("ISO-8859-1"); } } if (!strcmp(from_codeset, "ISO-")) { g_free(from_codeset); from_codeset = g_strdup("ISO-8859-1"); } if (s) g_free(s); for (c = (unsigned char *) t; *c != 0; c++) { if (*c < 32 && *c != '\n') { *c = ' '; } } s = g_convert(t, strlen(t), "UTF-8", from_codeset, &r_bytes, &w_bytes, &error); g_free(from_codeset); if (s == NULL) { s = g_strdup(t); for (c = (unsigned char *) s; *c != 0; c++) { if (*c > 128) { *c = '?'; } } } if (error) { printf("DBG: %s. Codeset for system is: %s\n", error->message, from_codeset); printf("DBG: You should set the environment variable " "SMB_CODESET to ISO-8859-1\n"); g_error_free(error); } return s; } static GtkTextTagTable *gretl_tags_new (void) { GtkTextTagTable *table; GtkTextTag *tag; table = gtk_text_tag_table_new(); tag = gtk_text_tag_new("bluetext"); g_object_set(tag, "foreground", "blue", NULL); gtk_text_tag_table_add(table, tag); tag = gtk_text_tag_new("redtext"); g_object_set(tag, "foreground", "red", NULL); gtk_text_tag_table_add(table, tag); tag = gtk_text_tag_new("title"); g_object_set(tag, "justification", GTK_JUSTIFY_CENTER, "pixels_above_lines", 20, "family", "sans", "size", 15 * PANGO_SCALE, NULL); gtk_text_tag_table_add(table, tag); tag = gtk_text_tag_new("sansbold"); g_object_set(tag, "family", "sans", "weight", PANGO_WEIGHT_BOLD, NULL); gtk_text_tag_table_add(table, tag); tag = gtk_text_tag_new("italic"); g_object_set(tag, "family", "sans", "style", PANGO_STYLE_ITALIC, NULL); gtk_text_tag_table_add(table, tag); tag = gtk_text_tag_new("replaceable"); g_object_set(tag, "family", "sans", "style", PANGO_STYLE_ITALIC, NULL); gtk_text_tag_table_add(table, tag); tag = gtk_text_tag_new("superscript"); g_object_set(tag, "style", PANGO_STYLE_NORMAL, "rise", 4 * PANGO_SCALE, "size", 8 * PANGO_SCALE, NULL); gtk_text_tag_table_add(table, tag); tag = gtk_text_tag_new("literal"); g_object_set(tag, "family", "monospace", NULL); gtk_text_tag_table_add(table, tag); tag = gtk_text_tag_new("text"); g_object_set(tag, "family", "sans", NULL); gtk_text_tag_table_add(table, tag); tag = gtk_text_tag_new("indented"); g_object_set(tag, "left_margin", 16, "indent", -12, NULL); gtk_text_tag_table_add(table, tag); tag = gtk_text_tag_new("code"); g_object_set(tag, "family", "monospace", NULL); gtk_text_tag_table_add(table, tag); return table; } GtkTextBuffer *gretl_text_buf_new (void) { static GtkTextTagTable *tags = NULL; GtkTextBuffer *tbuf; if (tags == NULL) { tags = gretl_tags_new(); } tbuf = gtk_text_buffer_new(tags); return tbuf; } static void real_textview_add_colorized (GtkWidget *view, const char *buf, int append) { GtkTextBuffer *tbuf; GtkTextIter iter; int thiscolor = PLAIN_TEXT; int nextcolor; char readbuf[MAXSTR]; g_return_if_fail(GTK_IS_TEXT_VIEW(view)); tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)); if (append) { gtk_text_buffer_get_end_iter(tbuf, &iter); } else { gtk_text_buffer_get_iter_at_offset(tbuf, &iter, 0); } bufgets_init(buf); while (bufgets(readbuf, sizeof readbuf, buf)) { if (ends_with_backslash(readbuf)) { nextcolor = BLUE_TEXT; } else { nextcolor = PLAIN_TEXT; } if (*readbuf == '#' || *readbuf == '?' || *readbuf == '>') { thiscolor = BLUE_TEXT; } if (thiscolor == BLUE_TEXT) { gtk_text_buffer_insert_with_tags_by_name(tbuf, &iter, readbuf, -1, "bluetext", NULL); } else { gtk_text_buffer_insert(tbuf, &iter, readbuf, -1); } /* bufgets strips newlines */ gtk_text_buffer_insert(tbuf, &iter, "\n", 1); thiscolor = nextcolor; } bufgets_finalize(buf); } void textview_set_text_colorized (GtkWidget *view, const char *buf) { real_textview_add_colorized(view, buf, 0); } void textview_append_text_colorized (GtkWidget *view, const char *buf) { real_textview_add_colorized(view, buf, 1); } void textview_insert_file (windata_t *vwin, const char *fname) { FILE *fp; GtkTextBuffer *tbuf; GtkTextIter iter; int thiscolor, nextcolor; char readbuf[MAXSTR], *chunk; g_return_if_fail(GTK_IS_TEXT_VIEW(vwin->w)); fp = gretl_fopen(fname, "r"); if (fp == NULL) return; thiscolor = nextcolor = PLAIN_TEXT; tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->w)); gtk_text_buffer_get_iter_at_offset(tbuf, &iter, 0); memset(readbuf, 0, sizeof readbuf); while (fgets(readbuf, sizeof readbuf, fp)) { #ifdef ENABLE_NLS chunk = my_utf_string(readbuf); #else chunk = readbuf; #endif nextcolor = PLAIN_TEXT; if (vwin->role == SCRIPT_OUT && ends_with_backslash(chunk)) { nextcolor = BLUE_TEXT; } if (*chunk == '?') { thiscolor = (vwin->role == CONSOLE)? RED_TEXT : BLUE_TEXT; } else if (*chunk == '#') { thiscolor = BLUE_TEXT; } switch (thiscolor) { case PLAIN_TEXT: gtk_text_buffer_insert(tbuf, &iter, chunk, -1); break; case BLUE_TEXT: gtk_text_buffer_insert_with_tags_by_name(tbuf, &iter, chunk, -1, "bluetext", NULL); break; case RED_TEXT: gtk_text_buffer_insert_with_tags_by_name(tbuf, &iter, chunk, -1, "redtext", NULL); break; } thiscolor = nextcolor; memset(readbuf, 0, sizeof readbuf); } fclose(fp); } static void insert_link (GtkTextBuffer *tbuf, GtkTextIter *iter, const char *text, gint page, const char *indent) { GtkTextTag *ltag; if (page == GUIDE_PAGE) { ltag = gtk_text_buffer_create_tag(tbuf, NULL, "foreground", "blue", "family", "sans", NULL); } else if (page == SCRIPT_PAGE) { ltag = gtk_text_buffer_create_tag(tbuf, NULL, "foreground", "blue", "family", "monospace", NULL); g_object_set_data_full(G_OBJECT(ltag), "fname", g_strdup(text), g_free); } else if (indent != NULL) { ltag = gtk_text_buffer_create_tag(tbuf, NULL, "foreground", "blue", "left_margin", 30, NULL); } else { ltag = gtk_text_buffer_create_tag(tbuf, NULL, "foreground", "blue", NULL); } g_object_set_data(G_OBJECT(ltag), "page", GINT_TO_POINTER(page)); gtk_text_buffer_insert_with_tags(tbuf, iter, text, -1, ltag, NULL); } static void link_open_script (GtkTextTag *tag) { const char *fname = g_object_get_data(G_OBJECT(tag), "fname"); char fullname[MAXLEN]; sprintf(fullname, "%sscripts%cmisc%c%s", paths.gretldir, SLASH, SLASH, fname); view_file(fullname, 0, 0, 78, 370, VIEW_SCRIPT); } static void follow_if_link (GtkWidget *tview, GtkTextIter *iter, gpointer p) { GSList *tags = NULL, *tagp = NULL; tags = gtk_text_iter_get_tags(iter); for (tagp = tags; tagp != NULL; tagp = tagp->next) { GtkTextTag *tag = tagp->data; gint page = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tag), "page")); if (page != 0) { if (page == GUIDE_PAGE) { display_pdf_help(NULL, 1, NULL); } else if (page == SCRIPT_PAGE) { link_open_script(tag); } else { plain_text_cmdref(p, page, NULL); } break; } } if (tags) { g_slist_free(tags); } } /* Help links can be activated by pressing Enter */ static gboolean cmdref_key_press (GtkWidget *tview, GdkEventKey *ev, gpointer p) { GtkTextIter iter; GtkTextBuffer *tbuf; switch (ev->keyval) { case GDK_Return: case GDK_KP_Enter: tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tview)); gtk_text_buffer_get_iter_at_mark(tbuf, &iter, gtk_text_buffer_get_insert(tbuf)); follow_if_link(tview, &iter, p); break; default: break; } return FALSE; } /* Help links can be activated by clicking */ static gboolean cmdref_event_after (GtkWidget *tview, GdkEvent *ev, gpointer p) { GtkTextIter start, end, iter; GtkTextBuffer *buffer; GdkEventButton *event; gint x, y; if (ev->type != GDK_BUTTON_RELEASE) { return FALSE; } event = (GdkEventButton *) ev; if (event->button != 1) return FALSE; buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tview)); /* don't follow a link if the user has selected something */ gtk_text_buffer_get_selection_bounds(buffer, &start, &end); if (gtk_text_iter_get_offset(&start) != gtk_text_iter_get_offset(&end)) return FALSE; gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(tview), GTK_TEXT_WINDOW_WIDGET, event->x, event->y, &x, &y); gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(tview), &iter, x, y); follow_if_link(tview, &iter, p); return FALSE; } static GdkCursor *hand_cursor = NULL; static GdkCursor *regular_cursor = NULL; static void set_cursor_if_appropriate (GtkTextView *tview, gint x, gint y) { static gboolean hovering_over_link = FALSE; GSList *tags = NULL, *tagp = NULL; GtkTextBuffer *tbuf; GtkTextIter iter; gboolean hovering = FALSE; tbuf = gtk_text_view_get_buffer(tview); gtk_text_view_get_iter_at_location(tview, &iter, x, y); tags = gtk_text_iter_get_tags(&iter); for (tagp = tags; tagp != NULL; tagp = tagp->next) { GtkTextTag *tag = tagp->data; gint page = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tag), "page")); if (page != 0) { hovering = TRUE; break; } } if (hovering != hovering_over_link) { hovering_over_link = hovering; if (hovering_over_link) { gdk_window_set_cursor(gtk_text_view_get_window(tview, GTK_TEXT_WINDOW_TEXT), hand_cursor); } else { gdk_window_set_cursor(gtk_text_view_get_window(tview, GTK_TEXT_WINDOW_TEXT), regular_cursor); } } if (tags) { g_slist_free(tags); } } static gboolean cmdref_motion_notify (GtkWidget *tview, GdkEventMotion *event) { gint x, y; gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(tview), GTK_TEXT_WINDOW_WIDGET, event->x, event->y, &x, &y); set_cursor_if_appropriate(GTK_TEXT_VIEW(tview), x, y); gdk_window_get_pointer(tview->window, NULL, NULL, NULL); return FALSE; } static gboolean cmdref_visibility_notify (GtkWidget *tview, GdkEventVisibility *e) { gint wx, wy, bx, by; gdk_window_get_pointer(tview->window, &wx, &wy, NULL); gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(tview), GTK_TEXT_WINDOW_WIDGET, wx, wy, &bx, &by); set_cursor_if_appropriate(GTK_TEXT_VIEW(tview), bx, by); return FALSE; } static void maybe_connect_help_signals (windata_t *hwin, int en) { int done = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(hwin->w), "sigs_connected")); if (hand_cursor == NULL) { hand_cursor = gdk_cursor_new(GDK_HAND2); } if (regular_cursor == NULL) { regular_cursor = gdk_cursor_new(GDK_XTERM); } if (!done) { gpointer en_ptr = GINT_TO_POINTER(en); g_signal_connect(hwin->w, "key-press-event", G_CALLBACK(cmdref_key_press), en_ptr); g_signal_connect(hwin->w, "event-after", G_CALLBACK(cmdref_event_after), en_ptr); g_signal_connect(hwin->w, "motion-notify-event", G_CALLBACK(cmdref_motion_notify), NULL); g_signal_connect(hwin->w, "visibility-notify-event", G_CALLBACK(cmdref_visibility_notify), NULL); g_object_set_data(G_OBJECT(hwin->w), "sigs_connected", GINT_TO_POINTER(1)); } } static void maybe_set_help_tabs (windata_t *hwin) { int done = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(hwin->w), "tabs_set")); if (!done) { PangoTabArray *tabs; tabs = pango_tab_array_new(1, TRUE); pango_tab_array_set_tab(tabs, 0, PANGO_TAB_LEFT, 50); gtk_text_view_set_tabs(GTK_TEXT_VIEW(hwin->w), tabs); pango_tab_array_free(tabs); g_object_set_data(G_OBJECT(hwin->w), "tabs_set", GINT_TO_POINTER(1)); } } static void cmdref_title_page (windata_t *hwin, GtkTextBuffer *tbuf, int en) { const char *header = N_("Gretl Command Reference"); GtkTextIter iter; int i, j, k; gtk_text_buffer_get_iter_at_offset(tbuf, &iter, 0); gtk_text_buffer_insert_with_tags_by_name(tbuf, &iter, (en)? header : _(header), -1, "title", NULL); gtk_text_buffer_insert(tbuf, &iter, "\n\n\n", -1); j = 1; for (i=1; iw), tbuf); maybe_connect_help_signals(hwin, en); maybe_set_help_tabs(hwin); } static gint help_popup_click (GtkWidget *w, gpointer p) { windata_t *hwin = (windata_t *) p; int action = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "action")); gpointer enp = NULL; int page = 0; if (hwin->role == CLI_HELP_EN) { enp = GINT_TO_POINTER(1); } if (action == 2) { page = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(hwin->w), "backpage")); } plain_text_cmdref(enp, page, NULL); return FALSE; } GtkWidget *build_help_popup (windata_t *hwin) { const char *items[] = { N_("Index"), N_("Back") }; GtkWidget *pmenu = gtk_menu_new(); GtkWidget *item; int i; for (i=0; i<2; i++) { item = gtk_menu_item_new_with_label(_(items[i])); g_object_set_data(G_OBJECT(item), "action", GINT_TO_POINTER(i+1)); g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(help_popup_click), hwin); gtk_widget_show(item); gtk_menu_shell_append(GTK_MENU_SHELL(pmenu), item); } return pmenu; } gboolean help_popup_handler (GtkWidget *w, GdkEventButton *event, gpointer p) { GdkModifierType mods; gdk_window_get_pointer(w->window, NULL, NULL, &mods); if (mods & GDK_BUTTON3_MASK) { windata_t *hwin = (windata_t *) p; if (hwin->active_var == 0) { return TRUE; } if (hwin->popup) { gtk_widget_destroy(hwin->popup); hwin->popup = NULL; } hwin->popup = build_help_popup(hwin); if (hwin->popup != NULL) { gtk_menu_popup(GTK_MENU(hwin->popup), NULL, NULL, NULL, NULL, event->button, event->time); gtk_signal_connect(GTK_OBJECT(hwin->popup), "destroy", GTK_SIGNAL_FUNC(gtk_widget_destroyed), &hwin->popup); } return TRUE; } return FALSE; } enum { INSERT_NONE, INSERT_REF, INSERT_FIG, INSERT_REPL, INSERT_LIT, INSERT_ITAL, INSERT_SUP, INSERT_TEXT, INSERT_PDFLINK, INSERT_INPLINK }; static void insert_help_figure (GtkTextBuffer *tbuf, GtkTextIter *iter, const char *fig) { char figfile[FILENAME_MAX]; GdkPixbuf *pixbuf; sprintf(figfile, "%shelpfigs%c%s.png", paths.gretldir, SLASH, fig); pixbuf = gdk_pixbuf_new_from_file(figfile, NULL); if (pixbuf != NULL) { gtk_text_buffer_insert_pixbuf(tbuf, iter, pixbuf); g_object_unref(G_OBJECT(pixbuf)); } } static void insert_tagged_text (GtkTextBuffer *tbuf, GtkTextIter *iter, const char *s, int ins, const char *indent) { const char *ftag = NULL; switch (ins) { case INSERT_ITAL: ftag = "italic"; break; case INSERT_REPL: ftag = "replaceable"; break; case INSERT_LIT: ftag = "literal"; break; case INSERT_SUP: ftag = "superscript"; break; default: break; } if (ftag != NULL) { gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, s, -1, ftag, indent, NULL); } } static int get_instruction_and_string (const char *p, char *str) { int ins = INSERT_NONE; *str = '\0'; if (!strncmp(p, "ref", 3)) { ins = INSERT_REF; } else if (!strncmp(p, "fig", 3)) { ins = INSERT_FIG; } else if (!strncmp(p, "itl", 3)) { ins = INSERT_ITAL; } else if (!strncmp(p, "var", 3)) { ins = INSERT_REPL; } else if (!strncmp(p, "lit", 3)) { ins = INSERT_LIT; } else if (!strncmp(p, "sup", 3)) { ins = INSERT_SUP; } else if (!strncmp(p, "pdf", 3)) { ins = INSERT_PDFLINK; } else if (!strncmp(p, "inp", 3)) { ins = INSERT_INPLINK; } if (ins != INSERT_NONE) { int i = 0; p += 5; while (*p) { if (*p == '"' && *(p+1) == '>') { str[i] = 0; break; } else { str[i++] = *p++; } } } return ins; } static int get_code_skip (const char *s) { int skip = 5; while (*s) { if (*s == '\n') { skip++; break; } else if (isspace(*s)) { skip++; } s++; } return skip; } static void insert_text_with_markup (GtkTextBuffer *tbuf, GtkTextIter *iter, const char *s) { static char targ[128]; const char *p; int ins; const char *indent = NULL; const char *code = NULL; while ((p = strstr(s, "<"))) { int skip = 0; if (code != NULL) { gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, s, p - s, "code", indent, NULL); } else { gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, s, p - s, "text", indent, NULL); } p++; if (*p == '@') { /* "atomic" markup */ ins = get_instruction_and_string(p + 1, targ); if (ins == INSERT_REF) { insert_link(tbuf, iter, targ, gretl_command_number(targ), indent); } else if (ins == INSERT_PDFLINK) { insert_link(tbuf, iter, targ, GUIDE_PAGE, indent); } else if (ins == INSERT_INPLINK) { insert_link(tbuf, iter, targ, SCRIPT_PAGE, indent); } else if (ins == INSERT_FIG) { insert_help_figure(tbuf, iter, targ); } else if (ins != INSERT_NONE) { insert_tagged_text(tbuf, iter, targ, ins, indent); } skip = 8 + strlen(targ); } else if (!strncmp(p, "indent", 6)) { indent = "indented"; skip = 7 + (*(p+7) == '\n'); } else if (!strncmp(p, "/indent", 7)) { indent = NULL; skip = 8 + (*(p+8) == '\n'); } else if (!strncmp(p, "code", 4)) { code = "code"; skip = get_code_skip(p + 5); } else if (!strncmp(p, "/code", 5)) { code = NULL; skip = 6 + (*(p+6) == '\n'); } else { /* literal "<" */ gtk_text_buffer_insert(tbuf, iter, "<", 1); } s = p + skip; } if (code != NULL) { gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, s, -1, "code", indent, NULL); } else { gtk_text_buffer_insert_with_tags_by_name(tbuf, iter, s, -1, "text", indent, NULL); } } static char *grab_topic_buffer (const char *s) { const char *p = strstr(s, "\n# "); char *buf; if (p != NULL) { buf = g_strndup(s, p - s); } else { buf = g_strdup(s); } return buf; } /* pull the appropriate chunk of help text out of the buffer attached to the help viewer and display it */ void set_help_topic_buffer (windata_t *hwin, int hcode, int pos, int en) { GtkTextBuffer *textb; GtkTextIter iter; char line[256]; gchar *hbuf; char *buf; textb = gretl_text_buf_new(); if (pos == 0) { /* cli help with no topic selected */ cmdref_title_page(hwin, textb, en); cursor_to_top(hwin); hwin->active_var = 0; return; } gtk_text_buffer_get_iter_at_offset(textb, &iter, 0); hbuf = (gchar *) hwin->data + pos; bufgets_init(hbuf); buf = bufgets(line, sizeof line, hbuf); bufgets_finalize(hbuf); if (buf == NULL) { return; } if (gui_help(hwin->role)) { /* topic heading: descriptive string */ gchar *p = quoted_help_string(line); gtk_text_buffer_insert_with_tags_by_name(textb, &iter, p, -1, "sansbold", NULL); free(p); } else { /* topic heading: plain command word */ char hword[9]; sscanf(line + 2, "%8s", hword); gtk_text_buffer_insert_with_tags_by_name(textb, &iter, hword, -1, "redtext", NULL); } gtk_text_buffer_insert(textb, &iter, "\n", 1); buf = grab_topic_buffer(hbuf + strlen(line) + 1); if (buf == NULL) { return; } insert_text_with_markup(textb, &iter, buf); free(buf); gtk_text_view_set_buffer(GTK_TEXT_VIEW(hwin->w), textb); g_object_set_data(G_OBJECT(hwin->w), "backpage", GINT_TO_POINTER(hwin->active_var)); maybe_connect_help_signals(hwin, en); cursor_to_top(hwin); hwin->active_var = hcode; } void create_text (windata_t *vwin, int hsize, int vsize, gboolean editable) { GtkTextBuffer *tbuf = gretl_text_buf_new(); GtkWidget *w = gtk_text_view_new_with_buffer(tbuf); vwin->w = w; gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(w), GTK_WRAP_WORD); gtk_text_view_set_left_margin(GTK_TEXT_VIEW(w), 4); gtk_text_view_set_right_margin(GTK_TEXT_VIEW(w), 4); gtk_widget_modify_font(GTK_WIDGET(w), fixed_font); if (hsize > 0) { hsize *= get_char_width(w); hsize += 48; } gtk_window_set_default_size(GTK_WINDOW(vwin->dialog), hsize, vsize); gtk_text_view_set_editable(GTK_TEXT_VIEW(w), editable); gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(w), editable); } void text_set_word_wrap (GtkWidget *w, gboolean wrap) { if (wrap) { gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(w), GTK_WRAP_WORD); } else { gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(w), GTK_WRAP_NONE); } } void text_table_setup (GtkWidget *vbox, GtkWidget *w) { GtkWidget *sw; sw = gtk_scrolled_window_new(NULL, NULL); gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, FALSE); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN); gtk_container_add(GTK_CONTAINER(sw), w); gtk_widget_show(w); gtk_widget_show(sw); }