/*
 * This file is part of gspell, a spell-checking library.
 *
 * Copyright 2015, 2016, 2017 - Sébastien Wilmet
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gspell-text-buffer.h"

/**
 * SECTION:text-buffer
 * @Title: GspellTextBuffer
 * @Short_description: Spell checking support for GtkTextBuffer
 *
 * #GspellTextBuffer extends the #GtkTextBuffer class but without subclassing
 * it, because the GtkSourceView library has already a #GtkTextBuffer subclass.
 *
 * # Support of the no-spell-check tag defined by GtkSourceView
 *
 * The syntax highlighting engine of the [GtkSourceView
 * library](https://wiki.gnome.org/Projects/GtkSourceView) has a feature called
 * “context classes”. One of the standard context classes is
 * “<emphasis>no-spell-check</emphasis>”: it defines the regions in the
 * #GtkTextBuffer that should not be spell-checked.
 *
 * GtkSourceView creates a #GtkTextTag named
 * `"gtksourceview:context-classes:no-spell-check"`. gspell reads this tag, to
 * skip the text contained within the tag.
 *
 * If you use the GtkSourceView library in your application, keep in mind that
 * the #GtkTextTag created by GtkSourceView is for read-only purposes; you
 * cannot apply it yourself to other regions.
 *
 * On the other hand if the GtkSourceView library is not used, you can create a
 * #GtkTextTag with the same name to mark certain regions in the text that
 * gspell should skip. As it is not a great API, it is
 * [planned](https://bugzilla.gnome.org/show_bug.cgi?id=771582) to add an
 * explicit API in #GspellTextBuffer to set a #GtkTextTag that gspell should
 * skip.
 *
 * See the class description of #GtkSourceBuffer for more information about
 * context classes.
 */

struct _GspellTextBuffer
{
	GObject parent;

	GtkTextBuffer *buffer;
	GspellChecker *spell_checker;
};

enum
{
	PROP_0,
	PROP_BUFFER,
	PROP_SPELL_CHECKER,
};

#define GSPELL_TEXT_BUFFER_KEY "gspell-text-buffer-key"

G_DEFINE_TYPE (GspellTextBuffer, gspell_text_buffer, G_TYPE_OBJECT)

static void
gspell_text_buffer_get_property (GObject    *object,
				 guint       prop_id,
				 GValue     *value,
				 GParamSpec *pspec)
{
	GspellTextBuffer *gspell_buffer = GSPELL_TEXT_BUFFER (object);

	switch (prop_id)
	{
		case PROP_BUFFER:
			g_value_set_object (value, gspell_text_buffer_get_buffer (gspell_buffer));
			break;

		case PROP_SPELL_CHECKER:
			g_value_set_object (value, gspell_text_buffer_get_spell_checker (gspell_buffer));
			break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
			break;
	}
}

static void
gspell_text_buffer_set_property (GObject      *object,
				 guint         prop_id,
				 const GValue *value,
				 GParamSpec   *pspec)
{
	GspellTextBuffer *gspell_buffer = GSPELL_TEXT_BUFFER (object);

	switch (prop_id)
	{
		case PROP_BUFFER:
			g_assert (gspell_buffer->buffer == NULL);
			gspell_buffer->buffer = g_value_get_object (value);
			break;

		case PROP_SPELL_CHECKER:
			gspell_text_buffer_set_spell_checker (gspell_buffer, g_value_get_object (value));
			break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
			break;
	}
}

static void
gspell_text_buffer_dispose (GObject *object)
{
	GspellTextBuffer *gspell_buffer = GSPELL_TEXT_BUFFER (object);

	gspell_buffer->buffer = NULL;
	g_clear_object (&gspell_buffer->spell_checker);

	G_OBJECT_CLASS (gspell_text_buffer_parent_class)->dispose (object);
}

static void
gspell_text_buffer_class_init (GspellTextBufferClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	object_class->get_property = gspell_text_buffer_get_property;
	object_class->set_property = gspell_text_buffer_set_property;
	object_class->dispose = gspell_text_buffer_dispose;

	/**
	 * GspellTextBuffer:buffer:
	 *
	 * The #GtkTextBuffer.
	 */
	g_object_class_install_property (object_class,
					 PROP_BUFFER,
					 g_param_spec_object ("buffer",
							      "Buffer",
							      "",
							      GTK_TYPE_TEXT_BUFFER,
							      G_PARAM_READWRITE |
							      G_PARAM_CONSTRUCT_ONLY |
							      G_PARAM_STATIC_STRINGS));

	/**
	 * GspellTextBuffer:spell-checker:
	 *
	 * The #GspellChecker.
	 */
	g_object_class_install_property (object_class,
					 PROP_SPELL_CHECKER,
					 g_param_spec_object ("spell-checker",
							      "Spell Checker",
							      "",
							      GSPELL_TYPE_CHECKER,
							      G_PARAM_READWRITE |
							      G_PARAM_STATIC_STRINGS));
}

static void
gspell_text_buffer_init (GspellTextBuffer *gspell_buffer)
{
}

/**
 * gspell_text_buffer_get_from_gtk_text_buffer:
 * @gtk_buffer: a #GtkTextBuffer.
 *
 * Returns the #GspellTextBuffer of @gtk_buffer. The returned object is
 * guaranteed to be the same for the lifetime of @gtk_buffer.
 *
 * Returns: (transfer none): the #GspellTextBuffer of @gtk_buffer.
 */
/* Yes I know, the function name is a bit long. But at least there is no
 * possible confusions. Other names that came to my mind:
 * - get_from_buffer(), but it's confusing: which buffer is it?
 * - get_from_sibling(): less clear.
 */
GspellTextBuffer *
gspell_text_buffer_get_from_gtk_text_buffer (GtkTextBuffer *gtk_buffer)
{
	GspellTextBuffer *gspell_buffer;

	g_return_val_if_fail (GTK_IS_TEXT_BUFFER (gtk_buffer), NULL);

	gspell_buffer = g_object_get_data (G_OBJECT (gtk_buffer), GSPELL_TEXT_BUFFER_KEY);

	if (gspell_buffer == NULL)
	{
		gspell_buffer = g_object_new (GSPELL_TYPE_TEXT_BUFFER,
					      "buffer", gtk_buffer,
					      NULL);

		g_object_set_data_full (G_OBJECT (gtk_buffer),
					GSPELL_TEXT_BUFFER_KEY,
					gspell_buffer,
					g_object_unref);
	}

	g_return_val_if_fail (GSPELL_IS_TEXT_BUFFER (gspell_buffer), NULL);
	return gspell_buffer;
}

/**
 * gspell_text_buffer_get_buffer:
 * @gspell_buffer: a #GspellTextBuffer.
 *
 * Returns: (transfer none): the #GtkTextBuffer of @gspell_buffer.
 */
GtkTextBuffer *
gspell_text_buffer_get_buffer (GspellTextBuffer *gspell_buffer)
{
	g_return_val_if_fail (GSPELL_IS_TEXT_BUFFER (gspell_buffer), NULL);

	return gspell_buffer->buffer;
}

/**
 * gspell_text_buffer_get_spell_checker:
 * @gspell_buffer: a #GspellTextBuffer.
 *
 * Returns: (nullable) (transfer none): the #GspellChecker if one has been set,
 *   or %NULL.
 */
GspellChecker *
gspell_text_buffer_get_spell_checker (GspellTextBuffer *gspell_buffer)
{
	g_return_val_if_fail (GSPELL_IS_TEXT_BUFFER (gspell_buffer), NULL);

	return gspell_buffer->spell_checker;
}

/**
 * gspell_text_buffer_set_spell_checker:
 * @gspell_buffer: a #GspellTextBuffer.
 * @spell_checker: (nullable): a #GspellChecker, or %NULL to unset the spell
 *   checker.
 *
 * Sets a #GspellChecker to a #GspellTextBuffer. The @gspell_buffer will own a
 * reference to @spell_checker, so you can release your reference to
 * @spell_checker if you no longer need it.
 */
void
gspell_text_buffer_set_spell_checker (GspellTextBuffer *gspell_buffer,
				      GspellChecker    *spell_checker)
{
	g_return_if_fail (GSPELL_IS_TEXT_BUFFER (gspell_buffer));
	g_return_if_fail (spell_checker == NULL || GSPELL_IS_CHECKER (spell_checker));

	if (g_set_object (&gspell_buffer->spell_checker, spell_checker))
	{
		g_object_notify (G_OBJECT (gspell_buffer), "spell-checker");
	}
}

/* ex:set ts=8 noet: */
