From ecc1f7ae88ae9ef26f182f97a481a00470969cd1 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Wed, 9 May 2012 19:50:33 +0200 Subject: Bug #200683 - Composer subject spell checking --- widgets/misc/Makefile.am | 2 + widgets/misc/e-spell-entry.c | 861 +++++++++++++++++++++++++++++++++++++++++++ widgets/misc/e-spell-entry.h | 59 +++ 3 files changed, 922 insertions(+) create mode 100644 widgets/misc/e-spell-entry.c create mode 100644 widgets/misc/e-spell-entry.h (limited to 'widgets') diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am index ab7efa0667..5768c72bc7 100644 --- a/widgets/misc/Makefile.am +++ b/widgets/misc/Makefile.am @@ -60,6 +60,7 @@ widgetsinclude_HEADERS = \ e-signature-preview.h \ e-signature-script-dialog.h \ e-signature-tree-view.h \ + e-spell-entry.h \ e-url-entry.h \ e-web-view.h \ e-web-view-gtkhtml.h \ @@ -142,6 +143,7 @@ libemiscwidgets_la_SOURCES = \ e-signature-preview.c \ e-signature-script-dialog.c \ e-signature-tree-view.c \ + e-spell-entry.c \ e-url-entry.c \ e-web-view.c \ e-web-view-gtkhtml.c \ diff --git a/widgets/misc/e-spell-entry.c b/widgets/misc/e-spell-entry.c new file mode 100644 index 0000000000..1f0e3c9dc4 --- /dev/null +++ b/widgets/misc/e-spell-entry.c @@ -0,0 +1,861 @@ +/* + * e-spell-entry.c + * + * This program 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 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + */ + +/* This code is based on libsexy's SexySpellEntry */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include "e-spell-entry.h" + +enum { + PROP_0, + PROP_CHECKING_ENABLED +}; + +struct _ESpellEntryPrivate +{ + PangoAttrList *attr_list; + gint mark_character; + gint entry_scroll_offset; + GSettings *settings; + gboolean custom_checkers; + gboolean checking_enabled; + GSList *checkers; + gchar **words; + gint *word_starts; + gint *word_ends; +}; + +#define E_SPELL_ENTRY_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_SPELL_ENTRY, ESpellEntryPrivate)) + +G_DEFINE_TYPE (ESpellEntry, e_spell_entry, GTK_TYPE_ENTRY); + +static gboolean +word_misspelled (ESpellEntry *entry, + gint start, + gint end) +{ + const gchar *text; + gchar *word; + gboolean result = TRUE; + + if (start == end) + return FALSE; + + text = gtk_entry_get_text (GTK_ENTRY (entry)); + word = g_new0 (gchar, end - start + 2); + + g_strlcpy (word, text + start, end - start + 1); + + if (g_unichar_isalpha (*word)) { + GSList *li; + gssize wlen = strlen (word); + + for (li = entry->priv->checkers; li; li = g_slist_next (li)) { + GtkhtmlSpellChecker *checker = li->data; + if (gtkhtml_spell_checker_check_word (checker, word, wlen)) { + result = FALSE; + break; + } + } + } + g_free (word); + + return result; +} + +static void +insert_underline (ESpellEntry *entry, + guint start, + guint end) +{ + PangoAttribute *ucolor = pango_attr_underline_color_new (65535, 0, 0); + PangoAttribute *unline = pango_attr_underline_new (PANGO_UNDERLINE_ERROR); + + ucolor->start_index = start; + unline->start_index = start; + + ucolor->end_index = end; + unline->end_index = end; + + pango_attr_list_insert (entry->priv->attr_list, ucolor); + pango_attr_list_insert (entry->priv->attr_list, unline); +} + +static void +check_word (ESpellEntry *entry, + gint start, + gint end) +{ + PangoAttrIterator *it; + + /* Check to see if we've got any attributes at this position. + * If so, free them, since we'll readd it if the word is misspelled */ + it = pango_attr_list_get_iterator (entry->priv->attr_list); + + if (it == NULL) + return; + do { + gint s, e; + pango_attr_iterator_range (it, &s, &e); + if (s == start) { + GSList *attrs = pango_attr_iterator_get_attrs (it); + g_slist_foreach (attrs, (GFunc) pango_attribute_destroy, NULL); + g_slist_free (attrs); + } + } while (pango_attr_iterator_next (it)); + pango_attr_iterator_destroy (it); + + if (word_misspelled (entry, start, end)) + insert_underline (entry, start, end); +} + +static void +spell_entry_recheck_all (ESpellEntry *entry) +{ + GtkWidget *widget = GTK_WIDGET (entry); + PangoLayout *layout; + gint length, i; + + /* Remove all existing pango attributes. These will get read as we check */ + pango_attr_list_unref (entry->priv->attr_list); + entry->priv->attr_list = pango_attr_list_new (); + + if (entry->priv->checkers && entry->priv->checking_enabled) { + /* Loop through words */ + for (i = 0; entry->priv->words[i]; i++) { + length = strlen (entry->priv->words[i]); + if (length == 0) + continue; + check_word (entry, entry->priv->word_starts[i], entry->priv->word_ends[i]); + } + + layout = gtk_entry_get_layout (GTK_ENTRY (entry)); + pango_layout_set_attributes (layout, entry->priv->attr_list); + } + + if (gtk_widget_get_realized (widget)) + gtk_widget_queue_draw (widget); +} + +static void +get_word_extents_from_position (ESpellEntry *entry, + gint *start, + gint *end, + guint position) +{ + const gchar *text; + gint i, bytes_pos; + + *start = -1; + *end = -1; + + if (entry->priv->words == NULL) + return; + + text = gtk_entry_get_text (GTK_ENTRY (entry)); + bytes_pos = (gint) (g_utf8_offset_to_pointer (text, position) - text); + + for (i = 0; entry->priv->words[i]; i++) { + if (bytes_pos >= entry->priv->word_starts[i] && + bytes_pos <= entry->priv->word_ends[i]) { + *start = entry->priv->word_starts[i]; + *end = entry->priv->word_ends[i]; + return; + } + } +} + +static void +entry_strsplit_utf8 (GtkEntry *entry, + gchar ***set, + gint **starts, + gint **ends) +{ + PangoLayout *layout; + PangoLogAttr *log_attrs; + const gchar *text; + gint n_attrs, n_strings, i, j; + + layout = gtk_entry_get_layout (GTK_ENTRY (entry)); + text = gtk_entry_get_text (GTK_ENTRY (entry)); + pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs); + + /* Find how many words we have */ + n_strings = 0; + for (i = 0; i < n_attrs; i++) + if (log_attrs[i].is_word_start) + n_strings++; + + *set = g_new0 (gchar *, n_strings + 1); + *starts = g_new0 (gint, n_strings); + *ends = g_new0 (gint, n_strings); + + /* Copy out strings */ + for (i = 0, j = 0; i < n_attrs; i++) { + if (log_attrs[i].is_word_start) { + gint cend, bytes; + gchar *start; + + /* Find the end of this string */ + cend = i; + while (!(log_attrs[cend].is_word_end)) + cend++; + + /* Copy sub-string */ + start = g_utf8_offset_to_pointer (text, i); + bytes = (gint) (g_utf8_offset_to_pointer (text, cend) - start); + (*set)[j] = g_new0 (gchar, bytes + 1); + (*starts)[j] = (gint) (start - text); + (*ends)[j] = (gint) (start - text + bytes); + g_utf8_strncpy ((*set)[j], start, cend - i); + + /* Move on to the next word */ + j++; + } + } + + g_free (log_attrs); +} + +static void +add_to_dictionary (GtkWidget *menuitem, + ESpellEntry *entry) +{ + gchar *word; + gint start, end; + GtkhtmlSpellChecker *checker; + + get_word_extents_from_position (entry, &start, &end, entry->priv->mark_character); + word = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end); + + checker = g_object_get_data (G_OBJECT (menuitem), "spell-entry-checker"); + if (checker) + gtkhtml_spell_checker_add_word (checker, word, -1); + + g_free (word); + + if (entry->priv->words) { + g_strfreev (entry->priv->words); + g_free (entry->priv->word_starts); + g_free (entry->priv->word_ends); + } + + entry_strsplit_utf8 (GTK_ENTRY (entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + spell_entry_recheck_all (entry); +} + +static void +ignore_all (GtkWidget *menuitem, + ESpellEntry *entry) +{ + gchar *word; + gint start, end; + GSList *li; + + get_word_extents_from_position (entry, &start, &end, entry->priv->mark_character); + word = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end); + + for (li = entry->priv->checkers; li; li = g_slist_next (li)) { + GtkhtmlSpellChecker *checker = li->data; + gtkhtml_spell_checker_add_word_to_session (checker, word, -1); + } + + g_free (word); + + if (entry->priv->words) { + g_strfreev (entry->priv->words); + g_free (entry->priv->word_starts); + g_free (entry->priv->word_ends); + } + entry_strsplit_utf8 (GTK_ENTRY (entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + spell_entry_recheck_all (entry); +} + +static void +replace_word (GtkWidget *menuitem, + ESpellEntry *entry) +{ + gchar *oldword; + const gchar *newword; + gint start, end; + gint cursor; + GtkhtmlSpellChecker *checker; + + get_word_extents_from_position (entry, &start, &end, entry->priv->mark_character); + oldword = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end); + newword = gtk_label_get_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN (menuitem)))); + + cursor = gtk_editable_get_position (GTK_EDITABLE (entry)); + /* is the cursor at the end? If so, restore it there */ + if (g_utf8_strlen (gtk_entry_get_text (GTK_ENTRY (entry)), -1) == cursor) + cursor = -1; + else if (cursor > start && cursor <= end) + cursor = start; + + gtk_editable_delete_text (GTK_EDITABLE (entry), start, end); + gtk_editable_set_position (GTK_EDITABLE (entry), start); + gtk_editable_insert_text (GTK_EDITABLE (entry), newword, strlen (newword), + &start); + gtk_editable_set_position (GTK_EDITABLE (entry), cursor); + + checker = g_object_get_data (G_OBJECT (menuitem), "spell-entry-checker"); + + if (checker) + gtkhtml_spell_checker_store_replacement (checker, oldword, -1, newword, -1); + + g_free (oldword); +} + +static void +build_suggestion_menu (ESpellEntry *entry, + GtkWidget *menu, + GtkhtmlSpellChecker *checker, + const gchar *word) +{ + GtkWidget *mi; + GList *suggestions, *iter; + + suggestions = gtkhtml_spell_checker_get_suggestions (checker, word, -1); + + if (!suggestions) { + /* no suggestions. Put something in the menu anyway... */ + GtkWidget *label = gtk_label_new (_("(no suggestions)")); + PangoAttribute *attribute; + PangoAttrList *attribute_list; + + attribute_list = pango_attr_list_new (); + attribute = pango_attr_style_new (PANGO_STYLE_ITALIC); + pango_attr_list_insert (attribute_list, attribute); + gtk_label_set_attributes (GTK_LABEL (label), attribute_list); + pango_attr_list_unref (attribute_list); + + mi = gtk_separator_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 { + gint ii = 0; + + /* build a set of menus with suggestions */ + for (iter = suggestions; iter; iter = g_list_next (iter), ii++) { + if ((ii != 0) && (ii % 10 == 0)) { + mi = gtk_separator_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); + } + + mi = gtk_menu_item_new_with_label (iter->data); + g_object_set_data (G_OBJECT (mi), "spell-entry-checker", checker); + g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (replace_word), entry); + gtk_widget_show (mi); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + } + } + + g_list_free_full (suggestions, g_free); +} + +static GtkWidget * +build_spelling_menu (ESpellEntry *entry, + const gchar *word) +{ + GtkhtmlSpellChecker *checker; + GtkWidget *topmenu, *mi; + gchar *label; + + topmenu = gtk_menu_new (); + + if (!entry->priv->checkers) + return topmenu; + + /* Suggestions */ + if (!entry->priv->checkers->next) { + checker = entry->priv->checkers->data; + build_suggestion_menu (entry, topmenu, checker, word); + } else { + GSList *li; + GtkWidget *menu; + const gchar *lang_name; + + for (li = entry->priv->checkers; li; li = g_slist_next (li)) { + const GtkhtmlSpellLanguage *language; + + checker = li->data; + language = gtkhtml_spell_checker_get_language (checker); + if (!language) + continue; + + lang_name = gtkhtml_spell_language_get_name (language); + if (!lang_name) + lang_name = gtkhtml_spell_language_get_code (language); + + mi = gtk_menu_item_new_with_label (lang_name ? lang_name : "???"); + + gtk_widget_show (mi); + gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi); + menu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu); + build_suggestion_menu (entry, menu, checker, word); + } + } + + /* Separator */ + mi = gtk_separator_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)); + + if (!entry->priv->checkers->next) { + checker = entry->priv->checkers->data; + g_object_set_data (G_OBJECT (mi), "spell-entry-checker", checker); + g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (add_to_dictionary), entry); + } else { + GSList *li; + GtkWidget *menu, *submi; + const gchar *lang_name; + + menu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu); + + for (li = entry->priv->checkers; li; li = g_slist_next (li)) { + const GtkhtmlSpellLanguage *language; + + checker = li->data; + language = gtkhtml_spell_checker_get_language (checker); + if (!language) + continue; + + lang_name = gtkhtml_spell_language_get_name (language); + if (!lang_name) + lang_name = gtkhtml_spell_language_get_code (language); + + submi = gtk_menu_item_new_with_label (lang_name ? lang_name : "???"); + g_object_set_data (G_OBJECT (submi), "spell-entry-checker", checker); + g_signal_connect (G_OBJECT (submi), "activate", G_CALLBACK (add_to_dictionary), entry); + + gtk_widget_show (submi); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), submi); + } + } + + 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), entry); + gtk_widget_show_all (mi); + gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi); + + return topmenu; +} + +static void +spell_entry_add_suggestions_menu (ESpellEntry *entry, + GtkMenu *menu, + const gchar *word) +{ + GtkWidget *icon, *mi; + + g_return_if_fail (menu != NULL); + g_return_if_fail (word != NULL); + + /* separator */ + mi = gtk_separator_menu_item_new (); + gtk_widget_show (mi); + gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi); + + /* Above the separator, show the suggestions menu */ + icon = 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), icon); + + gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), build_spelling_menu (entry, word)); + + gtk_widget_show_all (mi); + gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi); +} + +static gboolean +spell_entry_popup_menu (ESpellEntry *entry) +{ + /* Menu popped up from a keybinding (menu key or +F10). Use + * the cursor position as the mark position */ + entry->priv->mark_character = gtk_editable_get_position (GTK_EDITABLE (entry)); + + return FALSE; +} + +static void +spell_entry_populate_popup (ESpellEntry *entry, + GtkMenu *menu, + gpointer data) +{ + gint start, end; + gchar *word; + + if (!entry->priv->checkers) + return; + + get_word_extents_from_position (entry, &start, &end, entry->priv->mark_character); + if (start == end) + return; + + if (!word_misspelled (entry, start, end)) + return; + + word = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end); + g_return_if_fail (word != NULL); + + spell_entry_add_suggestions_menu (entry, menu, word); + + g_free (word); +} + +static void +spell_entry_changed (GtkEditable *editable) +{ + ESpellEntry *entry = E_SPELL_ENTRY (editable); + + if (!entry->priv->checkers) + return; + + if (entry->priv->words) { + g_strfreev (entry->priv->words); + g_free (entry->priv->word_starts); + g_free (entry->priv->word_ends); + } + entry_strsplit_utf8 (GTK_ENTRY (entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + spell_entry_recheck_all (entry); +} + +static void +spell_entry_notify_scroll_offset (ESpellEntry *spell_entry) +{ + g_return_if_fail (spell_entry != NULL); + + g_object_get (G_OBJECT (spell_entry), "scroll-offset", &spell_entry->priv->entry_scroll_offset, NULL); +} + +static GList * +spell_entry_load_spell_languages (void) +{ + GSettings *settings; + GList *spell_languages = NULL; + gchar **strv; + gint ii; + + /* Ask GSettings for a list of spell check language codes. */ + settings = g_settings_new ("org.gnome.evolution.mail"); + strv = g_settings_get_strv (settings, "composer-spell-languages"); + g_object_unref (settings); + + /* Convert the codes to spell language structs. */ + for (ii = 0; strv[ii] != NULL; ii++) { + gchar *language_code = strv[ii]; + const GtkhtmlSpellLanguage *language; + + language = gtkhtml_spell_language_lookup (language_code); + if (language != NULL) + spell_languages = g_list_prepend ( + spell_languages, (gpointer) language); + } + + g_strfreev (strv); + + spell_languages = g_list_reverse (spell_languages); + + /* Pick a default spell language if it came back empty. */ + if (spell_languages == NULL) { + const GtkhtmlSpellLanguage *language; + + language = gtkhtml_spell_language_lookup (NULL); + + if (language) { + spell_languages = g_list_prepend ( + spell_languages, (gpointer) language); + } + } + + return spell_languages; +} + +static void +spell_entry_settings_changed (ESpellEntry *spell_entry, + const gchar *key) +{ + GList *languages; + + g_return_if_fail (spell_entry != NULL); + + if (spell_entry->priv->custom_checkers) + return; + + if (key && !g_str_equal (key, "composer-spell-languages")) + return; + + languages = spell_entry_load_spell_languages (); + e_spell_entry_set_languages (spell_entry, languages); + g_list_free (languages); + + spell_entry->priv->custom_checkers = FALSE; +} + +static gint +spell_entry_find_position (ESpellEntry *spell_entry, + gint x) +{ + PangoLayout *layout; + PangoLayoutLine *line; + gint index; + gint pos; + gint trailing; + const gchar *text; + GtkEntry *entry = GTK_ENTRY (spell_entry); + + layout = gtk_entry_get_layout (entry); + text = pango_layout_get_text (layout); + + line = pango_layout_get_lines_readonly (layout)->data; + pango_layout_line_x_to_index (line, x * PANGO_SCALE, &index, &trailing); + + pos = g_utf8_pointer_to_offset (text, text + index); + pos += trailing; + + return pos; +} + +static gboolean +e_spell_entry_draw (GtkWidget *widget, + cairo_t *cr) +{ + ESpellEntry *spell_entry = E_SPELL_ENTRY (widget); + GtkEntry *entry = GTK_ENTRY (widget); + PangoLayout *layout; + + layout = gtk_entry_get_layout (entry); + pango_layout_set_attributes (layout, spell_entry->priv->attr_list); + + return GTK_WIDGET_CLASS (e_spell_entry_parent_class)->draw (widget, cr); +} + +static gboolean +e_spell_entry_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + ESpellEntry *spell_entry = E_SPELL_ENTRY (widget); + + spell_entry->priv->mark_character = spell_entry_find_position ( + spell_entry, event->x + spell_entry->priv->entry_scroll_offset); + + return GTK_WIDGET_CLASS (e_spell_entry_parent_class)->button_press_event (widget, event); +} + +static void +spell_entry_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CHECKING_ENABLED: + e_spell_entry_set_checking_enabled ( + E_SPELL_ENTRY (object), + g_value_get_boolean (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +spell_entry_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CHECKING_ENABLED: + g_value_set_boolean ( + value, + e_spell_entry_get_checking_enabled ( + E_SPELL_ENTRY (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +e_spell_entry_init (ESpellEntry *spell_entry) +{ + spell_entry->priv = E_SPELL_ENTRY_GET_PRIVATE (spell_entry); + spell_entry->priv->attr_list = pango_attr_list_new (); + spell_entry->priv->checkers = NULL; + spell_entry->priv->checking_enabled = TRUE; + + g_signal_connect (spell_entry, "popup-menu", G_CALLBACK (spell_entry_popup_menu), NULL); + g_signal_connect (spell_entry, "populate-popup", G_CALLBACK (spell_entry_populate_popup), NULL); + g_signal_connect (spell_entry, "changed", G_CALLBACK (spell_entry_changed), NULL); + g_signal_connect (spell_entry, "notify::scroll-offset", G_CALLBACK (spell_entry_notify_scroll_offset), NULL); + + /* listen for languages changes */ + spell_entry->priv->settings = g_settings_new ("org.gnome.evolution.mail"); + g_signal_connect_swapped (spell_entry->priv->settings, "changed", G_CALLBACK (spell_entry_settings_changed), spell_entry); + + /* load current settings */ + spell_entry_settings_changed (spell_entry, NULL); +} + +static void +e_spell_entry_finalize (GObject *object) +{ + ESpellEntry *entry; + + g_return_if_fail (object != NULL); + g_return_if_fail (E_IS_SPELL_ENTRY (object)); + + entry = E_SPELL_ENTRY (object); + + if (entry->priv->settings) + g_object_unref (entry->priv->settings); + if (entry->priv->checkers) + g_slist_free_full (entry->priv->checkers, g_object_unref); + if (entry->priv->attr_list) + pango_attr_list_unref (entry->priv->attr_list); + if (entry->priv->words) + g_strfreev (entry->priv->words); + if (entry->priv->word_starts) + g_free (entry->priv->word_starts); + if (entry->priv->word_ends) + g_free (entry->priv->word_ends); + + G_OBJECT_CLASS (e_spell_entry_parent_class)->finalize (object); +} + +static void +e_spell_entry_class_init (ESpellEntryClass *klass) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + + g_type_class_add_private (klass, sizeof (ESpellEntryPrivate)); + + object_class = G_OBJECT_CLASS (klass); + object_class->set_property = spell_entry_set_property; + object_class->get_property = spell_entry_get_property; + object_class->finalize = e_spell_entry_finalize; + + widget_class = GTK_WIDGET_CLASS (klass); + widget_class->draw = e_spell_entry_draw; + widget_class->button_press_event = e_spell_entry_button_press; + + g_object_class_install_property ( + object_class, + PROP_CHECKING_ENABLED, + g_param_spec_boolean ( + "checking-enabled", + "checking enabled", + "Spell Checking is Enabled", + TRUE, + G_PARAM_READWRITE)); +} + +GtkWidget * +e_spell_entry_new (void) +{ + return g_object_new (E_TYPE_SPELL_ENTRY, NULL); +} + +/* 'languages' consists of 'const GtkhtmlSpellLanguage *' */ +void +e_spell_entry_set_languages (ESpellEntry *spell_entry, + GList *languages) +{ + GList *iter; + + g_return_if_fail (spell_entry != NULL); + + spell_entry->priv->custom_checkers = TRUE; + + if (spell_entry->priv->checkers) + g_slist_free_full (spell_entry->priv->checkers, g_object_unref); + spell_entry->priv->checkers = NULL; + + for (iter = languages; iter; iter = g_list_next (iter)) { + const GtkhtmlSpellLanguage *language = iter->data; + + if (language) + spell_entry->priv->checkers = g_slist_prepend (spell_entry->priv->checkers, + gtkhtml_spell_checker_new (language)); + } + + spell_entry->priv->checkers = g_slist_reverse (spell_entry->priv->checkers); + + if (gtk_widget_get_realized (GTK_WIDGET (spell_entry))) + spell_entry_recheck_all (spell_entry); +} + +gboolean +e_spell_entry_get_checking_enabled (ESpellEntry *spell_entry) +{ + g_return_val_if_fail (spell_entry != NULL, FALSE); + + return spell_entry->priv->checking_enabled; +} + +void +e_spell_entry_set_checking_enabled (ESpellEntry *spell_entry, + gboolean enable_checking) +{ + g_return_if_fail (spell_entry != NULL); + + if ((enable_checking ? 1 : 0) == (spell_entry->priv->checking_enabled ? 1 : 0)) + return; + + spell_entry->priv->checking_enabled = enable_checking; + spell_entry_recheck_all (spell_entry); + + g_object_notify (G_OBJECT (spell_entry), "checking-enabled"); + +} diff --git a/widgets/misc/e-spell-entry.h b/widgets/misc/e-spell-entry.h new file mode 100644 index 0000000000..2d6aabad95 --- /dev/null +++ b/widgets/misc/e-spell-entry.h @@ -0,0 +1,59 @@ +/* + * e-spell-entry.h + * + * This program 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 of the License, or (at your option) version 3. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + */ + +#ifndef E_SPELL_ENTRY_H +#define E_SPELL_ENTRY_H + +#include + +#define E_TYPE_SPELL_ENTRY (e_spell_entry_get_type()) +#define E_SPELL_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), E_TYPE_SPELL_ENTRY, ESpellEntry)) +#define E_SPELL_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), E_TYPE_SPELL_ENTRY, ESpellEntryClass)) +#define E_IS_SPELL_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), E_TYPE_SPELL_ENTRY)) +#define E_IS_SPELL_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), E_TYPE_SPELL_ENTRY)) +#define E_SPELL_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), E_TYPE_SPELL_ENTRY, ESpellEntryClass)) + +G_BEGIN_DECLS + +typedef struct _ESpellEntry ESpellEntry; +typedef struct _ESpellEntryClass ESpellEntryClass; +typedef struct _ESpellEntryPrivate ESpellEntryPrivate; + +struct _ESpellEntry +{ + GtkEntry parent_object; + + ESpellEntryPrivate *priv; +}; + +struct _ESpellEntryClass +{ + GtkEntryClass parent_class; +}; + +GType e_spell_entry_get_type (void); +GtkWidget * e_spell_entry_new (void); +void e_spell_entry_set_languages (ESpellEntry *spell_entry, + GList *languages); +gboolean e_spell_entry_get_checking_enabled (ESpellEntry *spell_entry); +void e_spell_entry_set_checking_enabled (ESpellEntry *spell_entry, + gboolean enable_checking); + +G_END_DECLS + +#endif /* E_SPELL_ENTRY_H */ -- cgit v1.2.3