diff options
author | Perberos <[email protected]> | 2011-11-07 19:52:18 -0300 |
---|---|---|
committer | Perberos <[email protected]> | 2011-11-07 19:52:18 -0300 |
commit | 5ded9cba8563f336939400303d6a841d5089b107 (patch) | |
tree | c5676588cff26ba37e12369fe4de24b54e9f6682 /plugins/spell/pluma-automatic-spell-checker.c | |
parent | f00b3a11a199f9f85a4d46a600f9d14179b37dbf (diff) | |
download | pluma-5ded9cba8563f336939400303d6a841d5089b107.tar.bz2 pluma-5ded9cba8563f336939400303d6a841d5089b107.tar.xz |
renaming from gedit to pluma
Diffstat (limited to 'plugins/spell/pluma-automatic-spell-checker.c')
-rwxr-xr-x | plugins/spell/pluma-automatic-spell-checker.c | 1015 |
1 files changed, 1015 insertions, 0 deletions
diff --git a/plugins/spell/pluma-automatic-spell-checker.c b/plugins/spell/pluma-automatic-spell-checker.c new file mode 100755 index 00000000..88393379 --- /dev/null +++ b/plugins/spell/pluma-automatic-spell-checker.c @@ -0,0 +1,1015 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * pluma-automatic-spell-checker.c + * This file is part of pluma + * + * Copyright (C) 2002 Paolo Maggi + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the pluma Team, 2002. See the AUTHORS file for a + * list of people on the pluma Team. + * See the ChangeLog files for a list of changes. + */ + +/* This is a modified version of gtkspell 2.0.5 (gtkspell.sf.net) */ +/* gtkspell - a spell-checking addon for GTK's TextView widget + * Copyright (c) 2002 Evan Martin. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <glib/gi18n.h> + +#include "pluma-automatic-spell-checker.h" +#include "pluma-spell-utils.h" + +struct _PlumaAutomaticSpellChecker { + PlumaDocument *doc; + GSList *views; + + GtkTextMark *mark_insert_start; + GtkTextMark *mark_insert_end; + gboolean deferred_check; + + GtkTextTag *tag_highlight; + GtkTextMark *mark_click; + + PlumaSpellChecker *spell_checker; +}; + +static GQuark automatic_spell_checker_id = 0; +static GQuark suggestion_id = 0; + +static void pluma_automatic_spell_checker_free_internal (PlumaAutomaticSpellChecker *spell); + +static void +view_destroy (PlumaView *view, PlumaAutomaticSpellChecker *spell) +{ + pluma_automatic_spell_checker_detach_view (spell, view); +} + +static void +check_word (PlumaAutomaticSpellChecker *spell, GtkTextIter *start, GtkTextIter *end) +{ + gchar *word; + + word = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc), start, end, FALSE); + + /* + g_print ("Check word: %s [%d - %d]\n", word, gtk_text_iter_get_offset (start), + gtk_text_iter_get_offset (end)); + */ + + if (!pluma_spell_checker_check_word (spell->spell_checker, word, -1)) + { + /* + g_print ("Apply tag: [%d - %d]\n", gtk_text_iter_get_offset (start), + gtk_text_iter_get_offset (end)); + */ + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (spell->doc), + spell->tag_highlight, + start, + end); + } + + g_free (word); +} + +static void +check_range (PlumaAutomaticSpellChecker *spell, + 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; + GtkTextIter wend; + GtkTextIter cursor; + GtkTextIter precursor; + gboolean highlight; + + /* + g_print ("Check range: [%d - %d]\n", gtk_text_iter_get_offset (&start), + gtk_text_iter_get_offset (&end)); + */ + + 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. */ + + if (gtk_text_iter_forward_word_end (&start)) + gtk_text_iter_backward_word_start (&start); + } + } + + gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (spell->doc), + &cursor, + gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (spell->doc))); + + 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 (GTK_TEXT_BUFFER (spell->doc), + 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); + } + + wstart = start; + + while (pluma_spell_utils_skip_no_spell_check (&wstart, &end) && + gtk_text_iter_compare (&wstart, &end) < 0) + { + gboolean inword; + + /* 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, &wstart, &wend); + else + spell->deferred_check = TRUE; + } + else + { + check_word (spell, &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 (PlumaAutomaticSpellChecker *spell, + gboolean force_all) +{ + GtkTextIter start, end; + + gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (spell->doc), + &start, + spell->mark_insert_start); + gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (spell->doc), + &end, + spell->mark_insert_end); + + check_range (spell, 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, PlumaAutomaticSpellChecker *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, PlumaAutomaticSpellChecker *spell) +{ + GtkTextIter start; + + /* we need to check a range of text. */ + gtk_text_buffer_get_iter_at_mark (buffer, &start, spell->mark_insert_start); + + check_range (spell, 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, + PlumaAutomaticSpellChecker *spell) +{ + check_range (spell, *start, *end, FALSE); +} + +static void +mark_set (GtkTextBuffer *buffer, + GtkTextIter *iter, + GtkTextMark *mark, + PlumaAutomaticSpellChecker *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, 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 +remove_tag_to_word (PlumaAutomaticSpellChecker *spell, const gchar *word) +{ + GtkTextIter iter; + GtkTextIter match_start, match_end; + + gboolean found; + + gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (spell->doc), &iter, 0); + + found = TRUE; + + while (found) + { + found = gtk_text_iter_forward_search (&iter, + word, + GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY, + &match_start, + &match_end, + NULL); + + if (found) + { + if (gtk_text_iter_starts_word (&match_start) && + gtk_text_iter_ends_word (&match_end)) + { + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (spell->doc), + spell->tag_highlight, + &match_start, + &match_end); + } + + iter = match_end; + } + } +} + +static void +add_to_dictionary (GtkWidget *menuitem, PlumaAutomaticSpellChecker *spell) +{ + gchar *word; + + GtkTextIter start, end; + + get_word_extents_from_mark (GTK_TEXT_BUFFER (spell->doc), &start, &end, spell->mark_click); + + word = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc), + &start, + &end, + FALSE); + + pluma_spell_checker_add_word_to_personal (spell->spell_checker, word, -1); + + g_free (word); +} + +static void +ignore_all (GtkWidget *menuitem, PlumaAutomaticSpellChecker *spell) +{ + gchar *word; + + GtkTextIter start, end; + + get_word_extents_from_mark (GTK_TEXT_BUFFER (spell->doc), &start, &end, spell->mark_click); + + word = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc), + &start, + &end, + FALSE); + + pluma_spell_checker_add_word_to_session (spell->spell_checker, word, -1); + + g_free (word); +} + +static void +replace_word (GtkWidget *menuitem, PlumaAutomaticSpellChecker *spell) +{ + gchar *oldword; + const gchar *newword; + + GtkTextIter start, end; + + get_word_extents_from_mark (GTK_TEXT_BUFFER (spell->doc), &start, &end, spell->mark_click); + + oldword = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc), &start, &end, FALSE); + + newword = g_object_get_qdata (G_OBJECT (menuitem), suggestion_id); + g_return_if_fail (newword != NULL); + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (spell->doc)); + + gtk_text_buffer_delete (GTK_TEXT_BUFFER (spell->doc), &start, &end); + gtk_text_buffer_insert (GTK_TEXT_BUFFER (spell->doc), &start, newword, -1); + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (spell->doc)); + + pluma_spell_checker_set_correction (spell->spell_checker, + oldword, strlen (oldword), + newword, strlen (newword)); + + g_free (oldword); +} + +static GtkWidget * +build_suggestion_menu (PlumaAutomaticSpellChecker *spell, const gchar *word) +{ + GtkWidget *topmenu, *menu; + GtkWidget *mi; + GSList *suggestions; + GSList *list; + gchar *label_text; + + topmenu = menu = gtk_menu_new(); + + suggestions = pluma_spell_checker_get_suggestions (spell->spell_checker, word, -1); + + list = suggestions; + + if (suggestions == NULL) + { + /* no suggestions. put something in the menu anyway... */ + GtkWidget *label; + /* Translators: Displayed in the "Check Spelling" dialog if there are no suggestions for the current misspelled word */ + label = gtk_label_new (_("(no suggested words)")); + + mi = gtk_menu_item_new (); + gtk_widget_set_sensitive (mi, FALSE); + gtk_container_add (GTK_CONTAINER(mi), label); + gtk_widget_show_all (mi); + gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi); + } + else + { + gint count = 0; + + /* build a set of menus with suggestions. */ + while (suggestions != NULL) + { + GtkWidget *label; + + if (count == 10) + { + /* Separator */ + mi = gtk_menu_item_new (); + gtk_widget_show (mi); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + + mi = gtk_menu_item_new_with_mnemonic (_("_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; + } + + label_text = g_strdup_printf ("<b>%s</b>", (gchar*) suggestions->data); + + label = gtk_label_new (label_text); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + + mi = gtk_menu_item_new (); + gtk_container_add (GTK_CONTAINER(mi), label); + + gtk_widget_show_all (mi); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + + g_object_set_qdata_full (G_OBJECT (mi), + suggestion_id, + g_strdup (suggestions->data), + (GDestroyNotify)g_free); + + g_free (label_text); + g_signal_connect (mi, + "activate", + G_CALLBACK (replace_word), + spell); + + count++; + + suggestions = g_slist_next (suggestions); + } + } + + /* free the suggestion list */ + suggestions = list; + + while (list) + { + g_free (list->data); + list = g_slist_next (list); + } + + g_slist_free (suggestions); + + /* Separator */ + mi = gtk_menu_item_new (); + gtk_widget_show (mi); + gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi); + + /* Ignore all */ + mi = gtk_image_menu_item_new_with_mnemonic (_("_Ignore All")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), + gtk_image_new_from_stock (GTK_STOCK_GOTO_BOTTOM, + GTK_ICON_SIZE_MENU)); + + g_signal_connect (mi, + "activate", + G_CALLBACK(ignore_all), + spell); + + gtk_widget_show_all (mi); + + gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi); + + /* + Add to Dictionary */ + mi = gtk_image_menu_item_new_with_mnemonic (_("_Add")); + 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 (mi, + "activate", + G_CALLBACK (add_to_dictionary), + 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, PlumaAutomaticSpellChecker *spell) +{ + GtkWidget *img, *mi; + GtkTextIter start, end; + char *word; + + /* we need to figure out if they picked a misspelled word. */ + get_word_extents_from_mark (GTK_TEXT_BUFFER (spell->doc), &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_mnemonic (_("_Spelling Suggestions...")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), img); + + word = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc), &start, &end, FALSE); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), + build_suggestion_menu (spell, word)); + g_free(word); + + gtk_widget_show_all (mi); + gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi); +} + +void +pluma_automatic_spell_checker_recheck_all (PlumaAutomaticSpellChecker *spell) +{ + GtkTextIter start, end; + + g_return_if_fail (spell != NULL); + + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (spell->doc), &start, &end); + + check_range (spell, start, end, TRUE); +} + +static void +add_word_signal_cb (PlumaSpellChecker *checker, + const gchar *word, + gint len, + PlumaAutomaticSpellChecker *spell) +{ + gchar *w; + + if (len < 0) + w = g_strdup (word); + else + w = g_strndup (word, len); + + remove_tag_to_word (spell, w); + + g_free (w); +} + +static void +set_language_cb (PlumaSpellChecker *checker, + const PlumaSpellCheckerLanguage *lang, + PlumaAutomaticSpellChecker *spell) +{ + pluma_automatic_spell_checker_recheck_all (spell); +} + +static void +clear_session_cb (PlumaSpellChecker *checker, + PlumaAutomaticSpellChecker *spell) +{ + pluma_automatic_spell_checker_recheck_all (spell); +} + +/* 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, + PlumaAutomaticSpellChecker *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, 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. */ +} + +/* Move the insert mark before popping up the menu, otherwise it + * will contain the wrong set of suggestions. + */ +static gboolean +popup_menu_event (GtkTextView *view, PlumaAutomaticSpellChecker *spell) +{ + GtkTextIter iter; + GtkTextBuffer *buffer; + + buffer = gtk_text_view_get_buffer (view); + + /* handle deferred check if it exists */ + if (spell->deferred_check) + check_deferred_range (spell, TRUE); + + 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; +} + +static void +tag_table_changed (GtkTextTagTable *table, + PlumaAutomaticSpellChecker *spell) +{ + g_return_if_fail (spell->tag_highlight != NULL); + + gtk_text_tag_set_priority (spell->tag_highlight, + gtk_text_tag_table_get_size (table) - 1); +} + +static void +tag_added_or_removed (GtkTextTagTable *table, + GtkTextTag *tag, + PlumaAutomaticSpellChecker *spell) +{ + tag_table_changed (table, spell); +} + +static void +tag_changed (GtkTextTagTable *table, + GtkTextTag *tag, + gboolean size_changed, + PlumaAutomaticSpellChecker *spell) +{ + tag_table_changed (table, spell); +} + +static void +highlight_updated (GtkSourceBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end, + PlumaAutomaticSpellChecker *spell) +{ + check_range (spell, *start, *end, FALSE); +} + +static void +spell_tag_destroyed (PlumaAutomaticSpellChecker *spell, + GObject *where_the_object_was) +{ + spell->tag_highlight = NULL; +} + +PlumaAutomaticSpellChecker * +pluma_automatic_spell_checker_new (PlumaDocument *doc, + PlumaSpellChecker *checker) +{ + PlumaAutomaticSpellChecker *spell; + GtkTextTagTable *tag_table; + GtkTextIter start, end; + + g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL); + g_return_val_if_fail (PLUMA_IS_SPELL_CHECKER (checker), NULL); + g_return_val_if_fail ((spell = pluma_automatic_spell_checker_get_from_document (doc)) == NULL, + spell); + + /* attach to the widget */ + spell = g_new0 (PlumaAutomaticSpellChecker, 1); + + spell->doc = doc; + spell->spell_checker = g_object_ref (checker); + + if (automatic_spell_checker_id == 0) + { + automatic_spell_checker_id = + g_quark_from_string ("PlumaAutomaticSpellCheckerID"); + } + if (suggestion_id == 0) + { + suggestion_id = g_quark_from_string ("PlumaAutoSuggestionID"); + } + + g_object_set_qdata_full (G_OBJECT (doc), + automatic_spell_checker_id, + spell, + (GDestroyNotify)pluma_automatic_spell_checker_free_internal); + + g_signal_connect (doc, + "insert-text", + G_CALLBACK (insert_text_before), + spell); + g_signal_connect_after (doc, + "insert-text", + G_CALLBACK (insert_text_after), + spell); + g_signal_connect_after (doc, + "delete-range", + G_CALLBACK (delete_range_after), + spell); + g_signal_connect (doc, + "mark-set", + G_CALLBACK (mark_set), + spell); + + g_signal_connect (doc, + "highlight-updated", + G_CALLBACK (highlight_updated), + spell); + + g_signal_connect (spell->spell_checker, + "add_word_to_session", + G_CALLBACK (add_word_signal_cb), + spell); + g_signal_connect (spell->spell_checker, + "add_word_to_personal", + G_CALLBACK (add_word_signal_cb), + spell); + g_signal_connect (spell->spell_checker, + "clear_session", + G_CALLBACK (clear_session_cb), + spell); + g_signal_connect (spell->spell_checker, + "set_language", + G_CALLBACK (set_language_cb), + spell); + + spell->tag_highlight = gtk_text_buffer_create_tag ( + GTK_TEXT_BUFFER (doc), + "gtkspell-misspelled", + "underline", PANGO_UNDERLINE_ERROR, + NULL); + + g_object_weak_ref (G_OBJECT (spell->tag_highlight), + (GWeakNotify)spell_tag_destroyed, + spell); + + tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (doc)); + + gtk_text_tag_set_priority (spell->tag_highlight, + gtk_text_tag_table_get_size (tag_table) - 1); + + g_signal_connect (tag_table, + "tag-added", + G_CALLBACK (tag_added_or_removed), + spell); + g_signal_connect (tag_table, + "tag-removed", + G_CALLBACK (tag_added_or_removed), + spell); + g_signal_connect (tag_table, + "tag-changed", + G_CALLBACK (tag_changed), + spell); + + /* 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 (GTK_TEXT_BUFFER (doc), &start, &end); + + spell->mark_insert_start = gtk_text_buffer_get_mark (GTK_TEXT_BUFFER (doc), + "pluma-automatic-spell-checker-insert-start"); + + if (spell->mark_insert_start == NULL) + { + spell->mark_insert_start = + gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc), + "pluma-automatic-spell-checker-insert-start", + &start, + TRUE); + } + else + { + gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc), + spell->mark_insert_start, + &start); + } + + spell->mark_insert_end = gtk_text_buffer_get_mark (GTK_TEXT_BUFFER (doc), + "pluma-automatic-spell-checker-insert-end"); + + if (spell->mark_insert_end == NULL) + { + spell->mark_insert_end = + gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc), + "pluma-automatic-spell-checker-insert-end", + &start, + TRUE); + } + else + { + gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc), + spell->mark_insert_end, + &start); + } + + spell->mark_click = gtk_text_buffer_get_mark (GTK_TEXT_BUFFER (doc), + "pluma-automatic-spell-checker-click"); + + if (spell->mark_click == NULL) + { + spell->mark_click = + gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc), + "pluma-automatic-spell-checker-click", + &start, + TRUE); + } + else + { + gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc), + spell->mark_click, + &start); + } + + spell->deferred_check = FALSE; + + return spell; +} + +PlumaAutomaticSpellChecker * +pluma_automatic_spell_checker_get_from_document (const PlumaDocument *doc) +{ + g_return_val_if_fail (PLUMA_IS_DOCUMENT (doc), NULL); + + if (automatic_spell_checker_id == 0) + return NULL; + + return g_object_get_qdata (G_OBJECT (doc), automatic_spell_checker_id); +} + +void +pluma_automatic_spell_checker_free (PlumaAutomaticSpellChecker *spell) +{ + g_return_if_fail (spell != NULL); + g_return_if_fail (pluma_automatic_spell_checker_get_from_document (spell->doc) == spell); + + if (automatic_spell_checker_id == 0) + return; + + g_object_set_qdata (G_OBJECT (spell->doc), automatic_spell_checker_id, NULL); +} + +static void +pluma_automatic_spell_checker_free_internal (PlumaAutomaticSpellChecker *spell) +{ + GtkTextTagTable *table; + GtkTextIter start, end; + GSList *list; + + g_return_if_fail (spell != NULL); + + table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (spell->doc)); + + if (table != NULL && spell->tag_highlight != NULL) + { + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (spell->doc), + &start, + &end); + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (spell->doc), + spell->tag_highlight, + &start, + &end); + + g_signal_handlers_disconnect_matched (G_OBJECT (table), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + gtk_text_tag_table_remove (table, spell->tag_highlight); + } + + g_signal_handlers_disconnect_matched (G_OBJECT (spell->doc), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + g_signal_handlers_disconnect_matched (G_OBJECT (spell->spell_checker), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + g_object_unref (spell->spell_checker); + + list = spell->views; + while (list != NULL) + { + PlumaView *view = PLUMA_VIEW (list->data); + + g_signal_handlers_disconnect_matched (G_OBJECT (view), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + g_signal_handlers_disconnect_matched (G_OBJECT (view), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + list = g_slist_next (list); + } + + g_slist_free (spell->views); + + g_free (spell); +} + +void +pluma_automatic_spell_checker_attach_view ( + PlumaAutomaticSpellChecker *spell, + PlumaView *view) +{ + g_return_if_fail (spell != NULL); + g_return_if_fail (PLUMA_IS_VIEW (view)); + + g_return_if_fail (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)) == + GTK_TEXT_BUFFER (spell->doc)); + + g_signal_connect (view, + "button-press-event", + G_CALLBACK (button_press_event), + spell); + g_signal_connect (view, + "popup-menu", + G_CALLBACK (popup_menu_event), + spell); + g_signal_connect (view, + "populate-popup", + G_CALLBACK (populate_popup), + spell); + g_signal_connect (view, + "destroy", + G_CALLBACK (view_destroy), + spell); + + spell->views = g_slist_prepend (spell->views, view); +} + +void +pluma_automatic_spell_checker_detach_view ( + PlumaAutomaticSpellChecker *spell, + PlumaView *view) +{ + g_return_if_fail (spell != NULL); + g_return_if_fail (PLUMA_IS_VIEW (view)); + + g_return_if_fail (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)) == + GTK_TEXT_BUFFER (spell->doc)); + g_return_if_fail (spell->views != NULL); + + g_signal_handlers_disconnect_matched (G_OBJECT (view), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + g_signal_handlers_disconnect_matched (G_OBJECT (view), + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + spell); + + spell->views = g_slist_remove (spell->views, view); +} + |