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

/*  Suggestion for bug #59390 on http://bugs.gnome.org,
 *  "load Pango Markup into GtkTextBuffer"
 *
 *  by Tim-Philipp Mueller <t.i.m@zen.co.uk>
 *
 *  Four new functions:
 *  - text_buffer_insert_markup()
 *  - text_buffer_insert_markup_with_tag()
 *  - text_buffer_set_markup()
 *  - text_buffer_set_markup_with_tag()
 *
 *
 *  Thoughts:
 *   - maybe it would be better to use GMarkupParser here and
 *     parse the markup ourselves. That would also allow us to
 *     expand the markup to include stuff like word wrapping and
 *     text justification, which cannot be set via pango markup.
 *     It would also allow us to insert pixbufs with markup like
 *     <img file='foo.png'>. Or is that overkill?
 *
 */

const gchar *teststrings[] =
	{
		"<i><span size='xx-large'>This</span> is a <b>test</b></i>\n\n",
		"<span foreground='DarkGreen' background='Yellow'><b>fat Green</b> on <u>underlined Yellow</u></span>\n\n",
		"<span size='100000'>Huge Text</span>\n\n",
		"<span size='100000' stretch='ultracondensed' underline='double'>Huge &amp; underlined</span>\n\n",
		NULL
	};


static void
text_buffer_insert_markup_real (GtkTextBuffer *buffer,
                                GtkTextIter   *textiter,
                                const gchar   *markup,
                                gint           len,
                                GtkTextTag    *extratag)
{
	PangoAttrIterator  *paiter;
	PangoAttrList      *attrlist;
	GtkTextMark        *mark;
	GError             *error = NULL;
	gchar              *text;

  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
  g_return_if_fail (textiter != NULL);
  g_return_if_fail (markup != NULL);
  g_return_if_fail (gtk_text_iter_get_buffer (textiter) == buffer);

	if (len == 0)
		return;

	if (!pango_parse_markup(markup, len, 0, &attrlist, &text, NULL, &error))
	{
		g_warning("Invalid markup string: %s", error->message);
		g_error_free(error);
		return;
	}

	len = strlen(text); /* TODO: is this needed? */

	if (attrlist == NULL)
	{
		gtk_text_buffer_insert(buffer, textiter, text, len);
		g_free(text);
		return;
	}

	/* create mark with right gravity */
	mark = gtk_text_buffer_create_mark(buffer, NULL, textiter, FALSE);

	paiter = pango_attr_list_get_iterator(attrlist);

	do
	{
		PangoAttribute *attr;
		GtkTextTag     *tag;
		gint            start, end;

		pango_attr_iterator_range(paiter, &start, &end);

		if (end == G_MAXINT)  /* last chunk */
			end = strlen(text);

		tag = gtk_text_tag_new(NULL);

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_LANGUAGE)))
			g_object_set(tag, "language", pango_language_to_string(((PangoAttrLanguage*)attr)->value), NULL);

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_FAMILY)))
			g_object_set(tag, "family", ((PangoAttrString*)attr)->value, NULL);

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_STYLE)))
			g_object_set(tag, "style", ((PangoAttrInt*)attr)->value, NULL);

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_WEIGHT)))
			g_object_set(tag, "weight", ((PangoAttrInt*)attr)->value, NULL);

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_VARIANT)))
			g_object_set(tag, "variant", ((PangoAttrInt*)attr)->value, NULL);

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_STRETCH)))
			g_object_set(tag, "stretch", ((PangoAttrInt*)attr)->value, NULL);

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_SIZE)))
			g_object_set(tag, "size", ((PangoAttrInt*)attr)->value, NULL);

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_FONT_DESC)))
			g_object_set(tag, "font-desc", ((PangoAttrFontDesc*)attr)->desc, NULL);

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_FOREGROUND)))
		{
			GdkColor col = { 0,
			                 ((PangoAttrColor*)attr)->color.red,
			                 ((PangoAttrColor*)attr)->color.green,
			                 ((PangoAttrColor*)attr)->color.blue
			               };

			g_object_set(tag, "foreground-gdk", &col, NULL);
		}

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_BACKGROUND)))
		{
			GdkColor col = { 0,
			                 ((PangoAttrColor*)attr)->color.red,
			                 ((PangoAttrColor*)attr)->color.green,
			                 ((PangoAttrColor*)attr)->color.blue
			               };

			g_object_set(tag, "background-gdk", &col, NULL);
		}

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_UNDERLINE)))
			g_object_set(tag, "underline", ((PangoAttrInt*)attr)->value, NULL);

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_STRIKETHROUGH)))
			g_object_set(tag, "strikethrough", (gboolean)(((PangoAttrInt*)attr)->value != 0), NULL);

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_RISE)))
			g_object_set(tag, "rise", ((PangoAttrInt*)attr)->value, NULL);

		/* PANGO_ATTR_SHAPE cannot be defined via markup text */

		if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_SCALE)))
			g_object_set(tag, "scale", ((PangoAttrFloat*)attr)->value, NULL);

		gtk_text_tag_table_add(gtk_text_buffer_get_tag_table(buffer), tag);

		if (extratag)
		{
			gtk_text_buffer_insert_with_tags(buffer, textiter, text+start, end - start, tag, extratag, NULL);
		}
		else
		{
			gtk_text_buffer_insert_with_tags(buffer, textiter, text+start, end - start, tag, NULL);
		}

		/* mark had right gravity, so it should be
		 *  at the end of the inserted text now */
		gtk_text_buffer_get_iter_at_mark(buffer, textiter, mark);
	}
	while (pango_attr_iterator_next(paiter));

	gtk_text_buffer_delete_mark(buffer, mark);
	pango_attr_iterator_destroy(paiter);
	pango_attr_list_unref(attrlist);
	g_free(text);
}


/**
 * text_buffer_insert_markup:
 * @buffer: a #GtkTextBuffer
 * @iter: a position in the buffer
 * @markup: UTF-8 format text with pango markup to insert
 * @len: length of text in bytes, or -1
 *
 * Inserts @len bytes of @markup at position @iter.  If @len is -1,
 * @text must be nul-terminated and will be inserted in its
 * entirety. Emits the "insert_text" signal, possibly multiple
 * times; insertion actually occurs in the default handler for
 * the signal. @iter will point to the end of the inserted text
 * on return
 *
 **/

void
text_buffer_insert_markup (GtkTextBuffer *buffer,
                           GtkTextIter   *iter,
                           const gchar   *markup,
                           gint           len)
{
	text_buffer_insert_markup_real (buffer, iter, markup, len, NULL);
}


/**
 * text_buffer_insert_markup_with_tag:
 * @buffer: a #GtkTextBuffer
 * @iter: a position in the buffer
 * @markup: UTF-8 format text in pango markup format to insert
 * @len: length of text in bytes, or -1
 * @tag: additional text tag to apply to the whole text
 *
 * Just like <literal>text_buffer_insert_markup</literal>, only
 * that an additional tag can be specified that is applied to the
 * whole text to be inserted. This is useful to pass formatting
 * options to the text buffer that cannot be specified with
 * pango markup (e.g. text justification or wrap mode).
 *
 **/

void
text_buffer_insert_markup_with_tag (GtkTextBuffer *buffer,
                                    GtkTextIter   *iter,
                                    const gchar   *markup,
                                    gint           len,
                                    GtkTextTag    *tag)
{
	text_buffer_insert_markup_real (buffer, iter, markup, len, tag);
}


/**
 * text_buffer_set_markup:
 * @buffer: a #GtkTextBuffer
 * @markup: UTF-8 text with pango markup to insert
 * @len: length of text in bytes
 *
 * Deletes current contents of @buffer, and inserts the text
 * in @markup instead, which may contain pango markup. If
 * @len is -1, @markup must be nul-terminated. @markup must be valid UTF-8.
 **/

void
text_buffer_set_markup_with_tag (GtkTextBuffer *buffer,
                                 const gchar   *markup,
                                 gint           len,
                                 GtkTextTag    *tag)
{
	GtkTextIter start, end;

	g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
	g_return_if_fail (markup != NULL);

	if (len < 0)
		len = strlen (markup);

	gtk_text_buffer_get_bounds (buffer, &start, &end);

	gtk_text_buffer_delete (buffer, &start, &end);

	if (len > 0)
	{
		gtk_text_buffer_get_iter_at_offset (buffer, &start, 0);
		text_buffer_insert_markup_with_tag (buffer, &start, markup, len, tag);
	}
}

/**
 * text_buffer_set_markup:
 * @buffer: a #GtkTextBuffer
 * @markup: UTF-8 text with pango markup to insert
 * @len: length of text in bytes
 *
 * Deletes current contents of @buffer, and inserts the text
 * in @markup instead, which may contain pango markup. If
 * @len is -1, @markup must be nul-terminated. @markup must be valid UTF-8.
 **/

void
text_buffer_set_markup (GtkTextBuffer *buffer,
                        const gchar   *markup,
                        gint           len)
{
	text_buffer_set_markup_with_tag(buffer, markup, len, NULL);
}

gint
main (gint argc, gchar **argv)
{
	GtkTextBuffer *buf;
	GtkTextIter    end;
	GtkTextTag    *tag;
	GtkWidget     *win, *swin, *view;
	guint          i;

	gtk_init(&argc, &argv);

	win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_default_size(GTK_WINDOW(win), 640,480);
	gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER);

	swin = gtk_scrolled_window_new(NULL, NULL);

	view = gtk_text_view_new();

	buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));

	text_buffer_set_markup (buf, "<b><i>Boring bold and underline text</i></b>\n\n", -1);

	gtk_text_buffer_get_end_iter(buf, &end);

	for (i=0; teststrings[i] != NULL; ++i)
	{
		text_buffer_insert_markup(buf, &end, teststrings[i], -1);
	}

	tag = gtk_text_buffer_create_tag(buf, NULL, "wrap-mode", GTK_WRAP_WORD, "justification", GTK_JUSTIFY_RIGHT, NULL);

	text_buffer_insert_markup_with_tag (buf, &end, "<span size='xx-large'>This text should be "
		"wrapped and should also be justified to the right. Let&apos;s see how that looks on the "
		"screen. And indeed, this seems to work just like it should.</span>\n\n", -1, tag);

	gtk_container_add(GTK_CONTAINER(swin), view);

	gtk_container_add(GTK_CONTAINER(win), swin);

	gtk_widget_show_all(win);

	g_signal_connect(win, "delete-event", (GCallback) gtk_main_quit, NULL);

	gtk_main();

	return 0;
}

