Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Gimp/app/widgets/   (Gimp Bildeditor Version 3.2.0©)  Datei vom 14.2.2026 mit Größe 50 kB image not shown  

Quelle  gimptextbuffer.c   Sprache: C

 

/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * GimpTextBuffer
 * Copyright (C) 2010  Michael Natterer <mitch@gimp.org>
 *
 * 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 <https://www.gnu.org/licenses/>.
 */


#include "config.h"

#include <stdlib.h>

#include <gegl.h>
#include <gtk/gtk.h>

#include "libgimpbase/gimpbase.h"
#include "libgimpcolor/gimpcolor.h"

#include "widgets-types.h"

#include "gimptextbuffer.h"
#include "gimptextbuffer-serialize.h"
#include "gimptexttag.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, NULL,
                  G_TYPE_NONE, 1,
                  GEGL_TYPE_COLOR);
}

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,
                                 GeglColor         **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,
                                GeglColor      *color)
{
  GList      *list;
  GtkTextTag *tag;
  gchar       name[256];
  gdouble     rgba[4];

  for (list = buffer->color_tags; list; list = g_list_next (list))
    {
      GeglColor *tag_color;

      tag = list->data;

      gimp_text_tag_get_fg_color (tag, &tag_color);

      /* Do not compare the alpha channel, since it's unused */
      if (gimp_color_is_perceptually_identical (color, tag_color))
        return tag;
    }

  gegl_color_get_pixel (color, babl_format ("R'G'B'A double"), rgba);
  g_snprintf (name, sizeof (name), "color-#%02x%02x%02x",
              (guint) (rgba[0] * 255), (guint) (rgba[1] * 255), (guint) (rgba[2] * 255));

  tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
                                    name,
                                    "foreground-rgba", (GdkRGBA *) rgba,
                                    "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,
                            GeglColor         *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,
                                        GeglColor      *color)
{
  GList      *list;
  GtkTextTag *tag;
  gchar       name[256];
  gdouble     rgba[4];

  for (list = buffer->preedit_color_tags; list; list = g_list_next (list))
    {
      GeglColor *tag_color;

      tag = list->data;

      gimp_text_tag_get_fg_color (tag, &tag_color);

      /* Do not compare the alpha channel, since it's unused */
      if (gimp_color_is_perceptually_identical (color, tag_color))
        return tag;
    }

  /* We save the color as unbounded sRGB. Is it right? TODO */
  gegl_color_get_pixel (color, babl_format ("R'G'B'A double"), rgba);
  g_snprintf (name, sizeof (name), "preedit-color-#%02x%02x%02x",
              (guint) (rgba[0] * 255), (guint) (rgba[1] * 255), (guint) (rgba[2] * 255));

  tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
                                    name,
                                    "foreground-rgba", (GdkRGBA *) rgba,
                                    "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,
                                    GeglColor         *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,
                                           GeglColor      *color)
{
  GList      *list;
  GtkTextTag *tag;
  gchar       name[256];
  gdouble     rgba[4];

  for (list = buffer->preedit_bg_color_tags; list; list = g_list_next (list))
    {
      GeglColor *tag_color;

      tag = list->data;

      gimp_text_tag_get_bg_color (tag, &tag_color);

      /* Do not compare the alpha channel, since it's unused */
      if (gimp_color_is_perceptually_identical (color, tag_color))
        return tag;
    }

  gegl_color_get_pixel (color, babl_format ("R'G'B'A double"), rgba);
  g_snprintf (name, sizeof (name), "bg-color-#%02x%02x%02x",
              (guint) (rgba[0] * 255), (guint) (rgba[1] * 255), (guint) (rgba[2] * 255));

  tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
                                    name,
                                    "background-rgba", (GdkRGBA *) rgba,
                                    "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,
                                       GeglColor         *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)
        {
          GeglColor *color;
          guchar     rgb[3];

          gimp_text_tag_get_fg_color (tag, &color);
          gegl_color_get_pixel (color, babl_format ("R'G'B' u8"), rgb);
          *value = g_strdup_printf ("#%02x%02x%02x", rgb[0], rgb[1], rgb[2]);
          g_object_unref (color);
        }

      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)
        {
          GeglColor *color;
          guchar     rgb[3];

          gimp_text_tag_get_fg_color (tag, &color);
          gegl_color_get_pixel (color, babl_format ("R'G'B' u8"), rgb);
          *value = g_strdup_printf ("#%02x%02x%02x", rgb[0], rgb[1], rgb[2]);
          g_object_unref (color);
        }

      return "span";
    }
  else if (g_list_find (buffer->preedit_bg_color_tags, tag))
    {
      if (attribute)
        *attribute = GIMP_TEXT_ATTR_NAME_BG_COLOR;

      if (value)
        {
          GeglColor *color;
          guchar     rgb[3];

          gimp_text_tag_get_bg_color (tag, &color);
          gegl_color_get_pixel (color, babl_format ("R'G'B' u8"), rgb);
          *value = g_strdup_printf ("#%02x%02x%02x", rgb[0], rgb[1], rgb[2]);
          g_object_unref (color);
        }

      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))
        {
          GtkTextTag *tag;
          GeglColor  *color;
          guint       r, g, b;
          guchar      rgb[3];

          sscanf (value, "#%02x%02x%02x", &r, &g, &b);
          rgb[0] = r;
          rgb[1] = g;
          rgb[2] = b;

          color = gegl_color_new (NULL);
          gegl_color_set_pixel (color, babl_format ("R'G'B' u8"), rgb);

          tag = gimp_text_buffer_get_color_tag (buffer, color);
          g_object_unref (color);

          return tag;
        }
    }

  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;
  GeglColor   *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);
      g_object_unref (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;
}

/**
 * gimp_text_buffer_get_tags_on_iter:
 * @buffer: a #GimpTextBuffer
 * @iter: a position in @buffer
 *
 * Returns a list of tags that apply to @iter, in ascending order
 * of priority (highest-priority tags are last). The GtkTextTag in
 * the list don’t have a reference added, but you have to free the
 * list itself.
 *
 * Returns: (element-type GtkTextTag) (transfer container): GList of #GtkTextTag
 */

GList *
gimp_text_buffer_get_tags_on_iter (GimpTextBuffer    *buffer,
                                   const GtkTextIter *iter)
{
  GList  *result    = NULL;
  GSList *tag       = NULL;
  GSList *tags_list = NULL;

  g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL);
  g_return_val_if_fail (iter != NULL, NULL);
  g_return_val_if_fail (gtk_text_iter_get_buffer (iter) == GTK_TEXT_BUFFER (buffer), NULL);

  tags_list = gtk_text_iter_get_tags (iter);
  for (tag = tags_list; tag != NULL; tag = g_slist_next (tag))
    {
      result = g_list_prepend (result, tag->data);
    }
  g_slist_free (tags_list);

  result = g_list_reverse (result);
  return result;
}

/**
 * gimp_text_buffer_get_all_tags:
 * @buffer: a #GimpTextBuffer
 *
 * Returns a list of all tags for a @buffer, The GtkTextTag
 * in the list don’t have a reference added, but you have to
 * free the list itself.
 *
 * Returns: (element-type GtkTextTag) (transfer container): GList of #GtkTextTag
 */

GList *
gimp_text_buffer_get_all_tags (GimpTextBuffer *buffer)
{
  GList *result = NULL;

  g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL);

  result = g_list_prepend (result, buffer->bold_tag);
  result = g_list_prepend (result, buffer->italic_tag);
  result = g_list_prepend (result, buffer->underline_tag);
  result = g_list_prepend (result, buffer->strikethrough_tag);

  result = g_list_concat (result, g_list_copy (buffer->size_tags));
  result = g_list_concat (result, g_list_copy (buffer->baseline_tags));
  result = g_list_concat (result, g_list_copy (buffer->kerning_tags));
  result = g_list_concat (result, g_list_copy (buffer->font_tags));
  result = g_list_concat (result, g_list_copy (buffer->color_tags));

  return result;
}

Messung
CHZG
81959891

¤ Dauer der Verarbeitung: 0.14 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.