summaryrefslogtreecommitdiffstats
path: root/input-methods/sulekha/src/sulekhaspell.c~
diff options
context:
space:
mode:
Diffstat (limited to 'input-methods/sulekha/src/sulekhaspell.c~')
-rw-r--r--input-methods/sulekha/src/sulekhaspell.c~842
1 files changed, 842 insertions, 0 deletions
diff --git a/input-methods/sulekha/src/sulekhaspell.c~ b/input-methods/sulekha/src/sulekhaspell.c~
new file mode 100644
index 0000000..143d989
--- /dev/null
+++ b/input-methods/sulekha/src/sulekhaspell.c~
@@ -0,0 +1,842 @@
+/* sulekhaspell - a transliteratio - spell-checking addon for GTK's TextView widget
+ * Copyright (c) 2007-2008 Santhosh Thottingal
+ * Based on gtkspell by Evan Martin.
+ */
+
+
+#include <gtk/gtk.h>
+#include <libintl.h>
+#include <locale.h>
+#include "../config.h"
+#include "sulekhaspell.h"
+#include "transliteration.h"
+
+#define _(String) dgettext (PACKAGE, String)
+
+#define SULEKHASPELL_MISSPELLED_TAG "sulekhaspell-misspelled"
+
+#ifdef HAVE_ASPELL_H
+#define USING_ASPELL
+#include <aspell.h>
+#elif defined HAVE_PSPELL_H
+#define USING_PSPELL
+#include <pspell/pspell.h>
+#define AspellSpeller PspellManager
+#define speller manager
+#define aspell_speller_check pspell_manager_check
+#define aspell_speller_add_to_session pspell_manager_add_to_session
+#define aspell_speller_add_to_personal pspell_manager_add_to_personal
+#define aspell_speller_save_all_word_lists pspell_manager_save_all_word_lists
+#define aspell_speller_store_replacement pspell_manager_store_replacement
+#define AspellWordList PspellWordList
+#define AspellStringEnumeration PspellStringEmulation
+#define aspell_speller_suggest pspell_manager_suggest
+#define aspell_word_list_elements pspell_word_list_elements
+#define aspell_string_enumeration_next pspell_string_emulation_next
+#define delete_aspell_string_enumeration delete_pspell_string_emulation
+#define AspellConfig PspellConfig
+#define AspellCanHaveError PspellCanHaveError
+#define new_aspell_config new_pspell_config
+#define aspell_config_replace pspell_config_replace
+#define new_aspell_speller new_pspell_manager
+#define delete_aspell_config delete_pspell_config
+#define aspell_error_message pspell_error_message
+#define delete_aspell_speller delete_pspell_manager
+#define to_aspell_speller to_pspell_manager
+#define aspell_error_number pspell_error_number
+#define aspell pspell
+#endif
+
+const int debug = 0;
+const int quiet = 0;
+
+struct _SulekhaSpell
+{
+ GtkTextView *view;
+ GtkTextTag *tag_highlight;
+ GtkTextMark *mark_insert_start;
+ GtkTextMark *mark_insert_end;
+ gboolean deferred_check;
+ AspellSpeller *speller;
+ GtkTextMark *mark_click;
+};
+
+static void sulekhaspell_free (SulekhaSpell * spell);
+
+#define SULEKHASPELL_OBJECT_KEY "sulekhaspell"
+
+GQuark
+sulekhaspell_error_quark (void)
+{
+ static GQuark q = 0;
+ if (q == 0)
+ q = g_quark_from_static_string ("sulekhaspell-error-quark");
+ return q;
+}
+
+static gboolean
+sulekhaspell_text_iter_forward_word_end (GtkTextIter * i)
+{
+ GtkTextIter iter;
+
+/* heuristic:
+ * if we're on an singlequote/apostrophe and
+ * if the next letter is alphanumeric,
+ * this is an apostrophe. */
+
+ if (!gtk_text_iter_forward_word_end (i))
+ return FALSE;
+
+ if (gtk_text_iter_get_char (i) != '\'')
+ return TRUE;
+
+ iter = *i;
+ if (gtk_text_iter_forward_char (&iter))
+ {
+ if (g_unichar_isalpha (gtk_text_iter_get_char (&iter)))
+ {
+ return (gtk_text_iter_forward_word_end (i));
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+sulekhaspell_text_iter_backward_word_start (GtkTextIter * i)
+{
+ GtkTextIter iter;
+
+ if (!gtk_text_iter_backward_word_start (i))
+ return FALSE;
+
+ iter = *i;
+ if (gtk_text_iter_backward_char (&iter))
+ {
+ if (gtk_text_iter_get_char (&iter) == '\'')
+ {
+ if (gtk_text_iter_backward_char (&iter))
+ {
+ if (g_unichar_isalpha (gtk_text_iter_get_char (&iter)))
+ {
+ return (gtk_text_iter_backward_word_start (i));
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+#define gtk_text_iter_backward_word_start sulekhaspell_text_iter_backward_word_start
+#define gtk_text_iter_forward_word_end sulekhaspell_text_iter_forward_word_end
+
+static void
+check_word (SulekhaSpell * spell, GtkTextBuffer * buffer,
+ GtkTextIter * start, GtkTextIter * end)
+{
+ char *text;
+ char *oldword;
+ text = gtk_text_buffer_get_text (buffer, start, end, FALSE);
+ if (debug)
+ g_print ("checking: %s\n", text);
+ g_print ("[santhosh]checking: %s\n", text);
+ text = transliterate_ml (text, 0, strlen (text));
+ g_print ("[santhosh]After transliteration checking: %s\n", text);
+ if (g_unichar_isdigit (*text) == FALSE) /* don't check numbers */
+ if (aspell_speller_check (spell->speller, text, -1) == FALSE)
+ gtk_text_buffer_apply_tag (buffer, spell->tag_highlight, start, end);
+ g_free (text);
+}
+
+static void
+print_iter (char *name, GtkTextIter * iter)
+{
+ g_print ("%1s[%d%c%c%c] ", name, gtk_text_iter_get_offset (iter),
+ gtk_text_iter_starts_word (iter) ? 's' : ' ',
+ gtk_text_iter_inside_word (iter) ? 'i' : ' ',
+ gtk_text_iter_ends_word (iter) ? 'e' : ' ');
+}
+
+static void
+check_range (SulekhaSpell * spell, GtkTextBuffer * buffer,
+ GtkTextIter start, GtkTextIter end, gboolean force_all)
+{
+ /* we need to "split" on word boundaries.
+ * luckily, pango knows what "words" are
+ * so we don't have to figure it out. */
+
+ GtkTextIter wstart, wend, cursor, precursor;
+ gboolean inword, highlight;
+ if (debug)
+ {
+ g_print ("check_range: ");
+ print_iter ("s", &start);
+ print_iter ("e", &end);
+ g_print (" -> ");
+ }
+
+
+
+ if (gtk_text_iter_inside_word (&end))
+ gtk_text_iter_forward_word_end (&end);
+ if (!gtk_text_iter_starts_word (&start))
+ {
+ if (gtk_text_iter_inside_word (&start) ||
+ gtk_text_iter_ends_word (&start))
+ {
+ gtk_text_iter_backward_word_start (&start);
+ }
+ else
+ {
+ /* if we're neither at the beginning nor inside a word,
+ * me must be in some spaces.
+ * skip forward to the beginning of the next word. */
+ //gtk_text_buffer_remove_tag(buffer, tag_highlight, &start, &end);
+ if (gtk_text_iter_forward_word_end (&start))
+ gtk_text_iter_backward_word_start (&start);
+ }
+ }
+ gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
+ gtk_text_buffer_get_insert (buffer));
+
+ precursor = cursor;
+ gtk_text_iter_backward_char (&precursor);
+ highlight = gtk_text_iter_has_tag (&cursor, spell->tag_highlight) ||
+ gtk_text_iter_has_tag (&precursor, spell->tag_highlight);
+
+ gtk_text_buffer_remove_tag (buffer, spell->tag_highlight, &start, &end);
+
+ /* Fix a corner case when replacement occurs at beginning of buffer:
+ * An iter at offset 0 seems to always be inside a word,
+ * even if it's not. Possibly a pango bug.
+ */
+ if (gtk_text_iter_get_offset (&start) == 0)
+ {
+ gtk_text_iter_forward_word_end (&start);
+ gtk_text_iter_backward_word_start (&start);
+ }
+
+ if (debug)
+ {
+ print_iter ("s", &start);
+ print_iter ("e", &end);
+ g_print ("\n");
+ }
+
+ wstart = start;
+ while (gtk_text_iter_compare (&wstart, &end) < 0)
+ {
+ /* move wend to the end of the current word. */
+ wend = wstart;
+ gtk_text_iter_forward_word_end (&wend);
+
+ inword = (gtk_text_iter_compare (&wstart, &cursor) < 0) &&
+ (gtk_text_iter_compare (&cursor, &wend) <= 0);
+
+ if (inword && !force_all)
+ {
+ /* this word is being actively edited,
+ * only check if it's already highligted,
+ * otherwise defer this check until later. */
+ if (highlight)
+ check_word (spell, buffer, &wstart, &wend);
+ else
+ spell->deferred_check = TRUE;
+ }
+ else
+ {
+ check_word (spell, buffer, &wstart, &wend);
+ spell->deferred_check = FALSE;
+ }
+
+ /* now move wend to the beginning of the next word, */
+ gtk_text_iter_forward_word_end (&wend);
+ gtk_text_iter_backward_word_start (&wend);
+ /* make sure we've actually advanced
+ * (we don't advance in some corner cases), */
+ if (gtk_text_iter_equal (&wstart, &wend))
+ break; /* we're done in these cases.. */
+ /* and then pick this as the new next word beginning. */
+ wstart = wend;
+ }
+}
+
+static void
+check_deferred_range (SulekhaSpell * spell, GtkTextBuffer * buffer,
+ gboolean force_all)
+{
+ GtkTextIter start, end;
+ gtk_text_buffer_get_iter_at_mark (buffer, &start, spell->mark_insert_start);
+ gtk_text_buffer_get_iter_at_mark (buffer, &end, spell->mark_insert_end);
+ check_range (spell, buffer, start, end, force_all);
+}
+
+/* insertion works like this:
+ * - before the text is inserted, we mark the position in the buffer.
+ * - after the text is inserted, we see where our mark is and use that and
+ * the current position to check the entire range of inserted text.
+ *
+ * this may be overkill for the common case (inserting one character). */
+
+static void
+insert_text_before (GtkTextBuffer * buffer, GtkTextIter * iter,
+ gchar * text, gint len, SulekhaSpell * spell)
+{
+ gtk_text_buffer_move_mark (buffer, spell->mark_insert_start, iter);
+}
+
+static void
+insert_text_after (GtkTextBuffer * buffer, GtkTextIter * iter,
+ gchar * text, gint len, SulekhaSpell * spell)
+{
+ GtkTextIter start;
+
+ if (debug)
+ g_print ("insert\n");
+
+ /* we need to check a range of text. */
+ gtk_text_buffer_get_iter_at_mark (buffer, &start, spell->mark_insert_start);
+ check_range (spell, buffer, start, *iter, FALSE);
+
+ gtk_text_buffer_move_mark (buffer, spell->mark_insert_end, iter);
+}
+
+/* deleting is more simple: we're given the range of deleted text.
+ * after deletion, the start and end iters should be at the same position
+ * (because all of the text between them was deleted!).
+ * this means we only really check the words immediately bounding the
+ * deletion.
+ */
+
+static void
+delete_range_after (GtkTextBuffer * buffer,
+ GtkTextIter * start, GtkTextIter * end,
+ SulekhaSpell * spell)
+{
+ if (debug)
+ g_print ("delete\n");
+ check_range (spell, buffer, *start, *end, FALSE);
+}
+
+static void
+mark_set (GtkTextBuffer * buffer, GtkTextIter * iter,
+ GtkTextMark * mark, SulekhaSpell * spell)
+{
+ /* if the cursor has moved and there is a deferred check so handle it now */
+ if ((mark == gtk_text_buffer_get_insert (buffer)) && spell->deferred_check)
+ check_deferred_range (spell, buffer, FALSE);
+}
+
+static void
+get_word_extents_from_mark (GtkTextBuffer * buffer,
+ GtkTextIter * start, GtkTextIter * end,
+ GtkTextMark * mark)
+{
+ gtk_text_buffer_get_iter_at_mark (buffer, start, mark);
+ if (!gtk_text_iter_starts_word (start))
+ gtk_text_iter_backward_word_start (start);
+ *end = *start;
+ if (gtk_text_iter_inside_word (end))
+ gtk_text_iter_forward_word_end (end);
+}
+
+static void
+add_to_dictionary (GtkWidget * menuitem, SulekhaSpell * spell)
+{
+ char *word;
+ GtkTextIter start, end;
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (spell->view);
+
+ get_word_extents_from_mark (buffer, &start, &end, spell->mark_click);
+ word = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+ aspell_speller_add_to_personal (spell->speller, word, strlen (word));
+ aspell_speller_save_all_word_lists (spell->speller);
+
+ sulekhaspell_recheck_all (spell);
+
+ g_free (word);
+}
+
+static void
+ignore_all (GtkWidget * menuitem, SulekhaSpell * spell)
+{
+ char *word;
+ GtkTextIter start, end;
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (spell->view);
+
+ get_word_extents_from_mark (buffer, &start, &end, spell->mark_click);
+ word = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+ aspell_speller_add_to_session (spell->speller, word, strlen (word));
+
+ sulekhaspell_recheck_all (spell);
+
+ g_free (word);
+}
+
+static void
+replace_word (GtkWidget * menuitem, SulekhaSpell * spell)
+{
+ char *oldword;
+ const char *newword;
+ GtkTextIter start, end;
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (spell->view);
+
+ get_word_extents_from_mark (buffer, &start, &end, spell->mark_click);
+ oldword = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+ newword = gtk_label_get_text (GTK_LABEL (GTK_BIN (menuitem)->child));
+
+ if (debug)
+ {
+ g_print ("old word: '%s'\n", oldword);
+ print_iter ("s", &start);
+ print_iter ("e", &end);
+ g_print ("\nnew word: '%s'\n", newword);
+ }
+
+ gtk_text_buffer_delete (buffer, &start, &end);
+ gtk_text_buffer_insert (buffer, &start, newword, -1);
+
+ aspell_speller_store_replacement (spell->speller,
+ oldword, strlen (oldword),
+ newword, strlen (newword));
+
+ g_free (oldword);
+}
+
+GtkWidget *
+build_suggestion_menu (SulekhaSpell * spell, GtkTextBuffer * buffer,
+ const char *word)
+{
+ const char *suggestion;
+ GtkWidget *topmenu, *menu;
+ GtkWidget *mi;
+ GtkWidget *hbox;
+ int count = 0;
+ void *spelldata;
+ const AspellWordList *suggestions;
+ AspellStringEnumeration *elements;
+ char *label;
+ //santhosh
+ word = transliterate_ml (word, 0, strlen (word));
+
+ //end santhosh
+ topmenu = menu = gtk_menu_new ();
+
+ suggestions = aspell_speller_suggest (spell->speller, word, -1);
+ elements = aspell_word_list_elements (suggestions);
+
+ suggestion = aspell_string_enumeration_next (elements);
+ if (suggestion == NULL)
+ {
+ /* no suggestions. put something in the menu anyway... */
+ GtkWidget *label;
+ label = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (label), _("<i>(no suggestions)</i>"));
+
+ mi = gtk_menu_item_new ();
+ gtk_container_add (GTK_CONTAINER (mi), label);
+ gtk_widget_show_all (mi);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
+ }
+ else
+ {
+ /* build a set of menus with suggestions. */
+ while (suggestion != NULL)
+ {
+ if (count == 10)
+ {
+ mi = gtk_menu_item_new ();
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+
+ mi = gtk_menu_item_new_with_label (_("More..."));
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+
+ menu = gtk_menu_new ();
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
+ count = 0;
+ }
+ mi = gtk_menu_item_new_with_label (suggestion);
+ g_signal_connect (G_OBJECT (mi), "activate",
+ G_CALLBACK (replace_word), spell);
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+ count++;
+ suggestion = aspell_string_enumeration_next (elements);
+ }
+ }
+
+ delete_aspell_string_enumeration (elements);
+
+ /* Separator */
+ mi = gtk_menu_item_new ();
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
+
+ /* + Add to Dictionary */
+ label = g_strdup_printf (_("Add \"%s\" to Dictionary"), word);
+ mi = gtk_image_menu_item_new_with_label (label);
+ g_free (label);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi),
+ gtk_image_new_from_stock (GTK_STOCK_ADD,
+ GTK_ICON_SIZE_MENU));
+ g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (add_to_dictionary),
+ spell);
+ gtk_widget_show_all (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
+
+ /* - Ignore All */
+ mi = gtk_image_menu_item_new_with_label (_("Ignore All"));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi),
+ gtk_image_new_from_stock (GTK_STOCK_REMOVE,
+ GTK_ICON_SIZE_MENU));
+ g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (ignore_all),
+ spell);
+ gtk_widget_show_all (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
+
+ return topmenu;
+}
+
+static void
+populate_popup (GtkTextView * textview, GtkMenu * menu, SulekhaSpell * spell)
+{
+ GtkWidget *img, *mi;
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (textview);
+ GtkTextIter start, end;
+ char *word;
+
+ /* we need to figure out if they picked a misspelled word. */
+ get_word_extents_from_mark (buffer, &start, &end, spell->mark_click);
+
+ /* if our highlight algorithm ever messes up,
+ * this isn't correct, either. */
+ if (!gtk_text_iter_has_tag (&start, spell->tag_highlight))
+ return; /* word wasn't misspelled. */
+
+ /* menu separator comes first. */
+ mi = gtk_menu_item_new ();
+ gtk_widget_show (mi);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
+
+ /* then, on top of it, the suggestions menu. */
+ img = gtk_image_new_from_stock (GTK_STOCK_SPELL_CHECK, GTK_ICON_SIZE_MENU);
+ mi = gtk_image_menu_item_new_with_label (_("Spelling Suggestions"));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), img);
+
+ word = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi),
+ build_suggestion_menu (spell, buffer, word));
+
+ g_free (word);
+
+ gtk_widget_show_all (mi);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
+}
+
+/* when the user right-clicks on a word, they want to check that word.
+ * here, we do NOT move the cursor to the location of the clicked-upon word
+ * since that prevents the use of edit functions on the context menu. */
+static gboolean
+button_press_event (GtkTextView * view, GdkEventButton * event,
+ SulekhaSpell * spell)
+{
+ if (event->button == 3)
+ {
+ gint x, y;
+ GtkTextIter iter;
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (view);
+
+ /* handle deferred check if it exists */
+ if (spell->deferred_check)
+ check_deferred_range (spell, buffer, TRUE);
+
+ gtk_text_view_window_to_buffer_coords (view,
+ GTK_TEXT_WINDOW_TEXT,
+ event->x, event->y, &x, &y);
+ gtk_text_view_get_iter_at_location (view, &iter, x, y);
+ gtk_text_buffer_move_mark (buffer, spell->mark_click, &iter);
+ }
+ return FALSE; /* false: let gtk process this event, too.
+ we don't want to eat any events. */
+}
+
+/* This event occurs when the popup menu is requested through a key-binding
+ * (Menu Key or <shift>+F10 by default). In this case we want to set
+ * spell->mark_click to the cursor position. */
+static gboolean
+popup_menu_event (GtkTextView * view, SulekhaSpell * spell)
+{
+ GtkTextIter iter;
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (view);
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter,
+ gtk_text_buffer_get_insert (buffer));
+ gtk_text_buffer_move_mark (buffer, spell->mark_click, &iter);
+ return FALSE; /* false: let gtk process this event, too. */
+}
+
+static gboolean
+sulekhaspell_set_language_internal (SulekhaSpell * spell, const gchar * lang,
+ GError ** error)
+{
+ AspellConfig *config;
+ AspellCanHaveError *err;
+ AspellSpeller *speller;
+
+ if (lang == NULL)
+ {
+ lang = g_getenv ("LANG");
+ if (lang)
+ {
+ if (g_strncasecmp (lang, "C", 1) == 0)
+ lang = NULL;
+ else if (lang[0] == 0)
+ lang = NULL;
+ }
+ }
+
+ config = new_aspell_config ();
+ if (lang)
+ aspell_config_replace (config, "language-tag", lang);
+ aspell_config_replace (config, "encoding", "utf-8");
+ err = new_aspell_speller (config);
+ delete_aspell_config (config);
+
+ if (aspell_error_number (err) != 0)
+ {
+#ifdef USING_ASPELL
+ g_set_error (error, SULEKHASPELL_ERROR, SULEKHASPELL_ERROR_BACKEND,
+ "aspell: %s", aspell_error_message (err));
+#elif defined USING_PSPELL
+ g_set_error (error, SULEKHASPELL_ERROR, SULEKHASPELL_ERROR_BACKEND,
+ "pspell: %s", aspell_error_message (err));
+#endif
+ return FALSE;
+ }
+ if (spell->speller)
+ delete_aspell_speller (spell->speller);
+ spell->speller = to_aspell_speller (err);
+
+ return TRUE;
+}
+
+/**
+ * sulekhaspell_set_language:
+ * @spell: The #SulekhaSpell object.
+ * @lang: The language to use, in a form pspell understands (it appears to
+ * be a locale specifier?).
+ * @error: Return location for error.
+ *
+ * Set the language on @spell to @lang, possibily returning an error in
+ * @error.
+ *
+ * Returns: FALSE if there was an error.
+ */
+gboolean
+sulekhaspell_set_language (SulekhaSpell * spell, const gchar * lang,
+ GError ** error)
+{
+ gboolean ret;
+
+ if (error)
+ g_return_val_if_fail (*error == NULL, FALSE);
+
+ ret = sulekhaspell_set_language_internal (spell, lang, error);
+ if (ret)
+ sulekhaspell_recheck_all (spell);
+
+ return ret;
+}
+
+/**
+ * sulekhaspell_recheck_all:
+ * @spell: The #SulekhaSpell object.
+ *
+ * Recheck the spelling in the entire buffer.
+ */
+void
+sulekhaspell_recheck_all (SulekhaSpell * spell)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter start, end;
+
+ buffer = gtk_text_view_get_buffer (spell->view);
+
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+
+ check_range (spell, buffer, start, end, TRUE);
+}
+
+/**
+ * sulekhaspell_new_attach:
+ * @view: The #GtkTextView to attach to.
+ * @lang: The language to use, in a form pspell understands (it appears to
+ * be a locale specifier?).
+ * @error: Return location for error.
+ *
+ * Create a new #SulekhaSpell object attached to @view with language @lang.
+ *
+ * Returns: a new #SulekhaSpell object, or %NULL on error.
+ */
+SulekhaSpell *
+sulekhaspell_new_attach (GtkTextView * view, const gchar * lang,
+ GError ** error)
+{
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *tagtable;
+ GtkTextIter start, end;
+
+ SulekhaSpell *spell;
+
+#ifdef ENABLE_NLS
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset (PACKAGE, "UTF-8");
+#endif
+
+ if (error)
+ g_return_val_if_fail (*error == NULL, NULL);
+
+ spell = g_object_get_data (G_OBJECT (view), SULEKHASPELL_OBJECT_KEY);
+ g_assert (spell == NULL);
+
+ /* attach to the widget */
+ spell = g_new0 (SulekhaSpell, 1);
+ spell->view = view;
+ if (!sulekhaspell_set_language_internal (spell, lang, error))
+ {
+ g_free (spell);
+ return NULL;
+ }
+ g_object_set_data (G_OBJECT (view), SULEKHASPELL_OBJECT_KEY, spell);
+
+ g_signal_connect_swapped (G_OBJECT (view), "destroy",
+ G_CALLBACK (sulekhaspell_free), spell);
+ g_signal_connect (G_OBJECT (view), "button-press-event",
+ G_CALLBACK (button_press_event), spell);
+ g_signal_connect (G_OBJECT (view), "populate-popup",
+ G_CALLBACK (populate_popup), spell);
+ g_signal_connect (G_OBJECT (view), "popup-menu",
+ G_CALLBACK (popup_menu_event), spell);
+
+ buffer = gtk_text_view_get_buffer (view);
+
+ g_signal_connect (G_OBJECT (buffer),
+ "insert-text", G_CALLBACK (insert_text_before), spell);
+ g_signal_connect_after (G_OBJECT (buffer),
+ "insert-text",
+ G_CALLBACK (insert_text_after), spell);
+ g_signal_connect_after (G_OBJECT (buffer),
+ "delete-range",
+ G_CALLBACK (delete_range_after), spell);
+ g_signal_connect (G_OBJECT (buffer),
+ "mark-set", G_CALLBACK (mark_set), spell);
+
+ tagtable = gtk_text_buffer_get_tag_table (buffer);
+ spell->tag_highlight =
+ gtk_text_tag_table_lookup (tagtable, SULEKHASPELL_MISSPELLED_TAG);
+
+ if (spell->tag_highlight == NULL)
+ {
+ spell->tag_highlight = gtk_text_buffer_create_tag (buffer,
+ SULEKHASPELL_MISSPELLED_TAG,
+#ifdef HAVE_PANGO_UNDERLINE_ERROR
+ "underline",
+ PANGO_UNDERLINE_ERROR,
+#else
+ "foreground", "red",
+ "underline",
+ PANGO_UNDERLINE_SINGLE,
+#endif
+ NULL);
+ }
+
+ /* we create the mark here, but we don't use it until text is
+ * inserted, so we don't really care where iter points. */
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+ spell->mark_insert_start = gtk_text_buffer_create_mark (buffer,
+ "sulekhaspell-insert-start",
+ &start, TRUE);
+ spell->mark_insert_end = gtk_text_buffer_create_mark (buffer,
+ "sulekhaspell-insert-end",
+ &start, TRUE);
+ spell->mark_click = gtk_text_buffer_create_mark (buffer,
+ "sulekhaspell-click",
+ &start, TRUE);
+
+ spell->deferred_check = FALSE;
+
+ /* now check the entire text buffer. */
+ sulekhaspell_recheck_all (spell);
+ return spell;
+}
+
+static void
+sulekhaspell_free (SulekhaSpell * spell)
+{
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextIter start, end;
+
+ buffer = gtk_text_view_get_buffer (spell->view);
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+ gtk_text_buffer_remove_tag (buffer, spell->tag_highlight, &start, &end);
+
+ gtk_text_buffer_delete_mark (buffer, spell->mark_insert_start);
+ gtk_text_buffer_delete_mark (buffer, spell->mark_insert_end);
+ gtk_text_buffer_delete_mark (buffer, spell->mark_click);
+
+ delete_aspell_speller (spell->speller);
+
+ g_signal_handlers_disconnect_matched (spell->view,
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, spell);
+ g_signal_handlers_disconnect_matched (buffer,
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, spell);
+ g_free (spell);
+}
+
+/**
+ * sulekhaspell_get_from_text_view:
+ * @view: A #GtkTextView.
+ *
+ * Retrieves the #SulekhaSpell object attached to a text view.
+ *
+ * Returns: the #SulekhaSpell object, or %NULL if there is no #SulekhaSpell
+ * attached to @view.
+ */
+SulekhaSpell *
+sulekhaspell_get_from_text_view (GtkTextView * view)
+{
+ return g_object_get_data (G_OBJECT (view), SULEKHASPELL_OBJECT_KEY);
+}
+
+/**
+ * sulekhaspell_detach:
+ * @spell: A #SulekhaSpell.
+ *
+ * Detaches this #SulekhaSpell from its text view. Use
+ * sulekhaspell_get_from_text_view() to retrieve a SulekhaSpell from a
+ * #GtkTextView.
+ */
+void
+sulekhaspell_detach (SulekhaSpell * spell)
+{
+ g_return_if_fail (spell != NULL);
+
+ g_object_set_data (G_OBJECT (spell->view), SULEKHASPELL_OBJECT_KEY, NULL);
+ sulekhaspell_free (spell);
+}