/* gtkrichtext.c
 *
 * Copyright (C) 2006 Imendio AB
 * Contact: Michael Natterer <mitch@imendio.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <string.h>

#include "gtktextbufferrichtext.h"
#include "gtktextbufferserialize.h"
#include "gtkalias.h"
#include "gtkintl.h"


typedef struct
{
  gchar          *mime_type;
  gboolean        can_create_tags;
  GdkAtom         atom;
  gpointer        function;
  gpointer        user_data;
  GDestroyNotify  user_data_destroy;
} GtkRichTextFormat;


static GList   * register_format   (GList             *formats,
                                    const gchar       *mime_type,
                                    gpointer           function,
                                    gpointer           user_data,
                                    GDestroyNotify     user_data_destroy,
                                    GdkAtom           *atom);
static GList   * unregister_format (GList             *formats,
                                    GdkAtom            atom);
static GdkAtom * get_formats       (GList             *formats,
                                    gint              *n_formats);
static void      free_format       (GtkRichTextFormat *format);
static void      free_format_list  (GList             *formats);
static GQuark    serialize_quark   (void);
static GQuark    deserialize_quark (void);


/**
 * gtk_text_buffer_register_serialize_format:
 * @buffer: a #GtkTextBuffer
 * @mime_type: the format's mime-type
 * @function: the serialize function to register
 * @user_data: %function's user_data
 * @user_data_destroy: a function to call when user_data is no longer needed
 *
 * This function registers a rich text serialization %function along with
 * its %mime_type with the passed %buffer.
 *
 * Return value: the #GdkAtom that corresponds to the newly registered
 *               format's mime-type.
 **/
GdkAtom
gtk_text_buffer_register_serialize_format (GtkTextBuffer              *buffer,
                                           const gchar                *mime_type,
                                           GtkTextBufferSerializeFunc  function,
                                           gpointer                    user_data,
                                           GDestroyNotify              user_data_destroy)
{
  GList   *formats;
  GdkAtom  atom;

  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), GDK_NONE);
  g_return_val_if_fail (mime_type != NULL && *mime_type != '\0', GDK_NONE);
  g_return_val_if_fail (function != NULL, GDK_NONE);

  formats = g_object_steal_qdata (G_OBJECT (buffer), serialize_quark ());

  formats = register_format (formats, mime_type,
                             (gpointer) function,
                             user_data, user_data_destroy,
                             &atom);

  g_object_set_qdata_full (G_OBJECT (buffer), serialize_quark (),
                           formats, (GDestroyNotify) free_format_list);

  g_object_notify (G_OBJECT (buffer), "copy-target-list");

  return atom;
}

/**
 * gtk_text_buffer_register_serialize_tagset:
 * @buffer: a #GtkTextBuffer
 * @tagset_name: an optional tagset name, on %NULL
 *
 * This function registers GTK+'s internal rich text serialization
 * format with the passed %buffer. The internal format does not comply
 * to any standard rich text format and only works between #GtkTextBuffer
 * instances. It is capable of serializing all of a text buffer's tags
 * and embedded pixbufs.
 *
 * This function is just a wrapper around
 * gtk_text_buffer_register_serialize_format(). The %mime_type used
 * for registering is "application/x-gtk-text-buffer-rich-text", or
 * "application/x-gtk-text-buffer-rich-text;format=%tagset_name" if a
 * %tagset_name was passed.
 *
 * The %tagset_name can be used to restrict the transfer of rich text
 * to buffers with compatible sets of tags, in order to avoid unknown
 * tags from being pasted. It is probably the common case to pass an
 * identifier != %NULL here, since the %NULL tagset requires the
 * receiving buffer to deal with with pasting of arbitrary tags.
 *
 * Return value: the #GdkAtom that corresponds to the newly registered
 *               format's mime-type.
 **/
GdkAtom
gtk_text_buffer_register_serialize_tagset (GtkTextBuffer *buffer,
                                           const gchar   *tagset_name)
{
  gchar   *mime_type = "application/x-gtk-text-buffer-rich-text";
  GdkAtom  format;

  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), GDK_NONE);
  g_return_val_if_fail (tagset_name == NULL || *tagset_name != '\0', GDK_NONE);

  if (tagset_name)
    mime_type =
      g_strdup_printf ("application/x-gtk-text-buffer-rich-text;format=%s",
                       tagset_name);

  format = gtk_text_buffer_register_serialize_format (buffer, mime_type,
                                                      _gtk_text_buffer_serialize_rich_text,
                                                      NULL, NULL);

  if (tagset_name)
    g_free (mime_type);

  return format;
}

GdkAtom
gtk_text_buffer_register_deserialize_format (GtkTextBuffer                *buffer,
                                             const gchar                  *mime_type,
                                             GtkTextBufferDeserializeFunc  function,
                                             gpointer                      user_data,
                                             GDestroyNotify                user_data_destroy)
{
  GList   *formats;
  GdkAtom  atom;

  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), GDK_NONE);
  g_return_val_if_fail (mime_type != NULL && *mime_type != '\0', GDK_NONE);
  g_return_val_if_fail (function != NULL, GDK_NONE);

  formats = g_object_steal_qdata (G_OBJECT (buffer), deserialize_quark ());

  formats = register_format (formats, mime_type,
                             (gpointer) function,
                             user_data, user_data_destroy,
                             &atom);

  g_object_set_qdata_full (G_OBJECT (buffer), deserialize_quark (),
                           formats, (GDestroyNotify) free_format_list);

  g_object_notify (G_OBJECT (buffer), "paste-target-list");

  return atom;
}

GdkAtom
gtk_text_buffer_register_deserialize_tagset (GtkTextBuffer *buffer,
                                             const gchar   *tagset_name)
{
  gchar   *mime_type = "application/x-gtk-text-buffer-rich-text";
  GdkAtom  format;

  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), GDK_NONE);
  g_return_val_if_fail (tagset_name == NULL || *tagset_name != '\0', GDK_NONE);

  if (tagset_name)
    mime_type =
      g_strdup_printf ("application/x-gtk-text-buffer-rich-text;format=%s",
                       tagset_name);

  format = gtk_text_buffer_register_deserialize_format (buffer, mime_type,
                                                        _gtk_text_buffer_deserialize_rich_text,
                                                        NULL, NULL);

  if (tagset_name)
    g_free (mime_type);

  return format;
}

void
gtk_text_buffer_unregister_serialize_format (GtkTextBuffer *buffer,
                                             GdkAtom        format)
{
  GList *formats;

  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
  g_return_if_fail (format != GDK_NONE);

  formats = g_object_steal_qdata (G_OBJECT (buffer), serialize_quark ());

  formats = unregister_format (formats, format);

  g_object_set_qdata_full (G_OBJECT (buffer), serialize_quark (),
                           formats, (GDestroyNotify) free_format_list);

  g_object_notify (G_OBJECT (buffer), "copy-target-list");
}

void
gtk_text_buffer_unregister_deserialize_format (GtkTextBuffer *buffer,
                                               GdkAtom        format)
{
  GList *formats;

  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
  g_return_if_fail (format != GDK_NONE);

  formats = g_object_steal_qdata (G_OBJECT (buffer), deserialize_quark ());

  formats = unregister_format (formats, format);

  g_object_set_qdata_full (G_OBJECT (buffer), deserialize_quark (),
                           formats, (GDestroyNotify) free_format_list);

  g_object_notify (G_OBJECT (buffer), "paste-target-list");
}

void
gtk_text_buffer_deserialize_set_can_create_tags (GtkTextBuffer *buffer,
                                                 GdkAtom        format,
                                                 gboolean       can_create_tags)
{
  GList *formats;
  GList *list;
  gchar *format_name;

  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
  g_return_if_fail (format != GDK_NONE);

  formats = g_object_get_qdata (G_OBJECT (buffer), deserialize_quark ());

  for (list = formats; list; list = g_list_next (list))
    {
      GtkRichTextFormat *fmt = list->data;

      if (fmt->atom == format)
        {
          fmt->can_create_tags = can_create_tags ? TRUE : FALSE;
          return;
        }
    }

  format_name = gdk_atom_name (format);
  g_warning ("%s: \"%s\" is not registered as deserializable format "
             "with text buffer %p",
             G_STRFUNC, format_name ? format_name : "not a GdkAtom", buffer);
  g_free (format_name);
}

gboolean
gtk_text_buffer_deserialize_get_can_create_tags (GtkTextBuffer *buffer,
                                                 GdkAtom        format)
{
  GList *formats;
  GList *list;
  gchar *format_name;

  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
  g_return_val_if_fail (format != GDK_NONE, FALSE);

  formats = g_object_get_qdata (G_OBJECT (buffer), deserialize_quark ());

  for (list = formats; list; list = g_list_next (list))
    {
      GtkRichTextFormat *fmt = list->data;

      if (fmt->atom == format)
        {
          return fmt->can_create_tags;
        }
    }

  format_name = gdk_atom_name (format);
  g_warning ("%s: \"%s\" is not registered as deserializable format "
             "with text buffer %p",
             G_STRFUNC, format_name ? format_name : "not a GdkAtom", buffer);
  g_free (format_name);

  return FALSE;
}

GdkAtom *
gtk_text_buffer_get_serialize_formats (GtkTextBuffer *buffer,
                                       gint          *n_formats)
{
  GList *formats;

  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
  g_return_val_if_fail (n_formats != NULL, NULL);

  formats = g_object_get_qdata (G_OBJECT (buffer), serialize_quark ());

  return get_formats (formats, n_formats);
}

GdkAtom *
gtk_text_buffer_get_deserialize_formats (GtkTextBuffer *buffer,
                                         gint          *n_formats)
{
  GList *formats;

  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
  g_return_val_if_fail (n_formats != NULL, NULL);

  formats = g_object_get_qdata (G_OBJECT (buffer), deserialize_quark ());

  return get_formats (formats, n_formats);
}

guint8 *
gtk_text_buffer_serialize (GtkTextBuffer     *register_buffer,
                           GtkTextBuffer     *content_buffer,
                           GdkAtom            format,
                           const GtkTextIter *start,
                           const GtkTextIter *end,
                           gsize             *length)
{
  GList *formats;
  GList *list;

  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (register_buffer), NULL);
  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (content_buffer), NULL);
  g_return_val_if_fail (format != GDK_NONE, NULL);
  g_return_val_if_fail (start != NULL, NULL);
  g_return_val_if_fail (end != NULL, NULL);
  g_return_val_if_fail (length != NULL, NULL);

  *length = 0;

  formats = g_object_get_qdata (G_OBJECT (register_buffer),
                                serialize_quark ());

  for (list = formats; list; list = g_list_next (list))
    {
      GtkRichTextFormat *fmt = list->data;

      if (fmt->atom == format)
        {
          GtkTextBufferSerializeFunc function = fmt->function;

          return function (register_buffer, content_buffer,
                           start, end, length, fmt->user_data);
        }
    }

  return NULL;
}

gboolean
gtk_text_buffer_deserialize (GtkTextBuffer  *register_buffer,
                             GtkTextBuffer  *content_buffer,
                             GdkAtom         format,
                             GtkTextIter    *iter,
                             const guint8   *data,
                             gsize           length,
                             GError        **error)
{
  GList    *formats;
  GList    *list;

  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (register_buffer), FALSE);
  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (content_buffer), FALSE);
  g_return_val_if_fail (format != GDK_NONE, FALSE);
  g_return_val_if_fail (iter != NULL, FALSE);
  g_return_val_if_fail (data != NULL, FALSE);
  g_return_val_if_fail (length > 0, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  formats = g_object_get_qdata (G_OBJECT (register_buffer),
                                deserialize_quark ());

  for (list = formats; list; list = g_list_next (list))
    {
      GtkRichTextFormat *fmt = list->data;

      if (fmt->atom == format)
        {
          GtkTextBufferDeserializeFunc function = fmt->function;
          gboolean                     success;

          success = function (register_buffer, content_buffer,
                              iter, data, length,
                              fmt->can_create_tags,
                              fmt->user_data,
                              error);

          if (!success && error != NULL && *error == NULL)
            g_set_error (error, 0, 0,
                         _("Unknown error when trying to deserialize %s"),
                         gdk_atom_name (format));

          return success;
        }
    }

  g_set_error (error, 0, 0,
               _("No deserialize function found for format %s"),
               gdk_atom_name (format));

  return FALSE;
}


/*  private functions  */

static GList *
register_format (GList          *formats,
                 const gchar    *mime_type,
                 gpointer        function,
                 gpointer        user_data,
                 GDestroyNotify  user_data_destroy,
                 GdkAtom        *atom)
{
  GtkRichTextFormat *format;

  *atom = gdk_atom_intern (mime_type, FALSE);

  formats = unregister_format (formats, *atom);

  format = g_new0 (GtkRichTextFormat, 1);

  format->mime_type         = g_strdup (mime_type);
  format->can_create_tags   = FALSE;
  format->atom              = *atom;
  format->function          = function;
  format->user_data         = user_data;
  format->user_data_destroy = user_data_destroy;

  return g_list_append (formats, format);
}

static GList *
unregister_format (GList   *formats,
                   GdkAtom  atom)
{
  GList *list;

  for (list = formats; list; list = g_list_next (list))
    {
      GtkRichTextFormat *format = list->data;

      if (format->atom == atom)
        {
          free_format (format);

          return g_list_delete_link (formats, list);
        }
    }

  return formats;
}

static GdkAtom *
get_formats (GList *formats,
             gint  *n_formats)
{
  GdkAtom *array;
  GList   *list;
  gint     i;

  *n_formats = g_list_length (formats);
  array = g_new0 (GdkAtom, *n_formats);

  for (list = formats, i = 0; list; list = g_list_next (list), i++)
    {
      GtkRichTextFormat *format = list->data;

      array[i] = format->atom;
    }

  return array;
}

static void
free_format (GtkRichTextFormat *format)
{
  if (format->user_data_destroy)
    format->user_data_destroy (format->user_data);

  g_free (format->mime_type);
  g_free (format);
}

static void
free_format_list (GList *formats)
{
  g_list_foreach (formats, (GFunc) free_format, NULL);
  g_list_free (formats);
}

static GQuark
serialize_quark (void)
{
  static GQuark quark = 0;

  if (! quark)
    quark = g_quark_from_static_string ("gtk-text-buffer-serialize-formats");

  return quark;
}

static GQuark
deserialize_quark (void)
{
  static GQuark quark = 0;

  if (! quark)
    quark = g_quark_from_static_string ("gtk-text-buffer-deserialize-formats");

  return quark;
}

#define __GTK_TEXT_BUFFER_RICH_TEXT_C__
#include "gtkaliasdef.c"
