This repository has been archived on 2020-04-11. You can view files and clone it, but cannot push or open issues or pull requests.
gnoemoe/gnoemoe/widgets/gm-world-text-view.c

1509 lines
41 KiB
C

#include <gtk/gtk.h>
#include <string.h>
#include <gdk/gdk.h>
#include "gm-world-text-view.h"
#include "gm-color-table.h"
#include "gm-marshal.h"
#include "gm-ansi.h"
#include "gm-debug.h"
#include "gm-support.h"
static gboolean gm_world_text_view_button_press_event(GtkWidget *widget,
GdkEventButton *event);
static gboolean gm_world_text_view_button_release_event(GtkWidget *widget,
GdkEventButton *event);
static gboolean gm_world_text_view_leave_event(GtkWidget *widget,
GdkEventCrossing *event);
static void gm_world_text_view_drag_data_get(GtkWidget *widget,
GdkDragContext *context, GtkSelectionData *data, guint info,
guint time);
static void gm_world_text_view_drag_end(GtkWidget *widget,
GdkDragContext *context);
static void gm_world_text_view_style_set(GtkWidget *widget,
GtkStyle *previous_style);
static void gm_world_text_view_populate_popup(GtkTextView *text_view,
GtkMenu *menu);
/* Callback definitions */
// Popup menu actions callbacks
static void on_gm_world_text_view_copy_address(GtkMenuItem *item,
GmWorldTextView *view);
static void on_gm_world_text_view_open_address(GtkMenuItem *item,
GmWorldTextView *view);
static gboolean on_gm_world_text_view_url_event(GtkTextTag *tag,
GObject *object, GdkEvent *event, GtkTextIter *iter,
GmWorldTextView *view);
static gboolean on_gm_world_text_view_event(GmWorldTextView *view,
GdkEventMotion *event, GtkTextTag *tag);
/*void on_gm_world_text_view_color_table_bold_toggled(GmColorTable *table,
gboolean bold, GmWorldTextView *view);*/
static void on_gm_world_text_view_color_table_color_changed(GmColorTable *table,
gchar *name, GmWorldTextView *view);
static void on_gm_world_text_view_color_table_font_changed(GmColorTable *table,
gchar *font_description, GmWorldTextView *view);
static void gm_world_text_view_create_tags(GmWorldTextView *view);
static void gm_world_text_view_init_tags(GmWorldTextView *view);
static void gm_world_text_view_update_font(GmWorldTextView *view);
#define GM_WORLD_TEXT_VIEW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE( \
(object), GM_TYPE_WORLD_TEXT_VIEW, GmWorldTextViewPrivate))
#define GM_WORLD_TEXT_VIEW_BUFFER(view) gtk_text_view_get_buffer( \
GTK_TEXT_VIEW(view))
typedef struct _GmWorldTextViewInsertInfo GmWorldTextViewInsertInfo;
typedef struct _BlinkInfo {
guint blink;
GtkTextBuffer *text;
gchar *fill;
guint timeout;
GmWorldTextView *view;
gint offset;
} BlinkInfo;
static void blink_info_free(BlinkInfo *info);
struct _GmWorldTextViewInsertInfo {
GList *tags;
gboolean bold;
gboolean inverse;
guint blink;
gchar *text;
};
struct _GmWorldTextViewPrivate {
GSList *blinkers;
guint blink_state;
GtkTextTag *tag_blink;
GtkTextTag *tag_blink_fast;
guint blink_timeout_id;
guint character_width;
guint character_height;
gint max_lines;
GmWorldTextViewInsertInfo last_info;
GmColorTable *color_table;
gboolean drag_url;
gchar *drag_url_text;
guint drag_x;
guint drag_y;
GtkTargetList *source_target_list;
gboolean is_hand;
};
/* Signals */
enum {
URL_ACTIVATE,
CHARACTER_SIZE_CHANGED,
NUM_SIGNALS
};
static guint world_text_view_signals[NUM_SIGNALS] = {0};
static GtkTextViewClass *parent_class = NULL;
G_DEFINE_TYPE(GmWorldTextView, gm_world_text_view, GTK_TYPE_TEXT_VIEW)
/* Class object functions etc */
static void
gm_world_text_view_finalize(GObject *object) {
GmWorldTextView *view = GM_WORLD_TEXT_VIEW(object);
GSList *item;
BlinkInfo *info;
gm_world_text_view_set_color_table(view, NULL);
if (view->priv->blink_timeout_id) {
g_source_remove(view->priv->blink_timeout_id);
}
for (item = view->priv->blinkers; item; item = item->next) {
info = (BlinkInfo *)(item->data);
g_source_remove(info->timeout);
blink_info_free(info);
}
g_slist_free(view->priv->blinkers);
g_list_free(view->priv->last_info.tags);
g_free(view->priv->last_info.text);
gtk_target_list_unref(view->priv->source_target_list);
G_OBJECT_CLASS(gm_world_text_view_parent_class)->finalize(object);
}
static void
gm_world_text_view_class_init(GmWorldTextViewClass *klass) {
GObjectClass *object_class = G_OBJECT_CLASS(klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS(klass);
parent_class = GTK_TEXT_VIEW_CLASS(g_type_class_peek_parent(klass));
text_view_class->populate_popup = gm_world_text_view_populate_popup;
object_class->finalize = gm_world_text_view_finalize;
widget_class->button_press_event =
gm_world_text_view_button_press_event;
widget_class->button_release_event =
gm_world_text_view_button_release_event;
widget_class->leave_notify_event = gm_world_text_view_leave_event;
widget_class->drag_data_get = gm_world_text_view_drag_data_get;
widget_class->drag_end = gm_world_text_view_drag_end;
widget_class->style_set = gm_world_text_view_style_set;
world_text_view_signals[URL_ACTIVATE] =
g_signal_new("url_activate",
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(GmWorldTextViewClass, url_activate),
NULL, NULL,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE,
1,
G_TYPE_STRING);
world_text_view_signals[CHARACTER_SIZE_CHANGED] =
g_signal_new("character_size_changed",
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(GmWorldTextViewClass, character_size_changed),
NULL, NULL,
gm_marshal_VOID__UINT_UINT,
G_TYPE_NONE,
2,
G_TYPE_UINT,
G_TYPE_UINT);
g_type_class_add_private(object_class, sizeof(GmWorldTextViewPrivate));
}
enum {
DRAG_URL,
DRAG_URI_LIST,
DRAG_TEXT,
NUM_TARGETS
};
static const GtkTargetEntry drag_targets[] = {
{"_NETSCAPE_URL", 0, DRAG_URL},
{"text/uri-list", 0, DRAG_URI_LIST},
{"text/plain", 0, DRAG_TEXT},
};
static void
gm_world_text_view_init(GmWorldTextView *view) {
view->priv = GM_WORLD_TEXT_VIEW_GET_PRIVATE(view);
// This view can't focus
GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(view), GTK_CAN_FOCUS);
// Therefore we need the active state to be the selected state
gtk_widget_modify_base(GTK_WIDGET(view), GTK_STATE_ACTIVE,
&(GTK_WIDGET(view)->style->base[GTK_STATE_SELECTED]));
gtk_widget_modify_text(GTK_WIDGET(view), GTK_STATE_ACTIVE,
&(GTK_WIDGET(view)->style->text[GTK_STATE_SELECTED]));
gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
// Margins
gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), 3);
gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), 3);
// Set default wrapping mode
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD_CHAR);
view->priv->color_table = NULL;
view->priv->max_lines = 2000;
view->priv->last_info.bold = FALSE;
view->priv->last_info.inverse = FALSE;
view->priv->last_info.tags = NULL;
view->priv->last_info.text = NULL;
view->priv->source_target_list = gtk_target_list_new(drag_targets,
NUM_TARGETS);
}
/* Private functions */
static void
blink_info_free(BlinkInfo *info) {
g_object_unref(info->text);
g_free(info->fill);
g_free(info);
}
static gboolean
gm_world_text_view_button_press_event(GtkWidget *widget,
GdkEventButton *event) {
GtkTextView *text_view = GTK_TEXT_VIEW(widget);
GmWorldTextView *view = GM_WORLD_TEXT_VIEW(widget);
GtkTextIter start, end;
GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
GtkTextTag *tag;
gint x, y;
if (event->button == 1 && !(event->state & GDK_MOD1_MASK) &&
!(event->state & GDK_SHIFT_MASK) && event->window ==
gtk_text_view_get_window(text_view, GTK_TEXT_WINDOW_TEXT)) {
// Are we at a link
gtk_text_view_window_to_buffer_coords(text_view, GTK_TEXT_WINDOW_TEXT,
event->x, event->y, &x, &y);
gtk_text_view_get_iter_at_location(text_view, &start, x, y);
tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer),
"url");
if (gtk_text_iter_has_tag(&start, tag)) {
end = start;
if (!gtk_text_iter_begins_tag(&start, tag)) {
gtk_text_iter_backward_to_tag_toggle(&start, tag);
}
if (!gtk_text_iter_ends_tag(&end, tag)) {
gtk_text_iter_forward_to_tag_toggle(&end, tag);
}
view->priv->drag_url_text = gtk_text_buffer_get_text(buffer,
&start, &end, FALSE);
view->priv->drag_x = event->x;
view->priv->drag_y = event->y;
view->priv->drag_url = TRUE;
return TRUE;
}
}
if (GTK_WIDGET_CLASS(parent_class)->button_press_event) {
return GTK_WIDGET_CLASS(parent_class)->button_press_event(widget,
event);
}
return FALSE;
}
static void
gm_world_text_view_no_selection(GmWorldTextView *view) {
GtkTextBuffer *buffer = GM_WORLD_TEXT_VIEW_BUFFER(view);
GtkTextIter iter;
gtk_text_buffer_get_iter_at_mark(buffer, &iter,
gtk_text_buffer_get_insert(buffer));
gtk_text_buffer_move_mark(buffer,
gtk_text_buffer_get_selection_bound(buffer), &iter);
}
static gboolean
gm_world_text_view_button_release_event(GtkWidget *widget,
GdkEventButton *event) {
GmWorldTextView *view = GM_WORLD_TEXT_VIEW(widget);
if (event->button == 1 && view->priv->drag_url) {
gm_world_text_view_no_selection(view);
view->priv->drag_url = FALSE;
g_free(view->priv->drag_url_text);
view->priv->drag_url_text = NULL;
}
if (GTK_WIDGET_CLASS(parent_class)->button_release_event) {
return GTK_WIDGET_CLASS(parent_class)->button_release_event(widget,
event);
}
return FALSE;
}
static
gboolean gm_world_text_view_leave_event(GtkWidget *widget,
GdkEventCrossing *event) {
GM_WORLD_TEXT_VIEW(widget)->priv->drag_url = FALSE;
if (GTK_WIDGET_CLASS(parent_class)->leave_notify_event) {
return GTK_WIDGET_CLASS(parent_class)->leave_notify_event(widget,
event);
}
return FALSE;
}
static void
gm_world_text_view_drag_data_get(GtkWidget *widget, GdkDragContext *context,
GtkSelectionData *data, guint info, guint time) {
GmWorldTextView *view = GM_WORLD_TEXT_VIEW(widget);
GdkAtom target = data->target;
gchar *str = NULL;
if (view->priv->drag_url_text) {
if (target == gdk_atom_intern("_NETSCAPE_URL", FALSE) ||
target == gdk_atom_intern("text/plain", FALSE)) {
str = g_strdup(view->priv->drag_url_text);
} else if (target == gdk_atom_intern("text/uri-list", FALSE)) {
str = g_strconcat(view->priv->drag_url_text, "\r\n", NULL);
} else {
g_assert_not_reached();
}
} else if (GTK_WIDGET_CLASS(parent_class)->drag_data_get) {
GTK_WIDGET_CLASS(parent_class)->drag_data_get(widget, context, data,
info, time);
return;
} else {
g_assert_not_reached();
}
gtk_selection_data_set(data, target, 8, (const guchar *)str,
strlen(str));
g_free(str);
}
static void
gm_world_text_view_drag_end(GtkWidget *widget, GdkDragContext *context) {
GmWorldTextView *view = GM_WORLD_TEXT_VIEW(widget);
if (view->priv->drag_url_text) {
g_free(view->priv->drag_url_text);
view->priv->drag_url_text = NULL;
} else if (GTK_WIDGET_CLASS(parent_class)->drag_end) {
GTK_WIDGET_CLASS(parent_class)->drag_end(widget, context);
}
}
static void
gm_world_text_view_style_set(GtkWidget *widget,
GtkStyle *previous_style) {
GmWorldTextView *view = GM_WORLD_TEXT_VIEW(widget);
GtkRcStyle *style;
PangoContext *pc;
PangoLayout *pl;
gint cwidth, cheight;
if (GTK_WIDGET_CLASS(parent_class)->style_set) {
GTK_WIDGET_CLASS(parent_class)->style_set(widget, previous_style);
}
style = gtk_widget_get_modifier_style(widget);
pc = gtk_widget_create_pango_context(widget);
if (style->font_desc != NULL) {
pango_context_set_font_description(pc, style->font_desc);
pl = pango_layout_new(pc);
pango_layout_set_text(pl, "G", 1);
pango_layout_get_pixel_size(pl, &(cwidth),
&(cheight));
if (view->priv->character_width != (guint)cwidth ||
view->priv->character_height != (guint)cheight) {
view->priv->character_width = (guint)cwidth;
view->priv->character_height = (guint)cheight;
g_signal_emit(view,
world_text_view_signals[CHARACTER_SIZE_CHANGED], 0,
view->priv->character_width, view->priv->character_height);
}
g_object_unref(pl);
}
g_object_unref(pc);
}
static void
gm_world_text_view_populate_popup(GtkTextView *text_view,
GtkMenu *menu) {
GmWorldTextView *view = GM_WORLD_TEXT_VIEW(text_view);
GtkTextTagTable *table;
GtkTextTag *tag;
gint x, y;
GtkTextIter iter, start, end;
GtkWidget *item;
gchar *str = NULL;
if (parent_class->populate_popup) {
parent_class->populate_popup(text_view, menu);
}
table = gtk_text_buffer_get_tag_table(GM_WORLD_TEXT_VIEW_BUFFER(view));
tag = gtk_text_tag_table_lookup(table, "url");
gtk_widget_get_pointer(GTK_WIDGET(view), &x, &y);
gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(view),
GTK_TEXT_WINDOW_WIDGET, x, y, &x, &y);
gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(view), &iter, x, y);
start = end = iter;
if (gtk_text_iter_backward_to_tag_toggle(&start, tag) &&
gtk_text_iter_forward_to_tag_toggle(&end, tag)) {
str = gtk_text_buffer_get_text(GM_WORLD_TEXT_VIEW_BUFFER(view),
&start, &end, FALSE);
}
if (str == NULL || *str == '\0') {
return;
}
item = gtk_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
gtk_widget_show(item);
/* Set data just to get the string freed when not needed. */
g_object_set_data_full(G_OBJECT(menu), "url", str, (GDestroyNotify)g_free);
item = gtk_menu_item_new_with_mnemonic(_("_Copy Link Address"));
g_signal_connect(item, "activate",
G_CALLBACK(on_gm_world_text_view_copy_address), view);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
gtk_widget_show(item);
item = gtk_menu_item_new_with_mnemonic(_("_Open Link"));
g_signal_connect(item, "activate",
G_CALLBACK(on_gm_world_text_view_open_address), view);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
gtk_widget_show(item);
}
static void
gm_world_text_view_update_color_tag(GmWorldTextView *view, const gchar *name,
GtkTextTag *tag) {
GtkTextBuffer *buf = GM_WORLD_TEXT_VIEW_BUFFER(view);
GtkTextTagTable *table = gtk_text_buffer_get_tag_table(buf);
GdkColor col;
if (tag == NULL) {
tag = gtk_text_tag_table_lookup(table, name);
}
if (view->priv->color_table != NULL) {
gm_color_table_get(view->priv->color_table, name, &col);
}
if (name[0] == 'f') {
if (view->priv->color_table == NULL) {
gdk_color_parse("#ffffff", &col);
}
g_object_set(tag, "foreground-gdk", &col, NULL);
if (strcmp(name, "fg_default") == 0) {
// Update inverse_bg
tag = gtk_text_tag_table_lookup(table, "inverse_bg");
g_object_set(tag, "background-gdk", &col, NULL);
}
} else {
if (view->priv->color_table == NULL) {
gdk_color_parse("#000000", &col);
}
g_object_set(tag, "background-gdk", &col, NULL);
if (strcmp(name, "bg_default")) {
// Update inverse_fg
tag = gtk_text_tag_table_lookup(table, "inverse_fg");
g_object_set(tag, "foreground-gdk", &col, NULL);
}
}
}
static void
gm_world_text_view_check_buffer_size(GmWorldTextView *view) {
GtkTextBuffer *buf = GM_WORLD_TEXT_VIEW_BUFFER(view);
GtkTextIter start, end;
int d = gtk_text_buffer_get_line_count(buf) - view->priv->max_lines;
if (d > 0) {
gtk_text_buffer_get_iter_at_line(buf, &start, 0);
gtk_text_buffer_get_iter_at_line(buf, &end, d);
gtk_text_buffer_delete(buf, &start, &end);
}
}
static void
gm_world_text_view_create_tags(GmWorldTextView *view) {
/* Create all the tags */
int i;
GtkTextTag *tag;
GtkTextBuffer *buf = GM_WORLD_TEXT_VIEW_BUFFER(view);
GdkColor col;
/* Url tag */
tag = gtk_text_buffer_create_tag(buf, "url", "foreground", "steelblue",
"underline", PANGO_UNDERLINE_SINGLE, NULL);
g_signal_connect(tag, "event", G_CALLBACK(on_gm_world_text_view_url_event),
view);
g_signal_connect(view, "event", G_CALLBACK(on_gm_world_text_view_event),
tag);
view->priv->tag_blink = gtk_text_buffer_create_tag(buf, "blink", NULL);
view->priv->tag_blink_fast = gtk_text_buffer_create_tag(buf, "blink-fast",
NULL);
gtk_text_buffer_create_tag(buf, "blink-after", "weight",
PANGO_WEIGHT_ULTRABOLD, NULL);
tag = gtk_text_buffer_create_tag(buf, "inverse_bg", NULL);
if (view->priv->color_table != NULL) {
gm_color_table_get(view->priv->color_table, "fg_default", &col);
g_object_set(tag, "background-gdk", &col, NULL);
}
tag = gtk_text_buffer_create_tag(buf, "inverse_fg", NULL);
if (view->priv->color_table != NULL) {
gm_color_table_get(view->priv->color_table, "bg_default", &col);
g_object_set(tag, "background-gdk", &col, NULL);
}
for (i = 0; i < (int)(sizeof(ansi_colors) / sizeof(ansinamepair)); i++) {
tag = gtk_text_buffer_create_tag(buf, ansi_colors[i].name, NULL);
gm_world_text_view_update_color_tag(view, ansi_colors[i].name, tag);
}
for (i = 0; i < (int)(sizeof(ansi_styles) / sizeof(ansinamepair)); i++) {
tag = gtk_text_buffer_create_tag(buf, ansi_styles[i].name, NULL);
switch (ansi_styles[i].code) {
case A_BOLD:
g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_ULTRABOLD,
NULL);
break;
case A_BOLD_OFF:
g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_NORMAL,
NULL);
break;
case A_FAINT:
g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_ULTRALIGHT,
NULL);
break;
case A_UNDERLINE:
g_object_set(G_OBJECT(tag), "underline", PANGO_UNDERLINE_SINGLE,
NULL);
break;
case A_DOUBLE_UNDERLINE:
g_object_set(G_OBJECT(tag), "underline", PANGO_UNDERLINE_DOUBLE,
NULL);
break;
case A_UNDERLINE_OFF:
g_object_set(G_OBJECT(tag), "underline", PANGO_UNDERLINE_NONE,
NULL);
break;
case A_CROSSOUT:
g_object_set(G_OBJECT(tag), "strikethrough", TRUE, NULL);
break;
case A_CROSSOUT_OFF:
g_object_set(G_OBJECT(tag), "strikethrough", FALSE, NULL);
break;
case A_ITALIC:
g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, NULL);
break;
case A_ITALIC_OFF:
g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_NORMAL, NULL);
break;
case A_INVISIBLE:
g_object_set(G_OBJECT(tag), "invisible", TRUE, NULL);
break;
case A_INVISIBLE_OFF:
g_object_set(G_OBJECT(tag), "invisible", FALSE, NULL);
break;
case A_NOWRAP:
g_object_set(G_OBJECT(tag), "wrap-mode", GTK_WRAP_NONE, NULL);
break;
default:
gtk_text_tag_table_remove(gtk_text_buffer_get_tag_table(buf),
tag);
break;
}
}
}
static void
gm_world_text_view_update_tags(GmWorldTextView *view) {
gint i;
for (i = 0; i < (int)(sizeof(ansi_colors) / sizeof(ansinamepair)); i++) {
gm_world_text_view_update_color_tag(view, ansi_colors[i].name, NULL);
}
/*
tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(
GM_WORLD_TEXT_VIEW_BUFFER(view)), "bold");
if (gm_color_table_bold(view->priv->color_table)) {
g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_NORMAL, NULL);
} else {
g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_ULTRABOLD, NULL);
}*/
}
static void
gm_world_text_view_init_tags(GmWorldTextView *view) {
GdkColor col;
gm_color_table_get(view->priv->color_table, "fg_default", &col);
gtk_widget_modify_text(GTK_WIDGET(view), GTK_STATE_NORMAL, &col);
gm_color_table_get(view->priv->color_table, "bg_default", &col);
gtk_widget_modify_base(GTK_WIDGET(view), GTK_STATE_NORMAL, &col);
gm_world_text_view_update_tags(view);
}
static void
gm_world_text_view_update_font(GmWorldTextView *view) {
PangoFontDescription *f = pango_font_description_from_string(
gm_color_table_font_description(view->priv->color_table));
if (f != NULL) {
gtk_widget_modify_font(GTK_WIDGET(view), f);
pango_font_description_free(f);
}
}
static gboolean
g_utf8_toint(gchar *str, guint *result) {
gunichar c;
*result = 0;
while ((c = g_utf8_get_char(str)) != '\0') {
if (g_unichar_isdigit(c)) {
*result = (*result * 10) + g_unichar_digit_value(c);
} else {
return FALSE;
}
str = g_utf8_next_char(str);
}
return TRUE;
}
static void
gm_world_text_view_blinker_iters(BlinkInfo *info, GtkTextIter *start,
GtkTextIter *end, GtkTextTag **tag_blink) {
GtkTextBuffer *buffer = GM_WORLD_TEXT_VIEW_BUFFER(info->view);
GtkTextTag *tag;
if (info->blink == 1) {
tag = info->view->priv->tag_blink;
} else {
tag = info->view->priv->tag_blink_fast;
}
gtk_text_buffer_get_iter_at_offset(buffer, start, info->offset);
if (end != NULL) {
*end = *start;
gtk_text_iter_forward_chars(end, g_utf8_strlen(info->fill, -1));
}
if (tag_blink != NULL) {
*tag_blink = tag;
}
}
static void
gm_world_text_view_blinker_set_visible(BlinkInfo *info, gboolean visible) {
GtkTextBuffer *buffer = GM_WORLD_TEXT_VIEW_BUFFER(info->view);
GtkTextIter start, end, s, e;
GtkTextTag *tag;
gm_world_text_view_blinker_iters(info, &start, &end, &tag);
gtk_text_buffer_delete(buffer, &start, &end);
gm_world_text_view_blinker_iters(info, &start, NULL, NULL);
if (visible) {
gtk_text_buffer_get_bounds(info->text, &s, &e);
gtk_text_buffer_insert_range(buffer, &start, &s, &e);
} else {
gtk_text_buffer_insert_with_tags(buffer, &start, info->fill, -1, tag,
NULL);
}
}
static gboolean
gm_world_text_view_blinker_timeout(BlinkInfo *info) {
GtkTextBuffer *buffer = GM_WORLD_TEXT_VIEW_BUFFER(info->view);
GtkTextTag *tag;
GtkTextIter start, end;
gm_world_text_view_blinker_set_visible(info, TRUE);
gm_world_text_view_blinker_iters(info, &start, &end, &tag);
gtk_text_buffer_remove_tag(buffer, tag, &start, &end);
gtk_text_buffer_apply_tag_by_name(buffer, "blink-after", &start, &end);
info->view->priv->blinkers = g_slist_remove(info->view->priv->blinkers,
info);
blink_info_free(info);
return FALSE;
}
static gboolean
gm_world_text_view_blink_timeout(GmWorldTextView *view) {
guint blink_state = view->priv->blink_state;
GSList *blinker;
BlinkInfo *info;
if (!view->priv->blinkers) {
view->priv->blink_state = 0;
view->priv->blink_timeout_id = 0;
return FALSE;
}
for (blinker = view->priv->blinkers; blinker; blinker = blinker->next) {
info = (BlinkInfo *)(blinker->data);
if (info->blink == 1 && (blink_state == 0 || blink_state == 2)) {
gm_world_text_view_blinker_set_visible(info, blink_state == 2);
} else if (info->blink == 2) {
gm_world_text_view_blinker_set_visible(info, blink_state == 1 ||
blink_state == 3);
}
}
if (blink_state == 3) {
view->priv->blink_state = 0;
} else {
view->priv->blink_state = blink_state + 1;
}
return TRUE;
}
static const gchar *
gm_world_text_view_tagname_from_code(guint code) {
gint i, len;
len = (sizeof(ansi_colors) / sizeof(ansinamepair));
for (i = 0; i < len; i++) {
if (ansi_colors[i].code == code) {
return ansi_colors[i].name;
}
}
len = sizeof(ansi_styles) / sizeof(ansinamepair);
for (i = 0; i < len; i++) {
if (ansi_styles[i].code == code) {
return ansi_styles[i].name;
}
}
return NULL;
}
static GList *
gm_world_text_view_tags_apply_bold(GmWorldTextView *view, GList *tags) {
GList *t, *item;
GtkTextTag *tag, *htag;
gboolean fg_set;
gchar *name, *hname;
gboolean added = FALSE;
GtkTextTagTable *tag_table = gtk_text_buffer_get_tag_table(
GM_WORLD_TEXT_VIEW_BUFFER(view));
t = g_list_copy(tags);
for (item = t; item; item = item->next) {
tag = GTK_TEXT_TAG(item->data);
g_object_get(G_OBJECT(tag), "foreground-set", &fg_set, NULL);
g_object_get(G_OBJECT(tag), "name", &name, NULL);
if (fg_set) {
hname = g_strconcat(name, "_h", NULL);
htag = gtk_text_tag_table_lookup(tag_table, hname);
if (htag) {
if (!g_list_find(tags, htag)) {
tags = g_list_append(tags, htag);
}
added = TRUE;
}
g_free(hname);
}
g_free(name);
}
/* If there is no high color added than this means we have a default color */
if (!added) {
tags = g_list_append(tags, gtk_text_tag_table_lookup(tag_table,
"fg_default_h"));
}
g_list_free(t);
return tags;
}
static GList *
gm_world_text_view_tags_fix_for_inverse(GmWorldTextView *view, GList *tags) {
gboolean bold = view->priv->last_info.bold;
gboolean fg_changed = FALSE, bg_changed = FALSE;
GList *new_tags = NULL, *item;
GtkTextTag *tag;
gchar *name, *base, *tagname;
int i;
GtkTextTagTable *tab = gtk_text_buffer_get_tag_table(
GM_WORLD_TEXT_VIEW_BUFFER(view));
for (item = tags; item; item = item->next) {
tag = GTK_TEXT_TAG(item->data);
g_object_get(G_OBJECT(tag), "name", &name, NULL);
if (strncmp(name, "fg_", 3) == 0) {
base = name + 3;
bg_changed = TRUE;
i = 0;
while (base[i] != '_' && base[i] != '\0')
i++;
base[i] = '\0';
tagname = g_strconcat("bg_", base, NULL);
new_tags = g_list_append(new_tags, gtk_text_tag_table_lookup(tab,
tagname));
g_free(tagname);
} else if (strncmp(name, "bg_", 3) == 0) {
base = name + 3;
fg_changed = TRUE;
if (bold) {
tagname = g_strconcat("fg_", base, "_h", NULL);
} else {
tagname = g_strconcat("fg_", base, NULL);
}
new_tags = g_list_append(new_tags, gtk_text_tag_table_lookup(tab,
tagname));
g_free(tagname);
} else if (strcmp(name, "inverse_bg") == 0) {
fg_changed = TRUE;
new_tags = g_list_append(new_tags, gtk_text_tag_table_lookup(tab,
"bg_default"));
} else if (strcmp(name, "inverse_fg") == 0) {
bg_changed = TRUE;
new_tags = g_list_append(new_tags, gtk_text_tag_table_lookup(tab,
"fg_default"));
} else {
new_tags = g_list_append(new_tags, tag);
}
g_free(name);
}
if (!bg_changed) {
new_tags = g_list_append(new_tags, gtk_text_tag_table_lookup(tab,
"inverse_bg"));
}
if (!fg_changed) {
new_tags = g_list_append(new_tags, gtk_text_tag_table_lookup(tab,
"inverse_fg"));
}
g_list_free(tags);
return new_tags;
}
static const gchar * tag_checks[] = {
"foreground-set",
"background-set",
"weight-set",
"underline-set",
"style-set",
"wrap-mode-set",
"invisible-set",
NULL
};
static gboolean
gm_world_text_view_tags_overlap(GtkTextTag *t1, GtkTextTag *t2) {
int i = 0;
gboolean val1, val2;
while (tag_checks[i]) {
g_object_get(G_OBJECT(t1), tag_checks[i], &val1, NULL);
g_object_get(G_OBJECT(t2), tag_checks[i], &val2, NULL);
if (val1 && val2) {
return TRUE;
}
++i;
}
return FALSE;
}
static GList *
gm_world_text_view_tags_remove_obsolete(GmWorldTextView *view, GList *tags,
GtkTextTag *tag) {
GList *t = g_list_copy(tags), *item;
GtkTextTag *tag2;
for (item = t; item; item = item->next) {
tag2 = GTK_TEXT_TAG(item->data);
if (gm_world_text_view_tags_overlap(tag, tag2)) {
tags = g_list_remove(tags, tag2);
}
}
g_list_free(t);
return tags;
}
static GList *
gm_world_text_view_tags_remove_bold(GmWorldTextView *view, GList *tags) {
GList *t = g_list_copy(tags), *item;
gchar *name;
for (item = t; item; item = item->next) {
g_object_get(G_OBJECT(item->data), "name", &name, NULL);
if (strncmp(name, "fg_", 3) == 0 && name[strlen(name) - 1] == 'h') {
tags = g_list_remove(tags, item->data);
}
g_free(name);
}
g_list_free(t);
return tags;
}
static void
gm_world_text_view_insert_text(GmWorldTextView *view, const gchar *text,
GmWorldTextViewInsertInfo *insert_info) {
GtkTextIter end_iter, start_iter, ins;
GtkTextBuffer *tb = GM_WORLD_TEXT_VIEW_BUFFER(view);
gint start_offset;
BlinkInfo *info;
GList *tags;
if (insert_info->bold) {
// If it needs to be bold then apply high colors for every color
insert_info->tags = gm_world_text_view_tags_apply_bold(view,
insert_info->tags);
} else {
// Remove all possible high colors
insert_info->tags = gm_world_text_view_tags_remove_bold(view,
insert_info->tags);
}
gtk_text_buffer_get_end_iter(tb, &end_iter);
start_offset = gtk_text_iter_get_offset(&end_iter);
gtk_text_buffer_insert(tb, &end_iter, text, -1);
gtk_text_buffer_get_iter_at_offset(tb, &start_iter, start_offset);
gtk_text_buffer_get_end_iter(tb, &end_iter);
for (tags = insert_info->tags; tags; tags = tags->next) {
gtk_text_buffer_apply_tag(tb, GTK_TEXT_TAG(tags->data), &start_iter,
&end_iter);
}
if (insert_info->blink) {
info = g_new0(BlinkInfo, 1);
info->blink = insert_info->blink;
info->offset = gtk_text_iter_get_offset(&start_iter);
info->view = view;
if (insert_info->blink == 1) {
gtk_text_buffer_apply_tag_by_name(tb, "blink", &start_iter,
&end_iter);
} else {
gtk_text_buffer_apply_tag_by_name(tb, "blink-fast", &start_iter,
&end_iter);
}
info->text = gtk_text_buffer_new(gtk_text_buffer_get_tag_table(tb));
gtk_text_buffer_get_iter_at_offset(info->text, &ins, 0);
gtk_text_buffer_insert_range(info->text, &ins, &start_iter, &end_iter);
info->fill = g_strnfill(g_utf8_strlen(text, -1), ' ');
if (!view->priv->blink_timeout_id) {
view->priv->blink_timeout_id = g_timeout_add(250,
(GSourceFunc)gm_world_text_view_blink_timeout, view);
}
/* Let them blink for about... 10 seconds */
info->timeout = g_timeout_add(10000,
(GSourceFunc)gm_world_text_view_blinker_timeout, info);
view->priv->blinkers = g_slist_append(view->priv->blinkers, info);
}
}
static GList *
gm_world_text_view_tags_add(GmWorldTextView *view, GList *tags,
GtkTextTag *tag) {
tags = gm_world_text_view_tags_remove_obsolete(view, tags, tag);
return g_list_append(tags, tag);
}
static void
gm_world_text_view_tag_urls(GmWorldTextView *view, gchar *text,
GtkTextIter *started, GtkTextIter *ended) {
gint num_matches, i;
GArray *start, *end;
gint s = 0, e = 0;
GtkTextBuffer *buffer = GM_WORLD_TEXT_VIEW_BUFFER(view);
GtkTextTag *url_tag = gtk_text_tag_table_lookup(
gtk_text_buffer_get_tag_table(buffer), "url");
GtkTextIter urlstart, urlend;
start = g_array_new(FALSE, FALSE, sizeof(gint));
end = g_array_new(FALSE, FALSE, sizeof(gint));
num_matches = gm_url_regex_match(text, strlen(text), start, end);
for (i = 0; i < num_matches; i++) {
urlstart = *started;
urlend = *started;
s = g_array_index(start, gint, i);
e = g_array_index(end, gint, i);
gtk_text_iter_forward_cursor_positions(&urlstart,
g_utf8_pointer_to_offset(text, text + s));
gtk_text_iter_forward_cursor_positions(&urlend,
g_utf8_pointer_to_offset(text, text + e));
gtk_text_buffer_apply_tag(buffer, url_tag, &urlstart, &urlend);
}
g_array_free(start, TRUE);
g_array_free(end, TRUE);
}
/* Public/exported functions */
GtkWidget *
gm_world_text_view_new() {
GtkWidget *result;
GmColorTable *table = gm_color_table_new();
result = gm_world_text_view_new_with_color_table(table);
g_object_unref(table);
return result;
}
GtkWidget *
gm_world_text_view_new_with_color_table(GmColorTable *color_table) {
GmWorldTextView *view = GM_WORLD_TEXT_VIEW(g_object_new(
GM_TYPE_WORLD_TEXT_VIEW, NULL));
gm_world_text_view_create_tags(view);
gm_world_text_view_set_color_table(view, color_table);
return GTK_WIDGET(view);
}
void
gm_world_text_view_set_color_table(GmWorldTextView *view,
GmColorTable *color_table) {
if (view->priv->color_table != NULL) {
g_signal_handlers_disconnect_by_func(view->priv->color_table,
on_gm_world_text_view_color_table_color_changed, view);
g_signal_handlers_disconnect_by_func(view->priv->color_table,
on_gm_world_text_view_color_table_font_changed, view);
g_object_unref(view->priv->color_table);
}
if (color_table != NULL) {
view->priv->color_table = g_object_ref(color_table);
g_signal_connect(view->priv->color_table, "color_changed",
G_CALLBACK(on_gm_world_text_view_color_table_color_changed),
view);
g_signal_connect(view->priv->color_table, "font_changed",
G_CALLBACK(on_gm_world_text_view_color_table_font_changed),
view);
gm_world_text_view_init_tags(view);
gm_world_text_view_update_font(view);
} else {
view->priv->color_table = NULL;
}
}
GmColorTable *
gm_world_text_view_color_table(GmWorldTextView *view) {
return view->priv->color_table;
}
gchar *
gm_world_text_view_insert(GmWorldTextView *view, const gchar *text) {
gchar *ptr, *ansi_start, *ansi_stop, **ansis;
gchar *name;
int i;
guint seq;
GtkTextBuffer *buffer = GM_WORLD_TEXT_VIEW_BUFFER(view);
GtkTextTagTable *tab = gtk_text_buffer_get_tag_table(buffer);
GmWorldTextViewInsertInfo *info = &(view->priv->last_info);
GString *new_line = NULL;
GtkTextIter started, ended;
GtkTextMark *start_mark;
GtkTextTag *tag;
gboolean skip;
g_return_val_if_fail(g_utf8_validate(text, -1, NULL), NULL);
if (info->text != NULL) {
new_line = g_string_new(info->text);
g_free(info->text);
info->text = NULL;
} else {
new_line = g_string_new("");
}
g_string_append(new_line, text);
if ((ptr = g_utf8_strchr(new_line->str, -1, '\x07'))) {
gdk_beep();
while (ptr) {
g_string_erase(new_line, g_utf8_pointer_to_offset(new_line->str,
ptr), 1);
ptr = g_utf8_strchr(new_line->str, -1, '\x07');
}
}
gm_debug_msg(DEBUG_DEFAULT, "GmWorldTextView.Insert: %s", new_line->str);
gtk_text_buffer_get_end_iter(buffer, &started);
start_mark = gtk_text_buffer_create_mark(buffer, "last-insertion",
&started, TRUE);
while (new_line != NULL &&
(ansi_start = strstr(new_line->str, "\x1B["))) {
i = g_utf8_pointer_to_offset(new_line->str, ansi_start);
if (i != 0) {
ptr = g_strndup(new_line->str, ansi_start - new_line->str);
gm_world_text_view_insert_text(view, ptr, info);
g_free(ptr);
}
if ((ansi_stop = g_utf8_strchr(ansi_start, -1, 'm'))) {
/* Advance to the ansi sequence */
ansi_start = ansi_start + 2;
ptr = g_strndup(ansi_start, ansi_stop - ansi_start);
ansis = g_strsplit(ptr, ";", -1);
g_free(ptr);
for (i = 0; ansis[i] != NULL; i++) {
if (ansis[i][0] != '\0' && g_utf8_toint(ansis[i], &seq)) {
skip = FALSE;
switch (seq) {
case A_FG_DEFAULT: case A_FG_BLACK: case A_FG_RED:
case A_FG_GREEN: case A_FG_YELLOW:
case A_FG_BLUE: case A_FG_PURPLE:
case A_FG_CYAN: case A_FG_WHITE:
if (info->inverse) {
seq += 10;
}
break;
case A_BG_DEFAULT: case A_BG_BLACK: case A_BG_RED:
case A_BG_GREEN: case A_BG_YELLOW:
case A_BG_BLUE: case A_BG_PURPLE:
case A_BG_CYAN: case A_BG_WHITE:
if (info->inverse) {
seq -= 10;
}
break;
case A_BOLD:
info->bold = TRUE;
if (info->inverse)
skip = TRUE;
break;
case A_FAINT:
info->bold = TRUE;
if (info->inverse)
skip = TRUE;
break;
case A_BOLD_OFF:
info->bold = FALSE;
if (info->inverse)
skip = TRUE;
break;
case A_INVERSE:
info->inverse = !(info->inverse);
info->tags =
gm_world_text_view_tags_fix_for_inverse(
view, info->tags);
break;
case A_INVERSE_OFF:
if (info->inverse) {
info->tags =
gm_world_text_view_tags_fix_for_inverse(
view, info->tags);
info->inverse = FALSE;
}
break;
case A_BLINK:
info->blink = 1;
break;
case A_BLINK_OFF:
info->blink = 0;
break;
case A_BLINK_FAST:
info->blink = 2;
break;
case A_BLINK_FAST_OFF:
info->blink = 0;
break;
case A_DEFAULT:
g_list_free(info->tags);
info->tags = NULL;
info->bold = FALSE;
info->inverse = FALSE;
info->blink = 0;
break;
}
name = (gchar *)gm_world_text_view_tagname_from_code(seq);
if (name != NULL && !skip) {
tag = gtk_text_tag_table_lookup(tab, name);
if (tag != NULL) {
info->tags = gm_world_text_view_tags_add(view,
info->tags, tag);
}
}
}
}
g_strfreev(ansis);
g_string_erase(new_line, 0, (ansi_stop - new_line->str) + 1);
} else {
info->text = g_strdup(ansi_start);
g_string_free(new_line, TRUE);
new_line = NULL;
}
}
if (new_line && new_line->len != 0) {
gm_world_text_view_insert_text(view, new_line->str, info);
}
if (new_line) {
g_string_free(new_line, TRUE);
}
gm_world_text_view_check_buffer_size(view);
gtk_text_buffer_get_iter_at_mark(buffer, &started, start_mark);
gtk_text_buffer_get_end_iter(buffer, &ended);
gtk_text_buffer_delete_mark(buffer, start_mark);
ptr = gtk_text_buffer_get_text(buffer, &started, &ended, FALSE);
gm_world_text_view_tag_urls(view, ptr, &started, &ended);
return ptr;
}
void
gm_world_text_view_get_metrics(GmWorldTextView *view, guint *width,
guint *height) {
*width = view->priv->character_width;
*height = view->priv->character_height;
}
/* Callbacks */
static gboolean
on_gm_world_text_view_url_event(GtkTextTag *tag, GObject *object,
GdkEvent *event, GtkTextIter *iter, GmWorldTextView *view) {
GtkTextIter start, end;
gchar *str = NULL;
GtkTextBuffer *buffer = GM_WORLD_TEXT_VIEW_BUFFER(view);
if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1 &&
!(event->button.state & GDK_CONTROL_MASK) &&
!(event->button.state & GDK_SHIFT_MASK) &&
!(event->button.state & GDK_MOD1_MASK)) {
start = end = *iter;
if (gtk_text_iter_backward_to_tag_toggle(&start, tag) &&
gtk_text_iter_forward_to_tag_toggle(&end, tag)) {
str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
}
if (str == NULL) {
return FALSE;
}
/* If the link is being selected, don't do anything. */
gtk_text_buffer_get_selection_bounds(buffer, &start, &end);
if (!gtk_text_iter_equal(&start, &end)) {
g_free(str);
return FALSE;
}
g_signal_emit(view, world_text_view_signals[URL_ACTIVATE], 0, str);
g_free(str);
}
return FALSE;
}
gboolean
on_gm_world_text_view_event(GmWorldTextView *view, GdkEventMotion *event,
GtkTextTag *tag) {
static GdkCursor *hand = NULL;
GtkTextWindowType type;
GtkTextIter iter;
GdkWindow *win;
gint x, y, buf_x, buf_y;
gboolean has_tag;
type = gtk_text_view_get_window_type(GTK_TEXT_VIEW(view), event->window);
if (type != GTK_TEXT_WINDOW_TEXT) {
return FALSE;
}
if (event->type != GDK_MOTION_NOTIFY) {
return FALSE;
}
/* Get where the pointer really is. */
win = gtk_text_view_get_window(GTK_TEXT_VIEW(view), type);
gdk_window_get_pointer(win, &x, &y, NULL);
/* Get the iter where the cursor is at */
gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(view), type,
x, y, &buf_x, &buf_y);
gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(view), &iter, buf_x,
buf_y);
if (!hand) {
hand = gdk_cursor_new(GDK_HAND1);
}
has_tag = gtk_text_iter_has_tag(&iter, tag);
if (has_tag && !view->priv->is_hand) {
view->priv->is_hand = TRUE;
gdk_window_set_cursor(win, hand);
} else if (!has_tag && view->priv->is_hand) {
view->priv->is_hand = FALSE;
gdk_window_set_cursor(win, NULL);
}
if (view->priv->drag_url && gtk_drag_check_threshold(GTK_WIDGET(view),
view->priv->drag_x, view->priv->drag_y, event->x, event->y)) {
view->priv->drag_url = FALSE;
gtk_drag_begin(GTK_WIDGET(view), view->priv->source_target_list,
GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK,
1, (GdkEvent *)event);
}
return FALSE;
}
static void
on_gm_world_text_view_copy_address(GtkMenuItem *item, GmWorldTextView *view) {
GtkClipboard *clipboard;
gchar *url = (gchar *)(g_object_get_data(G_OBJECT(gtk_widget_get_parent(
GTK_WIDGET(item))), "url"));
clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
gtk_clipboard_set_text(clipboard, url, -1);
}
static void
on_gm_world_text_view_open_address(GtkMenuItem *item, GmWorldTextView *view) {
gchar *url = (gchar *)(g_object_get_data(G_OBJECT(gtk_widget_get_parent(
GTK_WIDGET(item))), "url"));
g_signal_emit(view, world_text_view_signals[URL_ACTIVATE], 0, url);
}
/*
void
gm_world_text_view_color_table_fix_high_color(GtkTextTag *tag, gpointer data) {
gboolean bold = (gboolean)(GPOINTER_TO_INT(data));
gchar *name;
GdkColor *col;
g_object_get(G_OBJECT(tag), "name", &name, NULL);
if (strncmp(name, "fg_", 3) == 0 && name[strlen(name) - 1] == 'h') {
g_object_get(G_OBJECT(tag), "foreground-gdk", &col, NULL);
if (bold) {
g_object_set(G_OBJECT(tag), "foreground-gdk", col, NULL);
} else {
g_object_set(G_OBJECT(tag), "foreground-set", FALSE, NULL);
}
}
g_free(name);
}
void
on_gm_world_text_view_color_table_bold_toggled(GmColorTable *table,
gboolean bold, GmWorldTextView *view) {
GtkTextTag *tag;
GtkTextBuffer *buf = GM_WORLD_TEXT_VIEW_BUFFER(view);
GtkTextTagTable *tb = gtk_text_buffer_get_tag_table(buf);
// Fix bold tag
tag = gtk_text_tag_table_lookup(tb, "bold");
if (tag != NULL) {
if (bold) {
g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_NORMAL, NULL);
} else {
g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_ULTRABOLD, NULL);
}
// Fix high color tags
gtk_text_tag_table_foreach(tb,
gm_world_text_view_color_table_fix_high_color,
GINT_TO_POINTER(bold));
}
}*/
static void
on_gm_world_text_view_color_table_color_changed(GmColorTable *table,
gchar *name, GmWorldTextView *view) {
GdkColor col;
gm_world_text_view_update_color_tag(view, name, NULL);
if (strcmp(name, "fg_default") == 0) {
gm_color_table_get(view->priv->color_table, "fg_default", &col);
gtk_widget_modify_text(GTK_WIDGET(view), GTK_STATE_NORMAL, &col);
} else if (strcmp(name, "bg_default") == 0) {
gm_color_table_get(view->priv->color_table, "bg_default", &col);
gtk_widget_modify_base(GTK_WIDGET(view), GTK_STATE_NORMAL, &col);
}
}
static void
on_gm_world_text_view_color_table_font_changed(GmColorTable *table,
gchar *font_description, GmWorldTextView *view) {
gm_world_text_view_update_font(view);
}