/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * GimpTextBuffer * Copyright (C) 2010 Michael Natterer * * 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 3 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, see . */ #include "config.h" #include #include #include #include "libgimpbase/gimpbase.h" #include "libgimpcolor/gimpcolor.h" #include "widgets-types.h" #include "gimptextbuffer.h" #include "gimptextbuffer-serialize.h" #include "gimptexttag.h" #include "gimpwidgets-utils.h" #include "gimp-intl.h" enum { COLOR_APPLIED, LAST_SIGNAL }; /* local function prototypes */ static void gimp_text_buffer_constructed (GObject *object); static void gimp_text_buffer_dispose (GObject *object); static void gimp_text_buffer_finalize (GObject *object); static void gimp_text_buffer_mark_set (GtkTextBuffer *buffer, const GtkTextIter *location, GtkTextMark *mark); G_DEFINE_TYPE (GimpTextBuffer, gimp_text_buffer, GTK_TYPE_TEXT_BUFFER) #define parent_class gimp_text_buffer_parent_class static guint buffer_signals[LAST_SIGNAL] = { 0, }; static void gimp_text_buffer_class_init (GimpTextBufferClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkTextBufferClass *buffer_class = GTK_TEXT_BUFFER_CLASS (klass); object_class->constructed = gimp_text_buffer_constructed; object_class->dispose = gimp_text_buffer_dispose; object_class->finalize = gimp_text_buffer_finalize; buffer_class->mark_set = gimp_text_buffer_mark_set; buffer_signals[COLOR_APPLIED] = g_signal_new ("color-applied", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GimpTextBufferClass, color_applied), NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, GIMP_TYPE_RGB); } static void gimp_text_buffer_init (GimpTextBuffer *buffer) { buffer->markup_atom = gtk_text_buffer_register_serialize_format (GTK_TEXT_BUFFER (buffer), "application/x-gimp-pango-markup", gimp_text_buffer_serialize, NULL, NULL); gtk_text_buffer_register_deserialize_format (GTK_TEXT_BUFFER (buffer), "application/x-gimp-pango-markup", gimp_text_buffer_deserialize, NULL, NULL); } static void gimp_text_buffer_constructed (GObject *object) { GimpTextBuffer *buffer = GIMP_TEXT_BUFFER (object); G_OBJECT_CLASS (parent_class)->constructed (object); gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), "", -1); buffer->bold_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), "bold", "weight", PANGO_WEIGHT_BOLD, NULL); buffer->italic_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), "italic", "style", PANGO_STYLE_ITALIC, NULL); buffer->underline_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), "underline", "underline", PANGO_UNDERLINE_SINGLE, NULL); buffer->preedit_underline_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), "preedit-underline", "underline", PANGO_UNDERLINE_SINGLE, NULL); buffer->strikethrough_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), "strikethrough", "strikethrough", TRUE, NULL); } static void gimp_text_buffer_dispose (GObject *object) { /* GimpTextBuffer *buffer = GIMP_TEXT_BUFFER (object); */ G_OBJECT_CLASS (parent_class)->dispose (object); } static void gimp_text_buffer_finalize (GObject *object) { GimpTextBuffer *buffer = GIMP_TEXT_BUFFER (object); if (buffer->size_tags) { g_list_free (buffer->size_tags); buffer->size_tags = NULL; } if (buffer->baseline_tags) { g_list_free (buffer->baseline_tags); buffer->baseline_tags = NULL; } if (buffer->kerning_tags) { g_list_free (buffer->kerning_tags); buffer->kerning_tags = NULL; } if (buffer->font_tags) { g_list_free (buffer->font_tags); buffer->font_tags = NULL; } if (buffer->color_tags) { g_list_free (buffer->color_tags); buffer->color_tags = NULL; } if (buffer->preedit_color_tags) { g_list_free (buffer->preedit_color_tags); buffer->preedit_color_tags = NULL; } if (buffer->preedit_bg_color_tags) { g_list_free (buffer->preedit_bg_color_tags); buffer->preedit_bg_color_tags = NULL; } gimp_text_buffer_clear_insert_tags (buffer); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gimp_text_buffer_mark_set (GtkTextBuffer *buffer, const GtkTextIter *location, GtkTextMark *mark) { gimp_text_buffer_clear_insert_tags (GIMP_TEXT_BUFFER (buffer)); GTK_TEXT_BUFFER_CLASS (parent_class)->mark_set (buffer, location, mark); } /* public functions */ GimpTextBuffer * gimp_text_buffer_new (void) { return g_object_new (GIMP_TYPE_TEXT_BUFFER, NULL); } void gimp_text_buffer_set_text (GimpTextBuffer *buffer, const gchar *text) { g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); if (text == NULL) text = ""; gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), text, -1); gimp_text_buffer_clear_insert_tags (buffer); } gchar * gimp_text_buffer_get_text (GimpTextBuffer *buffer) { GtkTextIter start, end; g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL); gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end); return gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer), &start, &end, TRUE); } void gimp_text_buffer_set_markup (GimpTextBuffer *buffer, const gchar *markup) { g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); gimp_text_buffer_set_text (buffer, NULL); if (markup) { GtkTextTagTable *tag_table; GtkTextBuffer *content; GtkTextIter insert; GError *error = NULL; tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer)); content = gtk_text_buffer_new (tag_table); gtk_text_buffer_get_start_iter (content, &insert); if (! gtk_text_buffer_deserialize (GTK_TEXT_BUFFER (buffer), content, buffer->markup_atom, &insert, (const guint8 *) markup, -1, &error)) { g_printerr ("EEK: %s\n", error->message); g_clear_error (&error); } else { GtkTextIter start, end; gimp_text_buffer_post_deserialize (buffer, content); gtk_text_buffer_get_bounds (content, &start, &end); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &insert); gtk_text_buffer_insert_range (GTK_TEXT_BUFFER (buffer), &insert, &start, &end); } g_object_unref (content); } gimp_text_buffer_clear_insert_tags (buffer); } gchar * gimp_text_buffer_get_markup (GimpTextBuffer *buffer) { GtkTextTagTable *tag_table; GtkTextBuffer *content; GtkTextIter insert; GtkTextIter start, end; gchar *markup; gsize length; g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL); tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer)); content = gtk_text_buffer_new (tag_table); gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end); gtk_text_buffer_get_start_iter (content, &insert); gtk_text_buffer_insert_range (content, &insert, &start, &end); gimp_text_buffer_pre_serialize (buffer, content); gtk_text_buffer_get_bounds (content, &start, &end); markup = (gchar *) gtk_text_buffer_serialize (GTK_TEXT_BUFFER (buffer), content, buffer->markup_atom, &start, &end, &length); g_object_unref (content); return markup; } gboolean gimp_text_buffer_has_markup (GimpTextBuffer *buffer) { GtkTextIter iter; g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), FALSE); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter); do { GSList *tags = gtk_text_iter_get_tags (&iter); if (tags) { g_slist_free (tags); return TRUE; } } while (gtk_text_iter_forward_char (&iter)); return FALSE; } GtkTextTag * gimp_text_buffer_get_iter_size (GimpTextBuffer *buffer, const GtkTextIter *iter, gint *size) { GList *list; for (list = buffer->size_tags; list; list = g_list_next (list)) { GtkTextTag *tag = list->data; if (gtk_text_iter_has_tag (iter, tag)) { if (size) *size = gimp_text_tag_get_size (tag); return tag; } } if (size) *size = 0; return NULL; } GtkTextTag * gimp_text_buffer_get_size_tag (GimpTextBuffer *buffer, gint size) { GList *list; GtkTextTag *tag; gchar name[32]; for (list = buffer->size_tags; list; list = g_list_next (list)) { tag = list->data; if (size == gimp_text_tag_get_size (tag)) return tag; } g_snprintf (name, sizeof (name), "size-%d", size); tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), name, GIMP_TEXT_PROP_NAME_SIZE, size, NULL); buffer->size_tags = g_list_prepend (buffer->size_tags, tag); return tag; } void gimp_text_buffer_set_size (GimpTextBuffer *buffer, const GtkTextIter *start, const GtkTextIter *end, gint size) { GList *list; g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); g_return_if_fail (start != NULL); g_return_if_fail (end != NULL); if (gtk_text_iter_equal (start, end)) return; gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); for (list = buffer->size_tags; list; list = g_list_next (list)) { gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, start, end); } if (size != 0) { GtkTextTag *tag; tag = gimp_text_buffer_get_size_tag (buffer, size); gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, start, end); } gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); } void gimp_text_buffer_change_size (GimpTextBuffer *buffer, const GtkTextIter *start, const GtkTextIter *end, gint count) { GtkTextIter iter; GtkTextIter span_start; GtkTextIter span_end; GtkTextTag *span_tag; gint span_size; g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); g_return_if_fail (start != NULL); g_return_if_fail (end != NULL); if (gtk_text_iter_equal (start, end)) return; iter = *start; span_start = *start; span_tag = gimp_text_buffer_get_iter_size (buffer, &iter, &span_size); gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); do { GtkTextTag *iter_tag; gint iter_size; gtk_text_iter_forward_char (&iter); iter_tag = gimp_text_buffer_get_iter_size (buffer, &iter, &iter_size); span_end = iter; if (iter_size != span_size || gtk_text_iter_compare (&iter, end) >= 0) { if (span_size != 0) { gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), span_tag, &span_start, &span_end); } if ((span_size + count) > 0) { span_tag = gimp_text_buffer_get_size_tag (buffer, span_size + count); gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), span_tag, &span_start, &span_end); } span_start = iter; span_size = iter_size; span_tag = iter_tag; } /* We might have moved too far */ if (gtk_text_iter_compare (&iter, end) > 0) iter = *end; } while (! gtk_text_iter_equal (&iter, end)); gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); } GtkTextTag * gimp_text_buffer_get_iter_baseline (GimpTextBuffer *buffer, const GtkTextIter *iter, gint *baseline) { GList *list; for (list = buffer->baseline_tags; list; list = g_list_next (list)) { GtkTextTag *tag = list->data; if (gtk_text_iter_has_tag (iter, tag)) { if (baseline) *baseline = gimp_text_tag_get_baseline (tag); return tag; } } if (baseline) *baseline = 0; return NULL; } static GtkTextTag * gimp_text_buffer_get_baseline_tag (GimpTextBuffer *buffer, gint baseline) { GList *list; GtkTextTag *tag; gchar name[32]; for (list = buffer->baseline_tags; list; list = g_list_next (list)) { tag = list->data; if (baseline == gimp_text_tag_get_baseline (tag)) return tag; } g_snprintf (name, sizeof (name), "baseline-%d", baseline); tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), name, GIMP_TEXT_PROP_NAME_BASELINE, baseline, NULL); buffer->baseline_tags = g_list_prepend (buffer->baseline_tags, tag); return tag; } void gimp_text_buffer_set_baseline (GimpTextBuffer *buffer, const GtkTextIter *start, const GtkTextIter *end, gint baseline) { GList *list; g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); g_return_if_fail (start != NULL); g_return_if_fail (end != NULL); if (gtk_text_iter_equal (start, end)) return; gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); for (list = buffer->baseline_tags; list; list = g_list_next (list)) { gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, start, end); } if (baseline != 0) { GtkTextTag *tag; tag = gimp_text_buffer_get_baseline_tag (buffer, baseline); gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, start, end); } gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); } void gimp_text_buffer_change_baseline (GimpTextBuffer *buffer, const GtkTextIter *start, const GtkTextIter *end, gint count) { GtkTextIter iter; GtkTextIter span_start; GtkTextIter span_end; GtkTextTag *span_tag; gint span_baseline; g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); g_return_if_fail (start != NULL); g_return_if_fail (end != NULL); if (gtk_text_iter_equal (start, end)) return; iter = *start; span_start = *start; span_tag = gimp_text_buffer_get_iter_baseline (buffer, &iter, &span_baseline); gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); do { GtkTextTag *iter_tag; gint iter_baseline; gtk_text_iter_forward_char (&iter); iter_tag = gimp_text_buffer_get_iter_baseline (buffer, &iter, &iter_baseline); span_end = iter; if (iter_baseline != span_baseline || gtk_text_iter_compare (&iter, end) >= 0) { if (span_baseline != 0) { gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), span_tag, &span_start, &span_end); } if (span_baseline + count != 0) { span_tag = gimp_text_buffer_get_baseline_tag (buffer, span_baseline + count); gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), span_tag, &span_start, &span_end); } span_start = iter; span_baseline = iter_baseline; span_tag = iter_tag; } /* We might have moved too far */ if (gtk_text_iter_compare (&iter, end) > 0) iter = *end; } while (! gtk_text_iter_equal (&iter, end)); gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); } GtkTextTag * gimp_text_buffer_get_iter_kerning (GimpTextBuffer *buffer, const GtkTextIter *iter, gint *kerning) { GList *list; for (list = buffer->kerning_tags; list; list = g_list_next (list)) { GtkTextTag *tag = list->data; if (gtk_text_iter_has_tag (iter, tag)) { if (kerning) *kerning = gimp_text_tag_get_kerning (tag); return tag; } } if (kerning) *kerning = 0; return NULL; } static GtkTextTag * gimp_text_buffer_get_kerning_tag (GimpTextBuffer *buffer, gint kerning) { GList *list; GtkTextTag *tag; gchar name[32]; for (list = buffer->kerning_tags; list; list = g_list_next (list)) { tag = list->data; if (kerning == gimp_text_tag_get_kerning (tag)) return tag; } g_snprintf (name, sizeof (name), "kerning-%d", kerning); tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), name, GIMP_TEXT_PROP_NAME_KERNING, kerning, NULL); buffer->kerning_tags = g_list_prepend (buffer->kerning_tags, tag); return tag; } void gimp_text_buffer_set_kerning (GimpTextBuffer *buffer, const GtkTextIter *start, const GtkTextIter *end, gint kerning) { GList *list; g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); g_return_if_fail (start != NULL); g_return_if_fail (end != NULL); if (gtk_text_iter_equal (start, end)) return; gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); for (list = buffer->kerning_tags; list; list = g_list_next (list)) { gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, start, end); } if (kerning != 0) { GtkTextTag *tag; tag = gimp_text_buffer_get_kerning_tag (buffer, kerning); gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, start, end); } gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); } void gimp_text_buffer_change_kerning (GimpTextBuffer *buffer, const GtkTextIter *start, const GtkTextIter *end, gint count) { GtkTextIter iter; GtkTextIter span_start; GtkTextIter span_end; GtkTextTag *span_tag; gint span_kerning; g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); g_return_if_fail (start != NULL); g_return_if_fail (end != NULL); if (gtk_text_iter_equal (start, end)) return; iter = *start; span_start = *start; span_tag = gimp_text_buffer_get_iter_kerning (buffer, &iter, &span_kerning); gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); do { GtkTextTag *iter_tag; gint iter_kerning; gtk_text_iter_forward_char (&iter); iter_tag = gimp_text_buffer_get_iter_kerning (buffer, &iter, &iter_kerning); span_end = iter; if (iter_kerning != span_kerning || gtk_text_iter_compare (&iter, end) >= 0) { if (span_kerning != 0) { gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), span_tag, &span_start, &span_end); } if (span_kerning + count != 0) { span_tag = gimp_text_buffer_get_kerning_tag (buffer, span_kerning + count); gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), span_tag, &span_start, &span_end); } span_start = iter; span_kerning = iter_kerning; span_tag = iter_tag; } /* We might have moved too far */ if (gtk_text_iter_compare (&iter, end) > 0) iter = *end; } while (! gtk_text_iter_equal (&iter, end)); gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); } GtkTextTag * gimp_text_buffer_get_iter_font (GimpTextBuffer *buffer, const GtkTextIter *iter, gchar **font) { GList *list; for (list = buffer->font_tags; list; list = g_list_next (list)) { GtkTextTag *tag = list->data; if (gtk_text_iter_has_tag (iter, tag)) { if (font) *font = gimp_text_tag_get_font (tag); return tag; } } if (font) *font = NULL; return NULL; } GtkTextTag * gimp_text_buffer_get_font_tag (GimpTextBuffer *buffer, const gchar *font) { GList *list; GtkTextTag *tag; gchar name[256]; PangoFontDescription *pfd = pango_font_description_from_string (font); char *description = pango_font_description_to_string (pfd); pango_font_description_free (pfd); for (list = buffer->font_tags; list; list = g_list_next (list)) { gchar *tag_font; tag = list->data; tag_font = gimp_text_tag_get_font (tag); if (! strcmp (description, tag_font)) { g_free (tag_font); g_free (description); return tag; } g_free (tag_font); } g_snprintf (name, sizeof (name), "font-%s", description); tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), name, "font", description, NULL); gtk_text_tag_set_priority (tag, 0); g_free (description); buffer->font_tags = g_list_prepend (buffer->font_tags, tag); return tag; } void gimp_text_buffer_set_font (GimpTextBuffer *buffer, const GtkTextIter *start, const GtkTextIter *end, const gchar *font) { GList *list; g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); g_return_if_fail (start != NULL); g_return_if_fail (end != NULL); if (gtk_text_iter_equal (start, end)) return; gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); for (list = buffer->font_tags; list; list = g_list_next (list)) { gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, start, end); } if (font) { GtkTextTag *tag = gimp_text_buffer_get_font_tag (buffer, font); gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, start, end); } gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); } GtkTextTag * gimp_text_buffer_get_iter_color (GimpTextBuffer *buffer, const GtkTextIter *iter, GimpRGB *color) { GList *list; for (list = buffer->color_tags; list; list = g_list_next (list)) { GtkTextTag *tag = list->data; if (gtk_text_iter_has_tag (iter, tag)) { if (color) gimp_text_tag_get_fg_color (tag, color); return tag; } } return NULL; } GtkTextTag * gimp_text_buffer_get_color_tag (GimpTextBuffer *buffer, const GimpRGB *color) { GList *list; GtkTextTag *tag; gchar name[256]; GdkColor gdk_color; guchar r, g, b; gimp_rgb_get_uchar (color, &r, &g, &b); for (list = buffer->color_tags; list; list = g_list_next (list)) { GimpRGB tag_color; guchar tag_r, tag_g, tag_b; tag = list->data; gimp_text_tag_get_fg_color (tag, &tag_color); gimp_rgb_get_uchar (&tag_color, &tag_r, &tag_g, &tag_b); /* Do not compare the alpha channel, since it's unused */ if (tag_r == r && tag_g == g && tag_b == b) { return tag; } } g_snprintf (name, sizeof (name), "color-#%02x%02x%02x", r, g, b); gimp_rgb_get_gdk_color (color, &gdk_color); tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), name, "foreground-gdk", &gdk_color, "foreground-set", TRUE, NULL); buffer->color_tags = g_list_prepend (buffer->color_tags, tag); return tag; } void gimp_text_buffer_set_color (GimpTextBuffer *buffer, const GtkTextIter *start, const GtkTextIter *end, const GimpRGB *color) { GList *list; g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); g_return_if_fail (start != NULL); g_return_if_fail (end != NULL); if (gtk_text_iter_equal (start, end)) return; gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); for (list = buffer->color_tags; list; list = g_list_next (list)) { gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, start, end); } if (color) { GtkTextTag *tag = gimp_text_buffer_get_color_tag (buffer, color); gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, start, end); g_signal_emit (buffer, buffer_signals[COLOR_APPLIED], 0, color); } gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); } GtkTextTag * gimp_text_buffer_get_preedit_color_tag (GimpTextBuffer *buffer, const GimpRGB *color) { GList *list; GtkTextTag *tag; gchar name[256]; GdkColor gdk_color; guchar r, g, b; gimp_rgb_get_uchar (color, &r, &g, &b); for (list = buffer->preedit_color_tags; list; list = g_list_next (list)) { GimpRGB tag_color; guchar tag_r, tag_g, tag_b; tag = list->data; gimp_text_tag_get_fg_color (tag, &tag_color); gimp_rgb_get_uchar (&tag_color, &tag_r, &tag_g, &tag_b); /* Do not compare the alpha channel, since it's unused */ if (tag_r == r && tag_g == g && tag_b == b) { return tag; } } g_snprintf (name, sizeof (name), "preedit-color-#%02x%02x%02x", r, g, b); gimp_rgb_get_gdk_color (color, &gdk_color); tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), name, "foreground-gdk", &gdk_color, "foreground-set", TRUE, NULL); buffer->preedit_color_tags = g_list_prepend (buffer->preedit_color_tags, tag); return tag; } void gimp_text_buffer_set_preedit_color (GimpTextBuffer *buffer, const GtkTextIter *start, const GtkTextIter *end, const GimpRGB *color) { GList *list; g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); g_return_if_fail (start != NULL); g_return_if_fail (end != NULL); if (gtk_text_iter_equal (start, end)) return; gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); for (list = buffer->preedit_color_tags; list; list = g_list_next (list)) { gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, start, end); } if (color) { GtkTextTag *tag = gimp_text_buffer_get_preedit_color_tag (buffer, color); gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, start, end); } gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); } GtkTextTag * gimp_text_buffer_get_preedit_bg_color_tag (GimpTextBuffer *buffer, const GimpRGB *color) { GList *list; GtkTextTag *tag; gchar name[256]; GdkColor gdk_color; guchar r, g, b; gimp_rgb_get_uchar (color, &r, &g, &b); for (list = buffer->preedit_bg_color_tags; list; list = g_list_next (list)) { GimpRGB tag_color; guchar tag_r, tag_g, tag_b; tag = list->data; gimp_text_tag_get_bg_color (tag, &tag_color); gimp_rgb_get_uchar (&tag_color, &tag_r, &tag_g, &tag_b); /* Do not compare the alpha channel, since it's unused */ if (tag_r == r && tag_g == g && tag_b == b) { return tag; } } g_snprintf (name, sizeof (name), "bg-color-#%02x%02x%02x", r, g, b); gimp_rgb_get_gdk_color (color, &gdk_color); tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), name, "background-gdk", &gdk_color, "background-set", TRUE, NULL); buffer->preedit_bg_color_tags = g_list_prepend (buffer->preedit_bg_color_tags, tag); return tag; } void gimp_text_buffer_set_preedit_bg_color (GimpTextBuffer *buffer, const GtkTextIter *start, const GtkTextIter *end, const GimpRGB *color) { GList *list; g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); g_return_if_fail (start != NULL); g_return_if_fail (end != NULL); if (gtk_text_iter_equal (start, end)) return; gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); for (list = buffer->preedit_bg_color_tags; list; list = g_list_next (list)) { gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, start, end); } if (color) { GtkTextTag *tag = gimp_text_buffer_get_preedit_bg_color_tag (buffer, color); gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, start, end); } gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); } /* Pango markup attribute names */ #define GIMP_TEXT_ATTR_NAME_SIZE "size" #define GIMP_TEXT_ATTR_NAME_BASELINE "rise" #define GIMP_TEXT_ATTR_NAME_KERNING "letter_spacing" #define GIMP_TEXT_ATTR_NAME_FONT "font" #define GIMP_TEXT_ATTR_NAME_STYLE "style" #define GIMP_TEXT_ATTR_NAME_COLOR "foreground" #define GIMP_TEXT_ATTR_NAME_FG_COLOR "fgcolor" #define GIMP_TEXT_ATTR_NAME_BG_COLOR "background" #define GIMP_TEXT_ATTR_NAME_UNDERLINE "underline" const gchar * gimp_text_buffer_tag_to_name (GimpTextBuffer *buffer, GtkTextTag *tag, const gchar **attribute, gchar **value) { g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL); g_return_val_if_fail (GTK_IS_TEXT_TAG (tag), NULL); if (attribute) *attribute = NULL; if (value) *value = NULL; if (tag == buffer->bold_tag) { return "b"; } else if (tag == buffer->italic_tag) { return "i"; } else if (tag == buffer->underline_tag) { return "u"; } else if (tag == buffer->strikethrough_tag) { return "s"; } else if (g_list_find (buffer->size_tags, tag)) { if (attribute) *attribute = GIMP_TEXT_ATTR_NAME_SIZE; if (value) *value = g_strdup_printf ("%d", gimp_text_tag_get_size (tag)); return "span"; } else if (g_list_find (buffer->baseline_tags, tag)) { if (attribute) *attribute = GIMP_TEXT_ATTR_NAME_BASELINE; if (value) *value = g_strdup_printf ("%d", gimp_text_tag_get_baseline (tag)); return "span"; } else if (g_list_find (buffer->kerning_tags, tag)) { if (attribute) *attribute = GIMP_TEXT_ATTR_NAME_KERNING; if (value) *value = g_strdup_printf ("%d", gimp_text_tag_get_kerning (tag)); return "span"; } else if (g_list_find (buffer->font_tags, tag)) { if (attribute) *attribute = GIMP_TEXT_ATTR_NAME_FONT; if (value) *value = gimp_text_tag_get_font (tag); return "span"; } else if (g_list_find (buffer->color_tags, tag)) { if (attribute) *attribute = GIMP_TEXT_ATTR_NAME_COLOR; if (value) { GimpRGB color; guchar r, g, b; gimp_text_tag_get_fg_color (tag, &color); gimp_rgb_get_uchar (&color, &r, &g, &b); *value = g_strdup_printf ("#%02x%02x%02x", r, g, b); } return "span"; } else if (g_list_find (buffer->preedit_color_tags, tag)) { /* "foreground" and "fgcolor" attributes are similar, but I use * one or the other as a trick to differentiate the color chosen * from the user and a display color for preedit. */ if (attribute) *attribute = GIMP_TEXT_ATTR_NAME_FG_COLOR; if (value) { GimpRGB color; guchar r, g, b; gimp_text_tag_get_fg_color (tag, &color); gimp_rgb_get_uchar (&color, &r, &g, &b); *value = g_strdup_printf ("#%02x%02x%02x", r, g, b); } return "span"; } else if (g_list_find (buffer->preedit_bg_color_tags, tag)) { if (attribute) *attribute = GIMP_TEXT_ATTR_NAME_BG_COLOR; if (value) { GimpRGB color; guchar r, g, b; gimp_text_tag_get_bg_color (tag, &color); gimp_rgb_get_uchar (&color, &r, &g, &b); *value = g_strdup_printf ("#%02x%02x%02x", r, g, b); } return "span"; } else if (tag == buffer->preedit_underline_tag) { if (attribute) *attribute = GIMP_TEXT_ATTR_NAME_UNDERLINE; if (value) *value = g_strdup ("single"); return "span"; } return NULL; } GtkTextTag * gimp_text_buffer_name_to_tag (GimpTextBuffer *buffer, const gchar *name, const gchar *attribute, const gchar *value) { g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL); g_return_val_if_fail (name != NULL, NULL); if (! strcmp (name, "b")) { return buffer->bold_tag; } else if (! strcmp (name, "i")) { return buffer->italic_tag; } else if (! strcmp (name, "u")) { return buffer->underline_tag; } else if (! strcmp (name, "s")) { return buffer->strikethrough_tag; } else if (! strcmp (name, "span") && attribute != NULL && value != NULL) { if (! strcmp (attribute, GIMP_TEXT_ATTR_NAME_SIZE)) { return gimp_text_buffer_get_size_tag (buffer, atoi (value)); } else if (! strcmp (attribute, GIMP_TEXT_ATTR_NAME_BASELINE)) { return gimp_text_buffer_get_baseline_tag (buffer, atoi (value)); } else if (! strcmp (attribute, GIMP_TEXT_ATTR_NAME_KERNING)) { return gimp_text_buffer_get_kerning_tag (buffer, atoi (value)); } else if (! strcmp (attribute, GIMP_TEXT_ATTR_NAME_FONT)) { return gimp_text_buffer_get_font_tag (buffer, value); } else if (! strcmp (attribute, GIMP_TEXT_ATTR_NAME_COLOR)) { GimpRGB color; guint r, g, b; sscanf (value, "#%02x%02x%02x", &r, &g, &b); gimp_rgb_set_uchar (&color, r, g, b); return gimp_text_buffer_get_color_tag (buffer, &color); } } return NULL; } void gimp_text_buffer_set_insert_tags (GimpTextBuffer *buffer, GList *insert_tags, GList *remove_tags) { g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); buffer->insert_tags_set = TRUE; g_list_free (buffer->insert_tags); g_list_free (buffer->remove_tags); buffer->insert_tags = insert_tags; buffer->remove_tags = remove_tags; } void gimp_text_buffer_clear_insert_tags (GimpTextBuffer *buffer) { g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); buffer->insert_tags_set = FALSE; g_list_free (buffer->insert_tags); g_list_free (buffer->remove_tags); buffer->insert_tags = NULL; buffer->remove_tags = NULL; } void gimp_text_buffer_insert (GimpTextBuffer *buffer, const gchar *text) { GtkTextIter iter, start; gint start_offset; gboolean insert_tags_set; GList *insert_tags; GList *remove_tags; GSList *tags_off = NULL; GimpRGB color; g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), &iter, gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer))); start_offset = gtk_text_iter_get_offset (&iter); insert_tags_set = buffer->insert_tags_set; insert_tags = buffer->insert_tags; remove_tags = buffer->remove_tags; buffer->insert_tags_set = FALSE; buffer->insert_tags = NULL; buffer->remove_tags = NULL; tags_off = gtk_text_iter_get_toggled_tags (&iter, FALSE); gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); gtk_text_buffer_insert (GTK_TEXT_BUFFER (buffer), &iter, text, -1); gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (buffer), &start, start_offset); if (insert_tags_set) { GList *list; for (list = remove_tags; list; list = g_list_next (list)) { GtkTextTag *tag = list->data; gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), tag, &start, &iter); } for (list = insert_tags; list; list = g_list_next (list)) { GtkTextTag *tag = list->data; gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, &start, &iter); } } if (tags_off) { GSList *slist; for (slist = tags_off; slist; slist = g_slist_next (slist)) { GtkTextTag *tag = slist->data; if (! g_list_find (remove_tags, tag) && ! g_list_find (buffer->kerning_tags, tag)) { gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, &start, &iter); } } g_slist_free (tags_off); } g_list_free (remove_tags); g_list_free (insert_tags); if (gimp_text_buffer_get_iter_color (buffer, &start, &color)) { g_signal_emit (buffer, buffer_signals[COLOR_APPLIED], 0, &color); } gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); } gint gimp_text_buffer_get_iter_index (GimpTextBuffer *buffer, GtkTextIter *iter, gboolean layout_index) { GtkTextIter start; gchar *string; gint index; g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), 0); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &start); string = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer), &start, iter, TRUE); index = strlen (string); g_free (string); if (layout_index) { do { GSList *tags = gtk_text_iter_get_tags (&start); GSList *list; for (list = tags; list; list = g_slist_next (list)) { GtkTextTag *tag = list->data; if (g_list_find (buffer->kerning_tags, tag)) { index += WORD_JOINER_LENGTH; break; } } g_slist_free (tags); gtk_text_iter_forward_char (&start); /* We might have moved too far */ if (gtk_text_iter_compare (&start, iter) > 0) start = *iter; } while (! gtk_text_iter_equal (&start, iter)); } return index; } void gimp_text_buffer_get_iter_at_index (GimpTextBuffer *buffer, GtkTextIter *iter, gint index, gboolean layout_index) { GtkTextIter start; GtkTextIter end; gchar *string; g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end); string = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer), &start, &end, TRUE); if (layout_index) { gchar *my_string = string; gint my_index = 0; gchar *tmp; do { GSList *tags = gtk_text_iter_get_tags (&start); GSList *list; tmp = g_utf8_next_char (my_string); my_index += (tmp - my_string); my_string = tmp; for (list = tags; list; list = g_slist_next (list)) { GtkTextTag *tag = list->data; if (g_list_find (buffer->kerning_tags, tag)) { index = MAX (0, index - WORD_JOINER_LENGTH); break; } } g_slist_free (tags); gtk_text_iter_forward_char (&start); /* We might have moved too far */ if (gtk_text_iter_compare (&start, &end) > 0) start = end; } while (my_index < index && ! gtk_text_iter_equal (&start, &end)); } string[index] = '\0'; gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (buffer), iter, g_utf8_strlen (string, -1)); g_free (string); } gboolean gimp_text_buffer_load (GimpTextBuffer *buffer, GFile *file, GError **error) { GInputStream *input; gchar buf[2048]; gint to_read; gsize bytes_read; gsize total_read = 0; gint remaining = 0; GtkTextIter iter; GError *my_error = NULL; g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), FALSE); g_return_val_if_fail (G_IS_FILE (file), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); input = G_INPUT_STREAM (g_file_read (file, NULL, &my_error)); if (! input) { g_set_error (error, my_error->domain, my_error->code, _("Could not open '%s' for reading: %s"), gimp_file_get_utf8_name (file), my_error->message); g_clear_error (&my_error); return FALSE; } gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); gimp_text_buffer_set_text (buffer, NULL); gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (buffer), &iter); do { gboolean success; const char *leftover; to_read = sizeof (buf) - remaining - 1; success = g_input_stream_read_all (input, buf + remaining, to_read, &bytes_read, NULL, &my_error); total_read += bytes_read; buf[bytes_read + remaining] = '\0'; g_utf8_validate (buf, bytes_read + remaining, &leftover); gtk_text_buffer_insert (GTK_TEXT_BUFFER (buffer), &iter, buf, leftover - buf); gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (buffer), &iter); remaining = (buf + remaining + bytes_read) - leftover; memmove (buf, leftover, remaining); if (! success) { if (total_read > 0) { g_message (_("Input file '%s' appears truncated: %s"), gimp_file_get_utf8_name (file), my_error->message); g_clear_error (&my_error); break; } gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); g_object_unref (input); g_propagate_error (error, my_error); return FALSE; } } while (remaining <= 6 && bytes_read == to_read); if (remaining) g_message (_("Invalid UTF-8 data in file '%s'."), gimp_file_get_utf8_name (file)); gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); g_object_unref (input); return TRUE; } gboolean gimp_text_buffer_save (GimpTextBuffer *buffer, GFile *file, gboolean selection_only, GError **error) { GOutputStream *output; GtkTextIter start_iter; GtkTextIter end_iter; gchar *text_contents; GError *my_error = NULL; g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), FALSE); g_return_val_if_fail (G_IS_FILE (file), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); output = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error)); if (! output) return FALSE; if (selection_only) gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer), &start_iter, &end_iter); else gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start_iter, &end_iter); text_contents = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer), &start_iter, &end_iter, TRUE); if (text_contents) { gint text_length = strlen (text_contents); if (! g_output_stream_write_all (output, text_contents, text_length, NULL, NULL, &my_error)) { GCancellable *cancellable = g_cancellable_new (); g_set_error (error, my_error->domain, my_error->code, _("Writing text file '%s' failed: %s"), gimp_file_get_utf8_name (file), my_error->message); g_clear_error (&my_error); g_free (text_contents); /* Cancel the overwrite initiated by g_file_replace(). */ g_cancellable_cancel (cancellable); g_output_stream_close (output, cancellable, NULL); g_object_unref (cancellable); g_object_unref (output); return FALSE; } g_free (text_contents); } g_object_unref (output); return TRUE; }