1090 lines
30 KiB
C
1090 lines
30 KiB
C
#include <gtk/gtk.h>
|
|
#include <string.h>
|
|
|
|
#include "gm-world-text-view.h"
|
|
#include "../gm-color-table.h"
|
|
#include "../gm-marshal.h"
|
|
#include "../ansi.h"
|
|
#include "../gm-debug.h"
|
|
#include "../gm-support.h"
|
|
|
|
/* Callback definitions */
|
|
void on_gm_world_text_view_style_set(GmWorldTextView *view,
|
|
GtkStyle *previous_style, gpointer user_data);
|
|
gboolean on_gm_world_text_view_url_event(GtkTextTag *tag, GObject *object,
|
|
GdkEvent *event, GtkTextIter *iter, GmWorldTextView *view);
|
|
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);*/
|
|
void on_gm_world_text_view_color_table_color_changed(GmColorTable *table,
|
|
gchar *name, GmWorldTextView *view);
|
|
void on_gm_world_text_view_color_table_font_changed(GmColorTable *table,
|
|
gchar *font_description, GmWorldTextView *view);
|
|
void on_gm_world_text_view_populate_popup(GmWorldTextView *view, GtkMenu *menu,
|
|
gpointer user_data);
|
|
|
|
void gm_world_text_view_create_tags(GmWorldTextView *view);
|
|
void gm_world_text_view_init_tags(GmWorldTextView *view);
|
|
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;
|
|
|
|
struct _GmWorldTextViewInsertInfo {
|
|
GList *tags;
|
|
gboolean bold;
|
|
gboolean inverse;
|
|
gchar *text;
|
|
};
|
|
|
|
struct _GmWorldTextViewPrivate {
|
|
gboolean is_hand;
|
|
|
|
guint character_width;
|
|
guint character_height;
|
|
gint max_lines;
|
|
GmWorldTextViewInsertInfo last_info;
|
|
GmColorTable *color_table;
|
|
};
|
|
|
|
/* Signals */
|
|
|
|
enum {
|
|
URL_ACTIVATE,
|
|
CHARACTER_SIZE_CHANGED,
|
|
NUM_SIGNALS
|
|
};
|
|
|
|
static guint world_text_view_signals[NUM_SIGNALS] = {0};
|
|
|
|
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);
|
|
|
|
gm_world_text_view_set_color_table(view, NULL);
|
|
g_list_free(view->priv->last_info.tags);
|
|
g_free(view->priv->last_info.text);
|
|
|
|
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);
|
|
|
|
object_class->finalize = gm_world_text_view_finalize;
|
|
|
|
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));
|
|
}
|
|
|
|
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->is_hand = FALSE;
|
|
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;
|
|
|
|
// Connect style set signal
|
|
g_signal_connect(view, "style-set",
|
|
G_CALLBACK(on_gm_world_text_view_style_set), NULL);
|
|
g_signal_connect(view, "populate_popup",
|
|
G_CALLBACK(on_gm_world_text_view_populate_popup), NULL);
|
|
}
|
|
|
|
/* Private functions */
|
|
void
|
|
gm_world_text_view_update_color_tag(GmWorldTextView *view, const gchar *name,
|
|
GtkTextTag *tag) {
|
|
GtkTextBuffer *buf = GM_WORLD_TEXT_VIEW_BUFFER(view);
|
|
GdkColor col;
|
|
|
|
if (tag == NULL) {
|
|
tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buf), name);
|
|
}
|
|
|
|
if (view->priv->color_table != NULL) {
|
|
gm_color_table_get(view->priv->color_table, name, &col);
|
|
}
|
|
|
|
if (name[0] == 'f') {
|
|
g_object_set(G_OBJECT(tag), "foreground-gdk", &col, NULL);
|
|
|
|
/*if (name[strlen(name) - 1] == 'h') {
|
|
// Color tag for bold colors
|
|
//g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_NORMAL, NULL);
|
|
if (view->priv->color_table == NULL ||
|
|
!gm_color_table_bold(view->priv->color_table)) {
|
|
g_object_set(G_OBJECT(tag), "foreground-set", FALSE, NULL);
|
|
}
|
|
}*/
|
|
} else {
|
|
g_object_set(G_OBJECT(tag), "background-gdk", &col, NULL);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
|
|
if (view->priv->color_table != NULL) {
|
|
gm_color_table_get(view->priv->color_table, "fg_default", &col);
|
|
}
|
|
|
|
gtk_text_buffer_create_tag(buf, "inverse_bg", "background-gdk", &col, NULL);
|
|
|
|
if (view->priv->color_table != NULL) {
|
|
gm_color_table_get(view->priv->color_table, "bg_default", &col);
|
|
}
|
|
|
|
gtk_text_buffer_create_tag(buf, "inverse_fg", "background-gdk", &col, NULL);
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
gm_world_text_view_update_tags(GmWorldTextView *view) {
|
|
int i;
|
|
GtkTextTag *tag;
|
|
|
|
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);
|
|
//}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void
|
|
gm_world_text_view_insert_text(GmWorldTextView *view, const gchar *text,
|
|
GList *tags) {
|
|
GtkTextIter end_iter, start_iter;
|
|
GtkTextBuffer *tb = GM_WORLD_TEXT_VIEW_BUFFER(view);
|
|
gint start_offset;
|
|
gchar *name;
|
|
|
|
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);
|
|
|
|
if (tags) {
|
|
gtk_text_buffer_get_iter_at_offset(tb, &start_iter, start_offset);
|
|
gtk_text_buffer_get_end_iter(tb, &end_iter);
|
|
|
|
for (; tags; tags = tags->next) {
|
|
g_object_get(GTK_TEXT_TAG(tags->data), "name", &name, NULL);
|
|
g_free(name);
|
|
gtk_text_buffer_apply_tag(tb, GTK_TEXT_TAG(tags->data), &start_iter,
|
|
&end_iter);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
GList *
|
|
gm_world_text_view_tags_fix_for_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;
|
|
}
|
|
|
|
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,
|
|
"fg_default"));
|
|
} else if (strcmp(name, "inverse_fg") == 0) {
|
|
bg_changed = TRUE;
|
|
new_tags = g_list_append(new_tags, gtk_text_tag_table_lookup(tab,
|
|
"bg_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
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
GList *
|
|
gm_world_text_view_tags_remove_high(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;
|
|
}
|
|
|
|
GList *
|
|
gm_world_text_view_tags_add(GmWorldTextView *view, GList *tags, GtkTextTag *tag,
|
|
gboolean inverse) {
|
|
gboolean weight;
|
|
gboolean fg_set;
|
|
gchar *name, *nameh;
|
|
GtkTextTag *tagh;
|
|
|
|
tags = gm_world_text_view_tags_remove_obsolete(view, tags, tag);
|
|
|
|
g_object_get(G_OBJECT(tag), "foreground-set", &fg_set, NULL);
|
|
|
|
if (fg_set) {
|
|
tags = gm_world_text_view_tags_remove_high(view, tags);
|
|
g_object_get(G_OBJECT(tag), "name", &name, NULL);
|
|
nameh = g_strconcat(name, "_h", NULL);
|
|
|
|
tagh = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(
|
|
GM_WORLD_TEXT_VIEW_BUFFER(view)), nameh);
|
|
if (tagh) {
|
|
tags = g_list_append(tags, tagh);
|
|
}
|
|
|
|
g_free(name);
|
|
g_free(nameh);
|
|
}
|
|
|
|
g_object_get(G_OBJECT(tag), "weight-set", &weight, NULL);
|
|
|
|
if (weight) {
|
|
tags = gm_world_text_view_tags_fix_for_bold(view, tags);
|
|
}
|
|
|
|
return g_list_append(tags, tag);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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->tags);
|
|
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_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_DEFAULT:
|
|
g_list_free(info->tags);
|
|
info->tags = NULL;
|
|
info->bold = FALSE;
|
|
info->inverse = FALSE;
|
|
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,
|
|
info->inverse);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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->tags);
|
|
}
|
|
|
|
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 */
|
|
void
|
|
on_gm_world_text_view_style_set(GmWorldTextView *view, GtkStyle *previous_style,
|
|
gpointer user_data) {
|
|
GtkRcStyle *style = gtk_widget_get_modifier_style(GTK_WIDGET(view));
|
|
PangoContext *pc = gtk_widget_create_pango_context(GTK_WIDGET(view));
|
|
PangoLayout *pl;
|
|
gint cwidth, cheight;
|
|
|
|
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);
|
|
}
|
|
|
|
gboolean
|
|
on_gm_world_text_view_url_event(GtkTextTag *tag, GObject *object,
|
|
GdkEvent *event, GtkTextIter *iter, GmWorldTextView *view) {
|
|
GtkTextIter start, end;
|
|
gchar *str;
|
|
GtkTextBuffer *buffer = GM_WORLD_TEXT_VIEW_BUFFER(view);
|
|
|
|
/* If the link is being selected, don't do anything. */
|
|
gtk_text_buffer_get_selection_bounds(buffer, &start, &end);
|
|
|
|
if (gtk_text_iter_get_offset(&start) != gtk_text_iter_get_offset(&end)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) {
|
|
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);
|
|
g_signal_emit(view, world_text_view_signals[URL_ACTIVATE], 0, str);
|
|
g_free(str);
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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
|
|
on_gm_world_text_view_populate_popup(GmWorldTextView *view, GtkMenu *menu,
|
|
gpointer user_data) {
|
|
GtkTextTagTable *table;
|
|
GtkTextTag *tag;
|
|
gint x, y;
|
|
GtkTextIter iter, start, end;
|
|
GtkWidget *item;
|
|
gchar *str = NULL;
|
|
|
|
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);
|
|
}
|
|
|
|
gboolean
|
|
on_gm_world_text_view_event(GmWorldTextView *view, GdkEventMotion *event,
|
|
GtkTextTag *tag) {
|
|
static GdkCursor *hand = NULL;
|
|
static GdkCursor *beam = 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;
|
|
}
|
|
|
|
/* 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_FLEUR);
|
|
beam = gdk_cursor_new(GDK_XTERM);
|
|
}
|
|
|
|
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, beam);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
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));
|
|
}
|
|
}*/
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void
|
|
on_gm_world_text_view_color_table_font_changed(GmColorTable *table,
|
|
gchar *font_description, GmWorldTextView *view) {
|
|
gm_world_text_view_update_font(view);
|
|
}
|