[evolution/webkit-composer: 45/231] Port spell-checking



commit 61c0bd2c8b00dcffce523066478538c182b2ce88
Author: Dan Vrátil <dvratil redhat com>
Date:   Thu Aug 16 15:14:58 2012 +0200

    Port spell-checking
    
    We use our own WebKitSpellChecker, which is in general the
    same as the WebKit's default, but it does not work with
    language codes, but directly with EnchantDicts. This allows
    us to remove ESpellDictionary and call enchant_ functions
    directly from EEditor* classes.
    
    FIXME: The spellchecking probably needs some polishing and lots
    of testing...

 configure.ac                         |   36 ++
 e-util/Makefile.am                   |    4 +
 e-util/e-editor-actions.c            |  162 +++-----
 e-util/e-editor-private.h            |    5 +
 e-util/e-editor-selection.c          |   79 ++++
 e-util/e-editor-selection.h          |    8 +
 e-util/e-editor-spell-check-dialog.c |  661 +++++++++++++++++++++++++++++
 e-util/e-editor-spell-check-dialog.h |   73 ++++
 e-util/e-editor-spell-checker.c      |  704 +++++++++++++++++++++++++++++++
 e-util/e-editor-spell-checker.h      |   85 ++++
 e-util/e-editor-widget.c             |    7 +
 e-util/e-editor.c                    |  442 ++++++++++++++++-----
 e-util/e-editor.h                    |    7 +
 e-util/e-spell-dialog.c              |  755 ----------------------------------
 e-util/e-spell-dialog.h              |   83 ----
 e-util/e-spell-dictionary.c          |  472 ---------------------
 e-util/e-spell-dictionary.h          |   53 ---
 e-util/e-util.h                      |    4 +-
 18 files changed, 2082 insertions(+), 1558 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 78a597f..13d7ab6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -511,6 +511,42 @@ dnl **************************************************
 PKG_CHECK_MODULES([GIT], [gnome-icon-theme >= gnome_icon_theme_minimum_version])
 
 dnl **************************************************
+dnl iso-codes
+dnl **************************************************
+AC_MSG_CHECKING([for iso-codes package])
+have_iso_codes=no
+if $PKG_CONFIG --exists iso-codes; then
+       if $PKG_CONFIG iso-codes --atleast-version=0.49; then
+               have_iso_codes=yes
+               AC_MSG_RESULT([$have_iso_codes])
+       else
+               AC_MSG_WARN([iso-codes detected, but version 0.49 or later is required due to licensing])
+       fi
+else
+       AC_MSG_RESULT([$have_iso_codes])
+fi
+
+if test "x$have_iso_codes" = "xyes"; then
+       AC_MSG_CHECKING([whether iso-codes has iso-639 and iso-3166 domains])
+       if $PKG_CONFIG --variable=domains iso-codes | grep 639 >/dev/null 2>&1 && \
+               $PKG_CONFIG --variable=domains iso-codes | grep 3166 >/dev/null 2>&1 ; then
+                       result=yes
+       else
+               result=no
+               have_iso_codes=no
+       fi
+       AC_MSG_RESULT([$result])
+fi
+
+if test "x$have_iso_codes" = "xyes"; then
+       AC_DEFINE_UNQUOTED([ISO_CODES_PREFIX],
+               ["`$PKG_CONFIG --variable=prefix iso-codes`"],
+               [ISO codes prefix])
+       AC_DEFINE([HAVE_ISO_CODES], [1],
+               [Define if you have the iso-codes package])
+fi
+
+dnl **************************************************
 dnl Accessibility support
 dnl **************************************************
 PKG_CHECK_MODULES([A11Y], [atk])
diff --git a/e-util/Makefile.am b/e-util/Makefile.am
index 9565191..d58c532 100644
--- a/e-util/Makefile.am
+++ b/e-util/Makefile.am
@@ -185,6 +185,8 @@ evolution_util_include_HEADERS =  \
        e-editor-paragraph-dialog.h \
        e-editor-replace-dialog.h \
        e-editor-selection.h \
+       e-editor-spell-check-dialog.h \
+       e-editor-spell-checker.h \
        e-editor-table-dialog.h \
        e-editor-text-dialog.h \
        e-editor-utils.h \
@@ -449,6 +451,8 @@ libevolution_util_la_SOURCES = \
        e-editor-private.h \
        e-editor-replace-dialog.c \
        e-editor-selection.c \
+       e-editor-spell-check-dialog.c \
+       e-editor-spell-checker.c \
        e-editor-table-dialog.c \
        e-editor-text-dialog.c \
        e-editor-utils.c \
diff --git a/e-util/e-editor-actions.c b/e-util/e-editor-actions.c
index cb216d0..4b147fe 100644
--- a/e-util/e-editor-actions.c
+++ b/e-util/e-editor-actions.c
@@ -15,8 +15,6 @@
  * Boston, MA 02111-1307, USA.
  */
 
-//#include "e-editor-private.h"
-
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
@@ -26,8 +24,9 @@
 #include <string.h>
 
 #include "e-editor.h"
-#include "e-editor-actions.h"
 #include "e-editor-private.h"
+#include "e-editor-actions.h"
+#include "e-editor-spell-checker.h"
 #include "e-editor-widgets.h"
 #include "e-editor-utils.h"
 #include "e-emoticon-action.h"
@@ -369,54 +368,34 @@ static void
 action_context_spell_add_cb (GtkAction *action,
                              EEditor *editor)
 {
-       WebKitDOMDocument *document;
-       WebKitDOMDOMWindow *window;
-       WebKitDOMDOMSelection *selection;
-       WebKitDOMRange *range;
        WebKitSpellChecker *spell_checker;
+       EEditorSelection *selection;
        gchar *word;
 
-       document = webkit_web_view_get_dom_document (
-                       WEBKIT_WEB_VIEW (e_editor_get_editor_widget (editor)));
-       window = webkit_dom_document_get_default_view (document);
-       selection = webkit_dom_dom_window_get_selection (window);
-       if (webkit_dom_dom_selection_get_range_count (selection) < 1)
-               return;
-
-       range = webkit_dom_dom_selection_get_range_at (selection, 0, NULL);
-       word = webkit_dom_range_get_text (range);
-
        spell_checker = WEBKIT_SPELL_CHECKER (webkit_get_text_checker ());
-       webkit_spell_checker_learn_word (spell_checker, word);
+       selection = e_editor_widget_get_selection (editor->priv->editor_widget);
 
-       g_free (word);
+       word = e_editor_selection_get_caret_word (selection);
+       if (word && *word) {
+               webkit_spell_checker_learn_word (spell_checker, word);
+       }
 }
 
 static void
 action_context_spell_ignore_cb (GtkAction *action,
                                 EEditor *editor)
 {
-       WebKitDOMDocument *document;
-       WebKitDOMDOMWindow *window;
-       WebKitDOMDOMSelection *selection;
-       WebKitDOMRange *range;
        WebKitSpellChecker *spell_checker;
+       EEditorSelection *selection;
        gchar *word;
 
-       document = webkit_web_view_get_dom_document (
-                       WEBKIT_WEB_VIEW (e_editor_get_editor_widget (editor)));
-       window = webkit_dom_document_get_default_view (document);
-       selection = webkit_dom_dom_window_get_selection (window);
-       if (webkit_dom_dom_selection_get_range_count (selection) < 1)
-               return;
-
-       range = webkit_dom_dom_selection_get_range_at (selection, 0, NULL);
-       word = webkit_dom_range_get_text (range);
-
        spell_checker = WEBKIT_SPELL_CHECKER (webkit_get_text_checker ());
-       webkit_spell_checker_ignore_word (spell_checker, word);
+       selection = e_editor_widget_get_selection (editor->priv->editor_widget);
 
-       g_free (word);
+       word = e_editor_selection_get_caret_word (selection);
+       if (word && *word) {
+               webkit_spell_checker_ignore_word (spell_checker, word);
+       }
 }
 
 static void
@@ -596,46 +575,40 @@ static void
 action_language_cb (GtkToggleAction *action,
                     EEditor *editor)
 {
-       /* FIXME WEBKIT */
-       /*
-       const GtkhtmlSpellLanguage *language;
-       GtkhtmlSpellChecker *checker;
+       EEditorSpellChecker *checker;
+       EnchantDict *dictionary;
        const gchar *language_code;
        GtkAction *add_action;
-       GtkHTML *html;
        GList *list;
        guint length;
        gchar *action_name;
        gboolean active;
 
+       checker = E_EDITOR_SPELL_CHECKER (webkit_get_text_checker ());
        active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
        language_code = gtk_action_get_name (GTK_ACTION (action));
-       language = gtkhtml_spell_language_lookup (language_code);
-
-       checker = g_hash_table_lookup (
-               editor->priv->available_spell_checkers, language);
-       g_return_if_fail (checker != NULL);
+       dictionary = e_editor_spell_checker_lookup_dict (checker, language_code);
 
-       //Update the list of active spell checkers.
-       list = editor->priv->active_spell_checkers;
+       /* Update the list of active dictionaries */
+       list = editor->priv->active_dictionaries;
        if (active)
                list = g_list_insert_sorted (
-                       list, g_object_ref (checker),
-                       (GCompareFunc) gtkhtml_spell_checker_compare);
+                       list, (EnchantDict *) dictionary,
+                       (GCompareFunc) e_editor_spell_checker_dict_compare);
        else {
                GList *link;
 
-               link = g_list_find (list, checker);
+               link = g_list_find (list, dictionary);
                g_return_if_fail (link != NULL);
+               e_editor_spell_checker_free_dict (checker, link->data);
                list = g_list_delete_link (list, link);
-               g_object_unref (checker);
        }
-       editor->priv->active_spell_checkers = list;
+       editor->priv->active_dictionaries = list;
        length = g_list_length (list);
 
-       // Update "Add Word To" context menu item visibility.
+       /* Update "Add Word To" context menu item visibility. */
        action_name = g_strdup_printf ("context-spell-add-%s", language_code);
-       add_action = gtkhtml_editor_get_action (editor, action_name);
+       add_action = e_editor_get_action (editor, action_name);
        gtk_action_set_visible (add_action, active);
        g_free (action_name);
 
@@ -645,11 +618,7 @@ action_language_cb (GtkToggleAction *action,
 
        gtk_action_set_sensitive (ACTION (SPELL_CHECK), length > 0);
 
-       html = gtkhtml_editor_get_html (editor);
-       html_engine_spell_check (html->engine);
-
-       gtkthtml_editor_emit_spell_languages_changed (editor);
-       */
+       e_editor_emit_spell_languages_changed (editor);
 }
 
 struct _ModeChanged {
@@ -935,9 +904,16 @@ static void
 action_spell_check_cb (GtkAction *action,
                        EEditor *editor)
 {
-       /* FIXME WEBKIT
-       e_editor_widget_spell_check (editor);
-       */
+       if (editor->priv->spell_check_dialog == NULL) {
+               editor->priv->spell_check_dialog =
+                       e_editor_spell_check_dialog_new (editor);
+
+               e_editor_spell_check_dialog_set_dictionaries (
+                       E_EDITOR_SPELL_CHECK_DIALOG (editor->priv->spell_check_dialog),
+                       editor->priv->active_dictionaries);
+       }
+
+       gtk_window_present (GTK_WINDOW (editor->priv->spell_check_dialog));
 }
 
 static void
@@ -1708,31 +1684,25 @@ static GtkActionEntry spell_context_entries[] = {
 static void
 editor_actions_setup_languages_menu (EEditor *editor)
 {
-       /* FIXME WEBKIT
+       EEditorSpellChecker *checker;
        GtkUIManager *manager;
        GtkActionGroup *action_group;
-       const GList *available_languages;
+       const GList *available_dicts;
        guint merge_id;
 
        manager = editor->priv->manager;
        action_group = editor->priv->language_actions;
-       available_languages = gtkhtml_spell_language_get_available ();
+       checker = E_EDITOR_SPELL_CHECKER (webkit_get_text_checker ());
+       available_dicts = e_editor_spell_checker_get_available_dicts (checker);
        merge_id = gtk_ui_manager_new_merge_id (manager);
 
-       while (available_languages != NULL) {
-               GtkhtmlSpellLanguage *language = available_languages->data;
-               GtkhtmlSpellChecker *checker;
+       while (available_dicts != NULL) {
+               EnchantDict *dictionary = available_dicts->data;
                GtkToggleAction *action;
 
-               checker = gtkhtml_spell_checker_new (language);
-
-               g_hash_table_insert (
-                       editor->priv->available_spell_checkers,
-                       language, checker);
-
                action = gtk_toggle_action_new (
-                       gtkhtml_spell_language_get_code (language),
-                       gtkhtml_spell_language_get_name (language),
+                       e_editor_spell_checker_get_dict_code (dictionary),
+                       e_editor_spell_checker_get_dict_name (dictionary),
                        NULL, NULL);
 
                g_signal_connect (
@@ -1747,42 +1717,41 @@ editor_actions_setup_languages_menu (EEditor *editor)
                gtk_ui_manager_add_ui (
                        manager, merge_id,
                        "/main-menu/edit-menu/language-menu",
-                       gtkhtml_spell_language_get_code (language),
-                       gtkhtml_spell_language_get_code (language),
+                       e_editor_spell_checker_get_dict_code (dictionary),
+                       e_editor_spell_checker_get_dict_code (dictionary),
                        GTK_UI_MANAGER_AUTO, FALSE);
 
-               available_languages = g_list_next (available_languages);
+               available_dicts = g_list_next (available_dicts);
        }
-       */
 }
 
 static void
 editor_actions_setup_spell_check_menu (EEditor *editor)
 {
-       /*
+       EEditorSpellChecker *checker;
        GtkUIManager *manager;
        GtkActionGroup *action_group;
-       const GList *available_languages;
+       const GList *available_dicts;
        guint merge_id;
 
        manager = editor->priv->manager;
        action_group = editor->priv->spell_check_actions;;
-       available_languages = gtkhtml_spell_language_get_available ();
+       checker = E_EDITOR_SPELL_CHECKER (webkit_get_text_checker ());
+       available_dicts = e_editor_spell_checker_get_available_dicts (checker);
        merge_id = gtk_ui_manager_new_merge_id (manager);
 
-       while (available_languages != NULL) {
-               GtkhtmlSpellLanguage *language = available_languages->data;
+       while (available_dicts != NULL) {
+               EnchantDict *dictionary = available_dicts->data;
                GtkAction *action;
                const gchar *code;
                const gchar *name;
                gchar *action_label;
                gchar *action_name;
 
-               code = gtkhtml_spell_language_get_code (language);
-               name = gtkhtml_spell_language_get_name (language);
-
-               // Add a suggestion menu. 
+               code = e_editor_spell_checker_get_dict_code (dictionary);
+               name = e_editor_spell_checker_get_dict_name (dictionary);
 
+               /* Add a suggestion menu. */
                action_name = g_strdup_printf (
                        "context-spell-suggest-%s-menu", code);
 
@@ -1798,11 +1767,11 @@ editor_actions_setup_spell_check_menu (EEditor *editor)
 
                g_free (action_name);
 
-               // Add an item to the "Add Word To" menu.
-
+               /* Add an item to the "Add Word To" menu. */
                action_name = g_strdup_printf ("context-spell-add-%s", code);
-               // Translators: %s will be replaced with the actual dictionary name,
-               //where a user can add a word to. This is part of an "Add Word To" submenu.
+               /* Translators: %s will be replaced with the actual dictionary
+                * name, where a user can add a word to. This is part of an
+                * "Add Word To" submenu. */
                action_label = g_strdup_printf (_("%s Dictionary"), name);
 
                action = gtk_action_new (
@@ -1812,8 +1781,8 @@ editor_actions_setup_spell_check_menu (EEditor *editor)
                        action, "activate",
                        G_CALLBACK (action_context_spell_add_cb), editor);
 
-               // Visibility is dependent on whether the
-               //corresponding language action is active.
+               /* Visibility is dependent on whether the
+                  corresponding language action is active. */
                gtk_action_set_visible (action, FALSE);
 
                gtk_action_group_add_action (action_group, action);
@@ -1829,9 +1798,8 @@ editor_actions_setup_spell_check_menu (EEditor *editor)
                g_free (action_label);
                g_free (action_name);
 
-               available_languages = g_list_next (available_languages);
+               available_dicts = g_list_next (available_dicts);
        }
-       */
 }
 
 void
diff --git a/e-util/e-editor-private.h b/e-util/e-editor-private.h
index 6a47fdb..df0d166 100644
--- a/e-util/e-editor-private.h
+++ b/e-util/e-editor-private.h
@@ -34,6 +34,7 @@
 #include <e-editor-text-dialog.h>
 #include <e-editor-paragraph-dialog.h>
 #include <e-editor-cell-dialog.h>
+#include <e-editor-spell-check-dialog.h>
 
 #ifdef HAVE_XFREE
 #include <X11/XF86keysym.h>
@@ -72,6 +73,7 @@ struct _EEditorPrivate {
        GtkWidget *text_dialog;
        GtkWidget *paragraph_dialog;
        GtkWidget *cell_dialog;
+       GtkWidget *spell_check_dialog;
 
        GtkWidget *color_combo_box;
        GtkWidget *mode_combo_box;
@@ -84,6 +86,9 @@ struct _EEditorPrivate {
 
        gchar *filename;
 
+       guint spell_suggestions_merge_id;
+       GList *active_dictionaries;
+
        WebKitDOMNode *image;
        WebKitDOMNode *table_cell;
 };
diff --git a/e-util/e-editor-selection.c b/e-util/e-editor-selection.c
index 710b914..d0aa6ab 100644
--- a/e-util/e-editor-selection.c
+++ b/e-util/e-editor-selection.c
@@ -585,6 +585,85 @@ e_editor_selection_new (WebKitWebView *parent_view)
                        "webview", parent_view, NULL);
 }
 
+gboolean
+e_editor_selection_has_text (EEditorSelection *selection)
+{
+       WebKitDOMRange *range;
+       WebKitDOMNode *node;
+
+       g_return_val_if_fail (E_IS_EDITOR_SELECTION (selection), FALSE);
+
+       range = editor_selection_get_current_range (selection);
+
+       node = webkit_dom_range_get_start_container (range, NULL);
+       if (webkit_dom_node_get_node_type (node) == 3) {
+               return TRUE;
+       }
+
+       node = webkit_dom_range_get_end_container (range, NULL);
+       if (webkit_dom_node_get_node_type (node) == 3) {
+               return TRUE;
+       }
+
+       node = WEBKIT_DOM_NODE (webkit_dom_range_clone_contents (range, NULL));
+       while (node) {
+               if (webkit_dom_node_get_node_type (node) == 3) {
+                       return TRUE;
+               }
+
+               if (webkit_dom_node_has_child_nodes (node)) {
+                       node = webkit_dom_node_get_first_child (node);
+               } else if (webkit_dom_node_get_next_sibling (node)) {
+                       node = webkit_dom_node_get_next_sibling (node);
+               } else {
+                       node = webkit_dom_node_get_parent_node (node);
+                       if (node) {
+                               node = webkit_dom_node_get_next_sibling (node);
+                       }
+               }
+       }
+
+       return FALSE;
+}
+
+gchar *
+e_editor_selection_get_caret_word (EEditorSelection *selection)
+{
+       WebKitDOMRange *range;
+
+       g_return_val_if_fail (E_IS_EDITOR_SELECTION (selection), NULL);
+
+       range = editor_selection_get_current_range (selection);
+
+       /* Don't operate on the visible selection */
+       range = webkit_dom_range_clone_range (range, NULL);
+       webkit_dom_range_expand (range, "word", NULL);
+
+       return webkit_dom_range_to_string (range, NULL);
+}
+
+void
+e_editor_selection_replace_caret_word (EEditorSelection *selection,
+                                      const gchar *replacement)
+{
+       WebKitDOMDocument *document;
+       WebKitDOMDOMWindow *window;
+       WebKitDOMDOMSelection *dom_selection;
+       WebKitDOMRange *range;
+
+       g_return_if_fail (E_IS_EDITOR_SELECTION (selection));
+       g_return_if_fail (replacement);
+
+       range = editor_selection_get_current_range (selection);
+       document = webkit_web_view_get_dom_document (selection->priv->webview);
+       window = webkit_dom_document_get_default_view (document);
+       dom_selection = webkit_dom_dom_window_get_selection (window);
+
+       webkit_dom_range_expand (range, "word", NULL);
+       webkit_dom_dom_selection_add_range (dom_selection, range);
+
+       e_editor_selection_insert_html (selection, replacement);
+}
 
 const gchar *
 e_editor_selection_get_string (EEditorSelection *selection)
diff --git a/e-util/e-editor-selection.h b/e-util/e-editor-selection.h
index 3798d30..1316e90 100644
--- a/e-util/e-editor-selection.h
+++ b/e-util/e-editor-selection.h
@@ -99,6 +99,14 @@ GType                        e_editor_selection_get_type     (void);
 
 EEditorSelection *     e_editor_selection_new          (WebKitWebView *parent_view);
 
+gboolean               e_editor_selection_has_text     (EEditorSelection *selection);
+
+gchar *                        e_editor_selection_get_caret_word
+                                                       (EEditorSelection *selection);
+void                   e_editor_selection_replace_caret_word
+                                                       (EEditorSelection *selection,
+                                                        const gchar *replacement);
+
 void                   e_editor_selection_set_alignment
                                                        (EEditorSelection *selection,
                                                         EEditorSelectionAlignment alignment);
diff --git a/e-util/e-editor-spell-check-dialog.c b/e-util/e-editor-spell-check-dialog.c
new file mode 100644
index 0000000..f2c8714
--- /dev/null
+++ b/e-util/e-editor-spell-check-dialog.c
@@ -0,0 +1,661 @@
+/* e-editor-spell-dialog.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-editor-spell-check-dialog.h"
+
+#include <glib/gi18n-lib.h>
+#include <enchant/enchant.h>
+
+#include "e-editor-spell-checker.h"
+#include "e-editor-widget.h"
+
+G_DEFINE_TYPE (
+       EEditorSpellCheckDialog,
+       e_editor_spell_check_dialog,
+       E_TYPE_EDITOR_DIALOG
+);
+
+enum {
+       COMBO_COLUMN_DICTIONARY,        /* E_TYPE_SPELL_DICTIONARY */
+       COMBO_COLUMN_TEXT               /* G_TYPE_STRING */
+};
+
+struct _EEditorSpellCheckDialogPrivate {
+       GtkWidget *add_word_button;
+       GtkWidget *back_button;
+       GtkWidget *dictionary_combo;
+       GtkWidget *ignore_button;
+       GtkWidget *replace_button;
+       GtkWidget *replace_all_button;
+       GtkWidget *skip_button;
+       GtkWidget *suggestion_label;
+       GtkWidget *tree_view;
+       GtkWidget *close_button;
+
+       GList *dictionaries;
+       WebKitDOMDOMSelection *selection;
+
+       gchar *word;
+       EnchantDict *current_dict;
+};
+
+static void
+editor_spell_check_dialog_set_word (EEditorSpellCheckDialog *dialog,
+                                   const gchar *word)
+{
+       GtkListStore *store;
+       gchar *markup;
+       gchar **suggestions;
+       gint ii;
+
+       if (word == NULL) {
+               return;
+       }
+
+       if (dialog->priv->word != word) {
+               g_free (dialog->priv->word);
+               dialog->priv->word = g_strdup (word);
+       }
+
+       markup = g_strdup_printf (_("<b>Suggestions for '%s'</b>"), word);
+       gtk_label_set_markup (
+               GTK_LABEL (dialog->priv->suggestion_label), markup);
+       g_free (markup);
+
+       store = GTK_LIST_STORE (
+                       gtk_tree_view_get_model (
+                               GTK_TREE_VIEW (dialog->priv->tree_view)));
+       gtk_list_store_clear (store);
+
+       suggestions = enchant_dict_suggest (
+                       dialog->priv->current_dict, word, -1, NULL);
+       for (ii = 0; suggestions && suggestions[ii]; ii++) {
+               GtkTreeIter iter;
+               gchar *suggestion = suggestions[ii];
+
+               gtk_list_store_append (store, &iter);
+               gtk_list_store_set (store, &iter, 0, suggestion, -1);
+       }
+
+       g_strfreev (suggestions);
+}
+
+static gboolean
+select_next_word (EEditorSpellCheckDialog *dialog)
+{
+       WebKitDOMNode *anchor;
+       gulong anchor_offset;
+
+       anchor = webkit_dom_dom_selection_get_anchor_node (dialog->priv->selection);
+       anchor_offset = webkit_dom_dom_selection_get_anchor_offset (dialog->priv->selection);
+
+       /* Jump _behind_ next word */
+       webkit_dom_dom_selection_modify (
+               dialog->priv->selection, "move", "forward", "word");
+       /* Jump before the word */
+       webkit_dom_dom_selection_modify (
+               dialog->priv->selection, "move", "backward", "word");
+       /* Select it */
+       webkit_dom_dom_selection_modify (
+               dialog->priv->selection, "extend", "forward", "word");
+
+       /* If the selection start didn't change, then we have most probably
+        * reached the end of document - return FALSE */
+       return ((anchor != webkit_dom_dom_selection_get_anchor_node (
+                               dialog->priv->selection)) ||
+               (anchor_offset != webkit_dom_dom_selection_get_anchor_offset (
+                               dialog->priv->selection)));
+}
+
+
+static void
+editor_spell_check_dialog_next (EEditorSpellCheckDialog *dialog)
+{
+       WebKitDOMNode *start = NULL, *end = NULL;
+       gulong start_offset, end_offset;
+
+       if (dialog->priv->word == NULL) {
+               webkit_dom_dom_selection_modify (
+                       dialog->priv->selection, "move", "left", "documentboundary");
+       } else {
+               /* Remember last selected word */
+               start = webkit_dom_dom_selection_get_anchor_node (
+                                       dialog->priv->selection);
+               end = webkit_dom_dom_selection_get_focus_node (
+                                       dialog->priv->selection);
+               start_offset = webkit_dom_dom_selection_get_anchor_offset (
+                                       dialog->priv->selection);
+               end_offset = webkit_dom_dom_selection_get_focus_offset (
+                                       dialog->priv->selection);
+       }
+
+       while (select_next_word (dialog)) {
+               WebKitDOMRange *range;
+               WebKitSpellChecker *checker;
+               gint loc, len;
+               gchar *word;
+
+               range = webkit_dom_dom_selection_get_range_at (dialog->priv->selection, 0, NULL);
+               word = webkit_dom_range_get_text (range);
+
+               checker = WEBKIT_SPELL_CHECKER (webkit_get_text_checker ());
+               webkit_spell_checker_check_spelling_of_string (
+                       checker, word, &loc, &len);
+
+               /* Found misspelled word! */
+               if (loc != -1) {
+                       editor_spell_check_dialog_set_word (dialog, word);
+                       g_free (word);
+                       return;
+               }
+
+               g_free (word);
+       }
+
+       /* Restore the selection to contain the last misspelled word. This is
+        * reached only when we reach the end of the document */
+       if (start && end) {
+               webkit_dom_dom_selection_set_base_and_extent (
+                       dialog->priv->selection, start, start_offset,
+                       end, end_offset, NULL);
+       }
+
+       /* Close the dialog */
+       gtk_widget_hide (GTK_WIDGET (dialog));
+}
+
+static gboolean
+select_previous_word (EEditorSpellCheckDialog *dialog)
+{
+       WebKitDOMNode *anchor;
+       gulong anchor_offset;
+
+       anchor = webkit_dom_dom_selection_get_anchor_node (dialog->priv->selection);
+       anchor_offset = webkit_dom_dom_selection_get_anchor_offset (dialog->priv->selection);
+
+       /* Jump on the beginning of current word */
+       webkit_dom_dom_selection_modify (
+               dialog->priv->selection, "move", "backward", "word");
+       /* Jump before previous word */
+       webkit_dom_dom_selection_modify (
+               dialog->priv->selection, "move", "backward", "word");
+       /* Select it */
+       webkit_dom_dom_selection_modify (
+               dialog->priv->selection, "extend", "forward", "word");
+
+       /* If the selection start didn't change, then we have most probably
+        * reached the beginnig of document. Return FALSE */
+       return ((anchor != webkit_dom_dom_selection_get_anchor_node (
+                               dialog->priv->selection)) ||
+               (anchor_offset != webkit_dom_dom_selection_get_anchor_offset (
+                               dialog->priv->selection)));
+}
+
+static void
+editor_spell_check_dialog_prev (EEditorSpellCheckDialog *dialog)
+{
+       WebKitDOMNode *start = NULL, *end = NULL;
+       gulong start_offset, end_offset;        
+
+       if (dialog->priv->word == NULL) {
+               webkit_dom_dom_selection_modify (
+                       dialog->priv->selection, "move", "right", "documentboundary");
+               webkit_dom_dom_selection_modify (
+                       dialog->priv->selection, "extend", "backward", "word");
+       } else {
+               /* Remember last selected word */
+               start = webkit_dom_dom_selection_get_anchor_node (
+                                       dialog->priv->selection);
+               end = webkit_dom_dom_selection_get_focus_node (
+                                       dialog->priv->selection);
+               start_offset = webkit_dom_dom_selection_get_anchor_offset (
+                                       dialog->priv->selection);
+               end_offset = webkit_dom_dom_selection_get_focus_offset (
+                                       dialog->priv->selection);
+       }
+
+       while (select_previous_word (dialog)) {
+               WebKitDOMRange *range;
+               WebKitSpellChecker *checker;
+               gint loc, len;
+               gchar *word;
+
+               range = webkit_dom_dom_selection_get_range_at (dialog->priv->selection, 0, NULL);
+               word = webkit_dom_range_get_text (range);
+
+               checker = WEBKIT_SPELL_CHECKER (webkit_get_text_checker ());
+               webkit_spell_checker_check_spelling_of_string (
+                               checker, word, &loc, &len);
+
+               /* Found misspelled word! */
+               if (loc != -1) {
+                       editor_spell_check_dialog_set_word (dialog, word);
+                       g_free (word);
+                       return;
+               }
+
+               g_free (word);
+       }
+
+       /* Restore the selection to contain the last misspelled word. This is
+        * reached only when we reach the beginning of the document */
+       if (start && end) {
+               webkit_dom_dom_selection_set_base_and_extent (
+                       dialog->priv->selection, start, start_offset,
+                       end, end_offset, NULL);
+       }
+
+       /* Close the dialog */
+       gtk_widget_hide (GTK_WIDGET (dialog));
+}
+
+static void
+editor_spell_check_dialog_replace (EEditorSpellCheckDialog *dialog)
+{
+       EEditor *editor;
+       EEditorWidget *widget;
+       EEditorSelection *editor_selection;
+       GtkTreeModel *model;
+       GtkTreeSelection *selection;
+       GtkTreeIter iter;
+       gchar *replacement;
+
+       editor = e_editor_dialog_get_editor (E_EDITOR_DIALOG (dialog));
+       widget = e_editor_get_editor_widget (editor);
+       editor_selection = e_editor_widget_get_selection (widget);
+
+       selection = gtk_tree_view_get_selection (
+                       GTK_TREE_VIEW (dialog->priv->tree_view));
+       gtk_tree_selection_get_selected (selection, &model, &iter);
+       gtk_tree_model_get (model, &iter, 0, &replacement, -1);
+
+       e_editor_selection_insert_html (
+               editor_selection, replacement);
+
+       g_free (replacement);
+       editor_spell_check_dialog_next (dialog);
+}
+
+static void
+editor_spell_check_dialog_replace_all (EEditorSpellCheckDialog *dialog)
+{
+       EEditor *editor;
+       EEditorWidget *widget;
+       EEditorSelection *editor_selection;
+       GtkTreeModel *model;
+       GtkTreeSelection *selection;
+       GtkTreeIter iter;
+       gchar *replacement;
+
+       editor = e_editor_dialog_get_editor (E_EDITOR_DIALOG (dialog));
+       widget = e_editor_get_editor_widget (editor);
+       editor_selection = e_editor_widget_get_selection (widget);
+
+       selection = gtk_tree_view_get_selection (
+                       GTK_TREE_VIEW (dialog->priv->tree_view));
+       gtk_tree_selection_get_selected (selection, &model, &iter);
+       gtk_tree_model_get (model, &iter, 0, &replacement, -1);
+
+       /* Repeatedly search for 'word', then replace selection by
+        * 'replacement'. Repeat until there's at least one occurence of
+        * 'word' in the document */
+       while (webkit_web_view_search_text (
+                       WEBKIT_WEB_VIEW (widget), dialog->priv->word,
+                       FALSE, TRUE, TRUE)) {
+
+               e_editor_selection_insert_html (
+                       editor_selection, replacement);
+
+       }
+
+       g_free (replacement);
+       editor_spell_check_dialog_next (dialog);
+}
+
+static void
+editor_spell_check_dialog_ignore (EEditorSpellCheckDialog *dialog)
+{
+       if (dialog->priv->word == NULL) {
+               return;
+       }
+
+       enchant_dict_add_to_session (
+               dialog->priv->current_dict, dialog->priv->word, -1);
+
+       editor_spell_check_dialog_next (dialog);
+}
+
+static void
+editor_spell_check_dialog_learn (EEditorSpellCheckDialog *dialog)
+{
+       if (dialog->priv->word == NULL) {
+               return;
+       }
+
+       enchant_dict_add_to_personal (
+               dialog->priv->current_dict, dialog->priv->word, -1);
+
+       editor_spell_check_dialog_next (dialog);
+}
+
+static void
+editor_spell_check_dialog_set_dictionary (EEditorSpellCheckDialog *dialog)
+{
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+       EnchantDict *dictionary;
+
+       gtk_combo_box_get_active_iter (
+                       GTK_COMBO_BOX (dialog->priv->dictionary_combo), &iter);
+       model = gtk_combo_box_get_model (
+                       GTK_COMBO_BOX (dialog->priv->dictionary_combo));
+
+       gtk_tree_model_get (model, &iter, 1, &dictionary, -1);
+
+       dialog->priv->current_dict = dictionary;
+
+       /* Update suggestions */
+       editor_spell_check_dialog_set_word (dialog, dialog->priv->word);
+}
+
+static void
+editor_spell_check_dialog_show (GtkWidget *gtk_widget)
+{
+       EEditorSpellCheckDialog *dialog;
+       EEditor *editor;
+       EEditorWidget *widget;
+       WebKitDOMDocument *document;
+       WebKitDOMDOMWindow *window;
+
+       dialog = E_EDITOR_SPELL_CHECK_DIALOG (gtk_widget);
+       editor = e_editor_dialog_get_editor (E_EDITOR_DIALOG (dialog));
+       widget = e_editor_get_editor_widget (editor);
+
+       g_free (dialog->priv->word);
+       dialog->priv->word = NULL;
+
+       document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (widget));
+       window = webkit_dom_document_get_default_view (document);
+       dialog->priv->selection = webkit_dom_dom_window_get_selection (window);
+
+       /* Select the first word */
+       editor_spell_check_dialog_next (dialog);
+
+       GTK_WIDGET_CLASS (e_editor_spell_check_dialog_parent_class)->show (gtk_widget);
+}
+
+static void
+editor_spell_check_dialog_finalize (GObject *object)
+{
+       EEditorSpellCheckDialog *dialog;
+
+       dialog = E_EDITOR_SPELL_CHECK_DIALOG (object);
+
+       g_free (dialog->priv->word);
+       dialog->priv->word = NULL;
+}
+
+static void
+e_editor_spell_check_dialog_class_init (EEditorSpellCheckDialogClass *klass)
+{
+       GtkWidgetClass *widget_class;
+       GObjectClass *object_class;
+
+       e_editor_spell_check_dialog_parent_class = g_type_class_peek_parent (klass);
+       g_type_class_add_private (klass, sizeof (EEditorSpellCheckDialogPrivate));
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->finalize = editor_spell_check_dialog_finalize;
+
+       widget_class = GTK_WIDGET_CLASS (klass);
+       widget_class->show = editor_spell_check_dialog_show;
+}
+
+static void
+e_editor_spell_check_dialog_init (EEditorSpellCheckDialog *dialog)
+{
+       GtkWidget *widget;
+       GtkGrid *main_layout;
+       GtkListStore *store;
+       GtkTreeViewColumn *column;
+       GtkCellRenderer *renderer;
+
+       dialog->priv = G_TYPE_INSTANCE_GET_PRIVATE (
+               dialog, E_TYPE_EDITOR_SPELL_CHECK_DIALOG, EEditorSpellCheckDialogPrivate);
+
+       main_layout = GTK_GRID (gtk_grid_new ());
+       gtk_grid_set_row_spacing (main_layout, 10);
+       gtk_grid_set_column_spacing (main_layout, 10);
+       gtk_container_add (GTK_CONTAINER (dialog), GTK_WIDGET (main_layout));
+       gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
+
+       /* == Suggestions == */
+       widget = gtk_label_new ("");
+       gtk_label_set_markup (GTK_LABEL (widget), _("<b>Suggestions</b>"));
+       gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+       gtk_grid_attach (main_layout, widget, 0, 0, 2, 1);
+       dialog->priv->suggestion_label = widget;
+
+       /* Tree view */
+       widget = gtk_tree_view_new ();
+       gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (widget), FALSE);
+       gtk_widget_set_vexpand (widget, TRUE);
+       gtk_widget_set_hexpand (widget, TRUE);
+       dialog->priv->tree_view = widget;
+
+       /* Column */
+       column = gtk_tree_view_column_new_with_attributes (
+                       "", gtk_cell_renderer_text_new (), "text", 0, NULL);
+       gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column);
+
+       /* Store */
+       store = gtk_list_store_new (1, G_TYPE_STRING);
+       gtk_tree_view_set_model (
+               GTK_TREE_VIEW (widget), GTK_TREE_MODEL (store));
+
+       /* Scrolled Window */
+       widget = gtk_scrolled_window_new (NULL, NULL);
+       gtk_widget_set_size_request (widget, 150, -1);
+       gtk_scrolled_window_set_policy (
+               GTK_SCROLLED_WINDOW (widget),
+               GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+       gtk_scrolled_window_set_shadow_type (
+               GTK_SCROLLED_WINDOW (widget),
+               GTK_SHADOW_ETCHED_IN);
+       gtk_container_add (GTK_CONTAINER (widget), dialog->priv->tree_view);
+       gtk_grid_attach (main_layout, widget, 0, 1, 1, 5);
+
+       /* Replace */
+       widget = gtk_button_new_with_mnemonic (_("Replace"));
+       gtk_button_set_image (
+               GTK_BUTTON (widget),
+               gtk_image_new_from_stock (
+                       GTK_STOCK_CONVERT, GTK_ICON_SIZE_BUTTON));
+       gtk_grid_attach (main_layout, widget, 1, 1, 1, 1);
+       dialog->priv->replace_button = widget;
+
+       g_signal_connect_swapped (
+               widget, "clicked",
+               G_CALLBACK (editor_spell_check_dialog_replace), dialog);
+
+       /* Replace All */
+       widget = gtk_button_new_with_mnemonic (_("Replace All"));
+       gtk_button_set_image (
+               GTK_BUTTON (widget),
+               gtk_image_new_from_stock (
+                       GTK_STOCK_APPLY, GTK_ICON_SIZE_BUTTON));
+       gtk_grid_attach (main_layout, widget, 1, 2, 1, 1);
+       dialog->priv->replace_all_button = widget;
+
+       g_signal_connect_swapped (
+               widget, "clicked",
+               G_CALLBACK (editor_spell_check_dialog_replace_all), dialog);
+
+       /* Ignore */
+       widget = gtk_button_new_with_mnemonic (_("Ignore"));
+       gtk_button_set_image (
+               GTK_BUTTON (widget),
+               gtk_image_new_from_stock (
+                       GTK_STOCK_CLEAR, GTK_ICON_SIZE_BUTTON));
+       gtk_grid_attach (main_layout, widget, 1, 3, 1, 1);
+       dialog->priv->ignore_button = widget;
+
+       g_signal_connect_swapped (
+               widget, "clicked",
+               G_CALLBACK (editor_spell_check_dialog_ignore), dialog);
+
+       /* Skip */
+       widget = gtk_button_new_with_mnemonic (_("Skip"));
+       gtk_button_set_image (
+               GTK_BUTTON (widget),
+               gtk_image_new_from_stock (
+                       GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_BUTTON));
+       gtk_grid_attach (main_layout, widget, 1, 4, 1, 1);
+       dialog->priv->skip_button = widget;
+
+       g_signal_connect_swapped (
+               widget, "clicked",
+               G_CALLBACK (editor_spell_check_dialog_next), dialog);
+
+       /* Back */
+       widget = gtk_button_new_with_mnemonic (_("Back"));
+       gtk_button_set_image (
+               GTK_BUTTON (widget),
+               gtk_image_new_from_stock (
+                       GTK_STOCK_GO_BACK, GTK_ICON_SIZE_BUTTON));
+       gtk_grid_attach (main_layout, widget, 1, 5, 1, 1);
+
+       g_signal_connect_swapped (
+               widget, "clicked",
+               G_CALLBACK (editor_spell_check_dialog_prev), dialog);
+
+       /* Dictionary label */
+       widget = gtk_label_new ("");
+       gtk_label_set_markup (GTK_LABEL (widget), _("<b>Dictionary</b>"));
+       gtk_misc_set_alignment (GTK_MISC (widget), 0, 0);
+       gtk_grid_attach (main_layout, widget, 0, 6, 2, 1);
+
+       /* Dictionaries combo */
+       widget = gtk_combo_box_new ();
+       gtk_grid_attach (main_layout, widget, 0, 7, 1, 1);
+       dialog->priv->dictionary_combo = widget;
+
+       renderer = gtk_cell_renderer_text_new ();
+       gtk_cell_layout_pack_start (
+               GTK_CELL_LAYOUT (widget), renderer, TRUE);
+       gtk_cell_layout_add_attribute (
+               GTK_CELL_LAYOUT (widget), renderer, "text", 0);
+
+       g_signal_connect_swapped (
+               widget, "changed",
+               G_CALLBACK (editor_spell_check_dialog_set_dictionary), dialog);
+
+       /* Add Word button */
+       widget = gtk_button_new_with_mnemonic (_("Add Word"));
+       gtk_button_set_image (
+               GTK_BUTTON (widget),
+               gtk_image_new_from_stock (
+                       GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON));
+       gtk_grid_attach (main_layout, widget, 1, 7, 1, 1);
+       dialog->priv->add_word_button = widget;
+
+       g_signal_connect_swapped (
+               widget, "clicked",
+               G_CALLBACK (editor_spell_check_dialog_learn), dialog);
+
+       /* Close Button */
+       widget = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
+       dialog->priv->close_button = widget;
+       g_signal_connect_swapped (
+               widget, "clicked",
+               G_CALLBACK (gtk_widget_hide), dialog);
+
+       /* Button box */
+       widget = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
+       gtk_button_box_set_layout (GTK_BUTTON_BOX (widget), GTK_BUTTONBOX_END);
+       gtk_grid_attach (main_layout, widget, 0, 8, 2, 1);
+       gtk_box_pack_start (GTK_BOX (widget), dialog->priv->close_button, FALSE, FALSE, 5);
+
+       gtk_widget_show_all (GTK_WIDGET (main_layout));
+}
+
+GtkWidget *
+e_editor_spell_check_dialog_new (EEditor *editor)
+{
+       return GTK_WIDGET (
+               g_object_new (
+                       E_TYPE_EDITOR_SPELL_CHECK_DIALOG,
+                       "editor", editor,
+                       "title", N_("Spell Checking"),
+                       NULL));
+}
+
+GList *
+e_editor_spell_check_dialog_get_dictionaries (EEditorSpellCheckDialog *dialog)
+{
+       g_return_val_if_fail (E_IS_EDITOR_SPELL_CHECK_DIALOG (dialog), NULL);
+
+       return g_list_copy (dialog->priv->dictionaries);
+}
+
+void
+e_editor_spell_check_dialog_set_dictionaries (EEditorSpellCheckDialog *dialog,
+                                             GList *dictionaries)
+{
+       GtkComboBox *combo_box;
+       GtkListStore *store;
+       GList *list;
+
+       g_return_if_fail (E_IS_EDITOR_SPELL_CHECK_DIALOG (dialog));
+
+       combo_box = GTK_COMBO_BOX (dialog->priv->dictionary_combo);
+
+       /* Free the old list of spell checkers. */
+       g_list_free (dialog->priv->dictionaries);
+
+       /* Copy and sort the new list of spell checkers. */
+       list = g_list_sort (
+               g_list_copy (dictionaries),
+               (GCompareFunc) e_editor_spell_checker_dict_compare);
+       dialog->priv->dictionaries = list;
+
+       /* Populate a list store for the combo box. */
+       store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
+
+       while (list != NULL) {
+               EnchantDict *dictionary = list->data;
+               GtkTreeIter iter;
+
+               gtk_list_store_append (store, &iter);
+               gtk_list_store_set (
+                       store, &iter,
+                       0, e_editor_spell_checker_get_dict_name (dictionary),
+                       1, dictionary, -1);
+
+               list = g_list_next (list);
+       }
+
+       /* FIXME: Try to restore selection */
+       gtk_combo_box_set_model (combo_box, GTK_TREE_MODEL (store));
+       gtk_combo_box_set_active (combo_box, 0);
+
+       g_object_unref (store);
+}
diff --git a/e-util/e-editor-spell-check-dialog.h b/e-util/e-editor-spell-check-dialog.h
new file mode 100644
index 0000000..624ff72
--- /dev/null
+++ b/e-util/e-editor-spell-check-dialog.h
@@ -0,0 +1,73 @@
+/* e-editor-spell-check-dialog.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_EDITOR_SPELL_CHECK_DIALOG_H
+#define E_EDITOR_SPELL_CHECK_DIALOG_H
+
+#include <e-util/e-editor-dialog.h>
+
+/* Standard GObject macros */
+#define E_TYPE_EDITOR_SPELL_CHECK_DIALOG \
+       (e_editor_spell_check_dialog_get_type ())
+#define E_EDITOR_SPELL_CHECK_DIALOG(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_EDITOR_SPELL_CHECK_DIALOG, EEditorSpellCheckDialog))
+#define E_EDITOR_SPELL_CHECK_DIALOG_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_EDITOR_SPELL_CHECK_DIALOG, EEditorSpellCheckDialogClass))
+#define E_IS_EDITOR_SPELL_CHECK_DIALOG(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_EDITOR_SPELL_CHECK_DIALOG))
+#define E_IS_EDITOR_SPELL_CHECK_DIALOG_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_EDITOR_SPELL_CHECK_DIALOG))
+#define E_EDITOR_SPELL_CHECK_DIALOG_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_EDITOR_SPELL_CHECK_DIALOG, EEditorSpellCheckDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EEditorSpellCheckDialog EEditorSpellCheckDialog;
+typedef struct _EEditorSpellCheckDialogClass EEditorSpellCheckDialogClass;
+typedef struct _EEditorSpellCheckDialogPrivate EEditorSpellCheckDialogPrivate;
+
+struct _EEditorSpellCheckDialog {
+       EEditorDialog parent;
+
+       EEditorSpellCheckDialogPrivate *priv;
+};
+
+struct _EEditorSpellCheckDialogClass {
+       EEditorDialogClass parent_class;
+};
+
+GType          e_editor_spell_check_dialog_get_type    (void);
+GtkWidget *    e_editor_spell_check_dialog_new         (EEditor *editor);
+
+GList *                e_editor_spell_check_dialog_get_dictionaries
+                                                       (EEditorSpellCheckDialog *dialog);
+void           e_editor_spell_check_dialog_set_dictionaries
+                                                       (EEditorSpellCheckDialog *dialog,
+                                                        GList *dictionaries);
+
+G_END_DECLS
+
+#endif /* E_EDITOR_SPELL_CHECK_DIALOG_H */
diff --git a/e-util/e-editor-spell-checker.c b/e-util/e-editor-spell-checker.c
new file mode 100644
index 0000000..b7c100f
--- /dev/null
+++ b/e-util/e-editor-spell-checker.c
@@ -0,0 +1,704 @@
+ /*
+ * e-editor-spell-checker.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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+ /* Based on webkitspellcheckerenchant.cpp */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-editor-spell-checker.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+#include <pango/pango.h>
+#include <enchant/enchant.h>
+#include <webkit/webkitspellchecker.h>
+
+#include "e-editor.h"
+
+static void e_editor_spell_checker_interface_init (WebKitSpellCheckerInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (
+       EEditorSpellChecker,
+       e_editor_spell_checker,
+       G_TYPE_OBJECT,
+       0,
+       G_IMPLEMENT_INTERFACE (
+               WEBKIT_TYPE_SPELL_CHECKER,
+               e_editor_spell_checker_interface_init));
+
+#define ISO_639_DOMAIN "iso_639"
+#define ISO_3166_DOMAIN        "iso_3166"
+
+struct _EEditorSpellCheckerPrivate {
+       EnchantBroker *broker;
+
+       GList *dicts;
+
+       /* Dictionary to which to write new words */
+       EnchantDict *write_dictionary;
+};
+
+struct _available_dictionaries_data {
+       EEditorSpellChecker *checker;
+       GList *dicts;
+};
+
+struct _enchant_dict_description_data {
+       const gchar *language_tag;
+       gchar *dict_name;
+};
+
+static GHashTable *iso_639_table = NULL;
+static GHashTable *iso_3166_table = NULL;
+
+#ifdef HAVE_ISO_CODES
+
+#define ISOCODESLOCALEDIR ISO_CODES_PREFIX "/share/locale"
+
+#ifdef G_OS_WIN32
+#ifdef DATADIR
+#undef DATADIR
+#endif
+#include <shlobj.h>
+static HMODULE hmodule;
+
+BOOL WINAPI
+DllMain (HINSTANCE hinstDLL,
+         DWORD fdwReason,
+         LPVOID lpvReserved);
+
+BOOL WINAPI
+DllMain (HINSTANCE hinstDLL,
+         DWORD fdwReason,
+         LPVOID lpvReserved)
+{
+       switch (fdwReason)
+    {
+    case DLL_PROCESS_ATTACH:
+               hmodule = hinstDLL;
+               break;
+    }
+
+       return TRUE;
+}
+
+static gchar *
+_get_iso_codes_prefix (void)
+{
+       static gchar retval[1000];
+       static gint beenhere = 0;
+       gchar *temp_dir = 0;
+
+       if (beenhere)
+               return retval;
+
+       if (!(temp_dir = g_win32_get_package_installation_directory_of_module ((gpointer) hmodule))) {
+               strcpy (retval, ISO_CODES_PREFIX);
+               return retval;
+       }
+
+       strcpy (retval, temp_dir);
+       g_free (temp_dir);
+       beenhere = 1;
+       return retval;
+}
+
+static gchar *
+_get_isocodeslocaledir (void)
+{
+       static gchar retval[1000];
+       static gint beenhere = 0;
+
+       if (beenhere)
+               return retval;
+
+       strcpy (retval, _get_iso_codes_prefix ());
+       strcat (retval, "\\share\\locale" );
+       beenhere = 1;
+       return retval;
+}
+
+#undef ISO_CODES_PREFIX
+#define ISO_CODES_PREFIX _get_iso_codes_prefix ()
+
+#undef ISOCODESLOCALEDIR
+#define ISOCODESLOCALEDIR _get_isocodeslocaledir ()
+
+#endif
+
+static void
+iso_639_start_element (GMarkupParseContext *context,
+                       const gchar *element_name,
+                       const gchar **attribute_names,
+                       const gchar **attribute_values,
+                       gpointer data,
+                       GError **error)
+{
+       GHashTable *hash_table = data;
+       const gchar *iso_639_1_code = NULL;
+       const gchar *iso_639_2_code = NULL;
+       const gchar *name = NULL;
+       const gchar *code = NULL;
+       gint ii;
+
+       if (g_strcmp0 (element_name, "iso_639_entry") != 0)
+               return;
+
+       for (ii = 0; attribute_names[ii] != NULL; ii++) {
+               if (strcmp (attribute_names[ii], "name") == 0)
+                       name = attribute_values[ii];
+               else if (strcmp (attribute_names[ii], "iso_639_1_code") == 0)
+                       iso_639_1_code = attribute_values[ii];
+               else if (strcmp (attribute_names[ii], "iso_639_2T_code") == 0)
+                       iso_639_2_code = attribute_values[ii];
+       }
+
+       code = (iso_639_1_code != NULL) ? iso_639_1_code : iso_639_2_code;
+
+       if (code != NULL && *code != '\0' && name != NULL && *name != '\0')
+               g_hash_table_insert (
+                       hash_table, g_strdup (code),
+                       g_strdup (dgettext (ISO_639_DOMAIN, name)));
+}
+
+static void
+iso_3166_start_element (GMarkupParseContext *context,
+                        const gchar *element_name,
+                        const gchar **attribute_names,
+                        const gchar **attribute_values,
+                        gpointer data,
+                        GError **error)
+{
+       GHashTable *hash_table = data;
+       const gchar *name = NULL;
+       const gchar *code = NULL;
+       gint ii;
+
+       if (strcmp (element_name, "iso_3166_entry") != 0)
+               return;
+
+       for (ii = 0; attribute_names[ii] != NULL; ii++) {
+               if (strcmp (attribute_names[ii], "name") == 0)
+                       name = attribute_values[ii];
+               else if (strcmp (attribute_names[ii], "alpha_2_code") == 0)
+                       code = attribute_values[ii];
+       }
+
+       if (code != NULL && *code != '\0' && name != NULL && *name != '\0')
+               g_hash_table_insert (
+                       hash_table, g_ascii_strdown (code, -1),
+                       g_strdup (dgettext (ISO_3166_DOMAIN, name)));
+}
+
+static GMarkupParser iso_639_parser = {
+       iso_639_start_element,
+       NULL, NULL, NULL, NULL
+};
+
+static GMarkupParser iso_3166_parser = {
+       iso_3166_start_element,
+       NULL, NULL, NULL, NULL
+};
+
+static void
+iso_codes_parse (const GMarkupParser *parser,
+                 const gchar *basename,
+                 GHashTable *hash_table)
+{
+       GMappedFile *mapped_file;
+       gchar *filename;
+       GError *error = NULL;
+
+       filename = g_build_filename (
+               ISO_CODES_PREFIX, "share", "xml",
+               "iso-codes", basename, NULL);
+       mapped_file = g_mapped_file_new (filename, FALSE, &error);
+       g_free (filename);
+
+       if (mapped_file != NULL) {
+               GMarkupParseContext *context;
+               const gchar *contents;
+               gsize length;
+
+               context = g_markup_parse_context_new (
+                       parser, 0, hash_table, NULL);
+               contents = g_mapped_file_get_contents (mapped_file);
+               length = g_mapped_file_get_length (mapped_file);
+               g_markup_parse_context_parse (
+                       context, contents, length, &error);
+               g_markup_parse_context_free (context);
+#if GLIB_CHECK_VERSION(2,21,3)
+               g_mapped_file_unref (mapped_file);
+#else
+               g_mapped_file_free (mapped_file);
+#endif
+       }
+
+       if (error != NULL) {
+               g_warning ("%s: %s", basename, error->message);
+               g_error_free (error);
+       }
+}
+
+#endif /* HAVE_ISO_CODES */
+
+
+static void
+get_available_dictionaries (const char *language_tag,
+                           const char *provider_name,
+                           const char *provider_desc,
+                           const char *provider_file,
+                           gpointer user_data)
+{
+       struct _available_dictionaries_data *data = user_data;
+       EnchantDict *dict;
+
+       dict = enchant_broker_request_dict (
+                       data->checker->priv->broker, language_tag);
+       if (dict) {
+               data->dicts = g_list_append (data->dicts, dict);
+       }
+}
+
+static void
+describe_dictionary (const gchar *language_tag,
+                    const gchar *provider_name,
+                    const gchar *provider_desc,
+                    const gchar *provider_file,
+                    gpointer user_data)
+{
+       struct _enchant_dict_description_data *data = user_data;
+       const gchar *iso_639_name;
+       const gchar *iso_3166_name;
+       gchar *language_name;
+       gchar *lowercase;
+       gchar **tokens;
+
+       /* Split language code into lowercase tokens. */
+       lowercase = g_ascii_strdown (language_tag, -1);
+       tokens = g_strsplit (lowercase, "_", -1);
+       g_free (lowercase);
+
+       g_return_if_fail (tokens != NULL);
+
+       iso_639_name = g_hash_table_lookup (iso_639_table, tokens[0]);
+
+       if (iso_639_name == NULL) {
+               language_name = g_strdup_printf (
+               /* Translators: %s is the language ISO code. */
+                       C_("language", "Unknown (%s)"), language_tag);
+               goto exit;
+       }
+
+       if (g_strv_length (tokens) < 2) {
+               language_name = g_strdup (iso_639_name);
+               goto exit;
+       }
+
+       iso_3166_name = g_hash_table_lookup (iso_3166_table, tokens[1]);
+
+       if (iso_3166_name != NULL)
+               language_name = g_strdup_printf (
+                /* Translators: The first %s is the language name, and the
+                * second is the country name. Example: "French (France)" */
+                       C_("language", "%s (%s)"), iso_639_name, iso_3166_name);
+       else
+               language_name = g_strdup_printf (
+                /* Translators: The first %s is the language name, and the
+                * second is the country name. Example: "French (France)" */
+                       C_("language", "%s (%s)"), iso_639_name, tokens[1]);
+
+exit:
+       g_strfreev (tokens);
+
+       data->language_tag = language_tag;
+       data->dict_name = language_name;
+}
+
+static void
+free_dictionary (gpointer data,
+                gpointer user_data)
+{
+       EEditorSpellChecker *checker = user_data;
+
+       enchant_broker_free_dict (checker->priv->broker, data);
+}
+
+static void
+update_spell_checking_languages (WebKitSpellChecker *ichecker,
+                                const char *languages)
+{
+       GList* spell_dictionaries = 0;
+       EEditorSpellChecker *checker = E_EDITOR_SPELL_CHECKER (ichecker);
+
+       if (languages) {
+               gchar **langs;
+               gint ii;
+
+               langs = g_strsplit (languages, ",", -1);
+               for (ii = 0; langs[ii]; ii++) {
+                       if (enchant_broker_dict_exists (
+                                       checker->priv->broker, langs[ii])) {
+
+                               EnchantDict* dict =
+                                       enchant_broker_request_dict (
+                                               checker->priv->broker, langs[ii]);
+                               spell_dictionaries = g_list_append (
+                                               spell_dictionaries, dict);
+                       }
+               }
+               g_strfreev (langs);
+       } else {
+               const char* language;
+
+               language = pango_language_to_string (gtk_get_default_language ());
+               if (enchant_broker_dict_exists (checker->priv->broker, language)) {
+                       EnchantDict* dict;
+
+                       dict = enchant_broker_request_dict (
+                               checker->priv->broker, language);
+                       spell_dictionaries =
+                               g_list_append (spell_dictionaries, dict);
+               } else {
+                       spell_dictionaries =
+                               e_editor_spell_checker_get_available_dicts (checker);
+               }
+       }
+
+       g_list_foreach (checker->priv->dicts, free_dictionary, checker);
+       g_list_free (checker->priv->dicts);
+       checker->priv->dicts = spell_dictionaries;
+}
+
+
+static void
+check_spelling_of_string (WebKitSpellChecker *webkit_checker,
+                         const gchar *word,
+                         gint *misspelling_location,
+                         gint *misspelling_length)
+{
+       EEditorSpellChecker *checker = E_EDITOR_SPELL_CHECKER (webkit_checker);
+       PangoLanguage *language;
+       PangoLogAttr *attrs;
+       GList *dicts;
+       gint length, ii;
+
+       dicts = checker->priv->dicts;
+       if (!dicts)
+               return;
+
+       length = g_utf8_strlen (word, -1);
+
+       language = pango_language_get_default ();
+       attrs = g_new (PangoLogAttr, length + 1);
+
+       pango_get_log_attrs (word, -1, -1, language, attrs, length + 1);
+
+       for (ii = 0; ii < length + 1; ii++) {
+               /* We go through each character until we find an is_word_start,
+                * then we get into an inner loop to find the is_word_end
+                * corresponding */
+               if (attrs[ii].is_word_start) {
+                       int start = ii;
+                       int end = ii;
+                       int word_length;
+                       gchar *cstart;
+                       gint bytes;
+                       gchar *new_word;
+                       GList *iter;
+
+                       while (attrs[end].is_word_end < 1) {
+                               end++;
+                       }
+
+                       word_length = end - start;
+                       /* Set the iterator to be at the current word end, so we don't
+                        * check characters twice. */
+                       ii = end;
+
+                       cstart = g_utf8_offset_to_pointer (word, start);
+                       bytes = g_utf8_offset_to_pointer (word, end) - cstart;
+                       new_word = g_new0 (gchar, bytes + 1);
+
+                       g_utf8_strncpy (new_word, cstart, word_length);
+
+                       for (iter = dicts; iter; iter = iter->next) {
+                               EnchantDict* dict = iter->data;
+
+                               if (enchant_dict_check (dict, new_word, word_length)) {
+                                       if (misspelling_location)
+                                               *misspelling_location = start;
+                                       if (misspelling_length)
+                                               *misspelling_length = word_length;
+                               } else {
+                                       /* Stop checking, this word is ok in at
+                                        * least one dict. */
+                                       if (misspelling_location)
+                                               *misspelling_location = -1;
+                                       if (misspelling_length)
+                                               *misspelling_length = 0;
+                                       break;
+                               }
+                       }
+               }
+       }
+}
+
+static gchar **
+get_guesses_for_word (WebKitSpellChecker *webkit_checker,
+                     const gchar *word,
+                     const gchar *context)
+{
+       EEditorSpellChecker *checker = E_EDITOR_SPELL_CHECKER (webkit_checker);
+       GList *dicts;
+       char** guesses = 0;
+
+       g_return_val_if_fail (E_IS_EDITOR_SPELL_CHECKER (checker), NULL);
+
+       for (dicts = checker->priv->dicts; dicts; dicts = dicts->next) {
+               EnchantDict *dict;
+               gchar **suggestions;
+               size_t suggestions_count;
+               size_t ii;
+
+               dict = dicts->data;
+               suggestions = enchant_dict_suggest (dict, word, -1, &suggestions_count);
+
+               if (suggestions_count > 0) {
+                       if (suggestions_count > 10) {
+                               suggestions_count = 10;
+                       }
+
+                       guesses = g_malloc0 ((suggestions_count + 1) * sizeof (char *));
+                       for (ii = 0; ii < suggestions_count && ii < 10; ii++) {
+                               guesses[ii] = g_strdup (suggestions[ii]);
+                       }
+
+                       guesses[ii] = 0;
+
+                       enchant_dict_free_suggestions (dict, suggestions);
+               }
+       }
+
+       return guesses;
+}
+
+static void
+ignore_word (WebKitSpellChecker *checker,
+            const gchar *word)
+{
+       EEditorSpellChecker *editor_spellchecker;
+
+       editor_spellchecker = E_EDITOR_SPELL_CHECKER (checker);
+       enchant_dict_add_to_session (
+               editor_spellchecker->priv->write_dictionary, word, -1);
+}
+
+static void
+learn_word (WebKitSpellChecker *checker,
+           const gchar *word)
+{
+       EEditorSpellChecker *editor_spellchecker;
+
+       editor_spellchecker = E_EDITOR_SPELL_CHECKER (checker);
+       enchant_dict_add_to_personal (
+               editor_spellchecker->priv->write_dictionary, word, -1);
+}
+
+static gchar *
+get_autocorrect_suggestions (WebKitSpellChecker *checker,
+                            const gchar *word)
+{
+       /* Not implemented, not needed */
+       return 0;
+}
+
+
+static void
+editor_spell_checker_finalize (GObject *object)
+{
+       EEditorSpellCheckerPrivate *priv = E_EDITOR_SPELL_CHECKER (object)->priv;
+
+       if (priv->broker) {
+               enchant_broker_free (priv->broker);
+               priv->broker = NULL;
+       }
+
+       if (priv->dicts) {
+               g_list_free (priv->dicts);
+               priv->dicts = NULL;
+       }
+
+       /* Chain up to parent implementation */
+       G_OBJECT_CLASS (e_editor_spell_checker_parent_class)->finalize (object);
+}
+
+static void
+e_editor_spell_checker_class_init (EEditorSpellCheckerClass *klass)
+{
+       GObjectClass *object_class;
+
+       e_editor_spell_checker_parent_class = g_type_class_peek_parent (klass);
+       g_type_class_add_private (klass, sizeof (EEditorSpellCheckerPrivate));
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->finalize = editor_spell_checker_finalize;
+}
+
+static void
+e_editor_spell_checker_interface_init (WebKitSpellCheckerInterface *iface)
+{
+       iface->check_spelling_of_string = check_spelling_of_string;
+       iface->get_autocorrect_suggestions_for_misspelled_word = get_autocorrect_suggestions;
+       iface->get_guesses_for_word = get_guesses_for_word;
+       iface->update_spell_checking_languages = update_spell_checking_languages;
+       iface->ignore_word = ignore_word;
+       iface->learn_word = learn_word;
+}
+
+static void
+e_editor_spell_checker_init (EEditorSpellChecker *checker)
+{
+       checker->priv = G_TYPE_INSTANCE_GET_PRIVATE (
+               checker, E_TYPE_EDITOR_SPELL_CHECKER, EEditorSpellCheckerPrivate);
+
+       checker->priv->broker = enchant_broker_init ();
+
+#if defined (ENABLE_NLS) && defined (HAVE_ISO_CODES)
+       bindtextdomain (ISO_639_DOMAIN, ISOCODESLOCALEDIR);
+       bind_textdomain_codeset (ISO_639_DOMAIN, "UTF-8");
+
+       bindtextdomain (ISO_3166_DOMAIN, ISOCODESLOCALEDIR);
+       bind_textdomain_codeset (ISO_3166_DOMAIN, "UTF-8");
+#endif
+
+       iso_639_table = g_hash_table_new_full (
+               g_str_hash, g_str_equal,
+               (GDestroyNotify) g_free,
+               (GDestroyNotify) g_free);
+
+       iso_3166_table = g_hash_table_new_full (
+               g_str_hash, g_str_equal,
+               (GDestroyNotify) g_free,
+               (GDestroyNotify) g_free);
+
+#ifdef HAVE_ISO_CODES
+       iso_codes_parse (&iso_639_parser, "iso_639.xml", iso_639_table);
+       iso_codes_parse (&iso_3166_parser, "iso_3166.xml", iso_3166_table);
+#endif
+
+}
+
+void
+e_editor_spell_checker_set_dictionaries (EEditorSpellChecker *checker,
+                                        GList *dictionaries)
+{
+       g_return_if_fail (E_IS_EDITOR_SPELL_CHECKER (checker));
+
+       g_list_foreach (checker->priv->dicts, free_dictionary, checker);
+       g_list_free (checker->priv->dicts);
+
+       checker->priv->dicts = g_list_copy (dictionaries);
+}
+
+EnchantDict *
+e_editor_spell_checker_lookup_dict (EEditorSpellChecker *checker,
+                                   const gchar *language_code)
+{
+       g_return_val_if_fail (E_IS_EDITOR_SPELL_CHECKER (checker), NULL);
+       g_return_val_if_fail (language_code != NULL, NULL);
+
+       if (!enchant_broker_dict_exists (checker->priv->broker, language_code)) {
+               return NULL;
+       }
+
+       return enchant_broker_request_dict (checker->priv->broker, language_code);
+}
+
+void
+e_editor_spell_checker_free_dict (EEditorSpellChecker *checker,
+                                 EnchantDict *dict)
+{
+       g_return_if_fail (E_IS_EDITOR_SPELL_CHECKER (checker));
+       g_return_if_fail (dict != NULL);
+
+       enchant_broker_free_dict (checker->priv->broker, dict);
+}
+
+
+gint
+e_editor_spell_checker_dict_compare (const EnchantDict *dict_a,
+                                    const EnchantDict *dict_b)
+{
+       const gchar *dict_a_name, *dict_b_name;
+       gchar *dict_a_ckey, *dict_b_ckey;
+       gint result;
+
+       dict_a_name = e_editor_spell_checker_get_dict_name (dict_a);
+       dict_b_name = e_editor_spell_checker_get_dict_name (dict_b);
+
+       dict_a_ckey = g_utf8_collate_key (dict_a_name, -1);
+       dict_b_ckey = g_utf8_collate_key (dict_b_name, -1);
+
+       result = g_strcmp0 (dict_a_ckey, dict_b_ckey);
+
+       g_free (dict_a_ckey);
+       g_free (dict_b_ckey);
+
+       return result;
+}
+
+GList *
+e_editor_spell_checker_get_available_dicts (EEditorSpellChecker *checker)
+{
+       struct _available_dictionaries_data data;
+
+       g_return_val_if_fail (E_IS_EDITOR_SPELL_CHECKER (checker), NULL);
+
+       data.checker = checker;
+       data.dicts = NULL;
+
+       enchant_broker_list_dicts (
+               checker->priv->broker, get_available_dictionaries, &data);
+
+       return data.dicts;
+}
+
+const gchar *
+e_editor_spell_checker_get_dict_name (const EnchantDict *dictionary)
+{
+       struct _enchant_dict_description_data data;
+
+       enchant_dict_describe (
+               (EnchantDict *) dictionary, describe_dictionary, &data);
+
+       return data.dict_name;
+}
+
+const gchar *
+e_editor_spell_checker_get_dict_code (const EnchantDict *dictionary)
+{
+       struct _enchant_dict_description_data data;
+
+       enchant_dict_describe (
+               (EnchantDict *) dictionary, describe_dictionary, &data);
+
+       return data.language_tag;
+}
diff --git a/e-util/e-editor-spell-checker.h b/e-util/e-editor-spell-checker.h
new file mode 100644
index 0000000..16bb54e
--- /dev/null
+++ b/e-util/e-editor-spell-checker.h
@@ -0,0 +1,85 @@
+/*
+ * e-editor-spell-checker.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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_EDITOR_SPELL_CHECKER_H
+#define E_EDITOR_SPELL_CHECKER_H
+
+#include <glib-object.h>
+#include <enchant/enchant.h>
+
+#define E_TYPE_EDITOR_SPELL_CHECKER \
+       (e_editor_spell_checker_get_type ())
+#define E_EDITOR_SPELL_CHECKER(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_EDITOR_SPELL_CHECKER, EEditorSpellChecker))
+#define E_EDITOR_SPELL_CHECKER_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_EDITOR_SPELL_CHECKER, EEditorSpellCheckerClass))
+#define E_IS_EDITOR_SPELL_CHECKER(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_EDITOR_SPELL_CHECKER))
+#define E_IS_EDITOR_SPELL_CHECKER_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_EDITOR_SPELL_CHECKER))
+#define E_EDITOR_SPELL_CHECKER_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_EDITOR_SPELL_CHECKER, EEditorSpellCheckerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EEditorSpellChecker EEditorSpellChecker;
+typedef struct _EEditorSpellCheckerClass EEditorSpellCheckerClass;
+typedef struct _EEditorSpellCheckerPrivate EEditorSpellCheckerPrivate;
+
+struct _EEditorSpellChecker {
+       GObject parent;
+
+       EEditorSpellCheckerPrivate *priv;
+};
+
+struct _EEditorSpellCheckerClass {
+       GObjectClass parent_class;
+};
+
+GType          e_editor_spell_checker_get_type         (void);
+
+
+void           e_editor_spell_checker_set_dictionaries (EEditorSpellChecker *checker,
+                                                        GList *dictionaries);
+
+EnchantDict *  e_editor_spell_checker_lookup_dict      (EEditorSpellChecker *checker,
+                                                        const gchar *language_code);
+void           e_editor_spell_checker_free_dict        (EEditorSpellChecker *checker,
+                                                        EnchantDict *dict);
+
+gint           e_editor_spell_checker_dict_compare     (const EnchantDict *dict_a,
+                                                        const EnchantDict *dict_b);
+
+GList *                e_editor_spell_checker_get_available_dicts
+                                                       (EEditorSpellChecker *checker);
+
+const gchar *  e_editor_spell_checker_get_dict_name    (const EnchantDict *dictionary);
+const gchar *  e_editor_spell_checker_get_dict_code    (const EnchantDict *dictionary);
+
+G_END_DECLS
+
+#endif /* E_EDITOR_SPELL_CHECKER_H */
diff --git a/e-util/e-editor-widget.c b/e-util/e-editor-widget.c
index 313f56a..605e5bc 100644
--- a/e-util/e-editor-widget.c
+++ b/e-util/e-editor-widget.c
@@ -23,6 +23,7 @@
 #include "e-editor-widget.h"
 #include "e-editor.h"
 #include "e-emoticon-chooser.h"
+#include "e-editor-spell-checker.h"
 
 #include <e-util/e-util.h>
 #include <e-util/e-marshal.h>
@@ -642,6 +643,7 @@ e_editor_widget_init (EEditorWidget *editor)
        WebKitDOMDocument *document;
        GSettings *g_settings;
        GSettingsSchema *settings_schema;
+       EEditorSpellChecker *checker;
 
        editor->priv = G_TYPE_INSTANCE_GET_PRIVATE (
                editor, E_TYPE_EDITOR_WIDGET, EEditorWidgetPrivate);
@@ -656,10 +658,15 @@ e_editor_widget_init (EEditorWidget *editor)
                "enable-file-access-from-file-uris", TRUE,
                "enable-plugins", FALSE,
                "enable-scripts", FALSE,
+               "enable-spell-checking", TRUE,
                NULL);
 
        webkit_web_view_set_settings (WEBKIT_WEB_VIEW (editor), settings);
 
+       /* Override the spell-checker, use our own */
+       checker = g_object_new (E_TYPE_EDITOR_SPELL_CHECKER, NULL);
+       webkit_set_text_checker (G_OBJECT (checker));
+
        /* Don't use CSS when possible to preserve compatibility with older
         * versions of Evolution or other MUAs */
        document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (editor));
diff --git a/e-util/e-editor.c b/e-util/e-editor.c
index c755f8c..8432ec8 100644
--- a/e-util/e-editor.c
+++ b/e-util/e-editor.c
@@ -22,8 +22,11 @@
 #include "e-editor.h"
 #include "e-editor-private.h"
 #include "e-editor-utils.h"
+#include "e-editor-selection.h"
+#include "e-editor-spell-checker.h"
 
 #include <glib/gi18n-lib.h>
+#include <enchant/enchant.h>
 
 G_DEFINE_TYPE (
        EEditor,
@@ -37,25 +40,260 @@ enum {
 
 enum {
        UPDATE_ACTIONS,
+       SPELL_LANGUAGES_CHANGED,
        LAST_SIGNAL
 };
 
 static guint signals[LAST_SIGNAL] = { 0 };
 
+
+/* This controls how spelling suggestions are divided between the primary
+ * context menu and a secondary menu.  The idea is to prevent the primary
+ * menu from growing too long.
+ *
+ * The constants below are used as follows:
+ *
+ * if TOTAL_SUGGESTIONS <= MAX_LEVEL1_SUGGETIONS:
+ *     LEVEL1_SUGGESTIONS = TOTAL_SUGGESTIONS
+ * elif TOTAL_SUGGESTIONS - MAX_LEVEL1_SUGGESTIONS < MIN_LEVEL2_SUGGESTIONS:
+ *     LEVEL1_SUGGESTIONS = TOTAL_SUGGESTIONS
+ * else
+ *     LEVEL1_SUGGESTIONS = MAX_LEVEL1_SUGGETIONS
+ *
+ * LEVEL2_SUGGESTIONS = TOTAL_SUGGESTIONS - LEVEL1_SUGGESTIONS
+ *
+ * Note that MAX_LEVEL1_SUGGETIONS is not a hard maximum.
+ */
+#define MAX_LEVEL1_SUGGESTIONS 4
+#define MIN_LEVEL2_SUGGESTIONS 3
+
+/* Action callback for context menu spelling suggestions.
+ * XXX This should really be in e-editor-actions.c */
+static void
+action_context_spell_suggest_cb (GtkAction *action,
+                                 EEditor *editor)
+{
+       EEditorWidget *widget;
+       EEditorSelection *selection;
+       const gchar *word;
+
+       word = g_object_get_data (G_OBJECT (action), "word");
+       g_return_if_fail (word != NULL);
+
+       widget = e_editor_get_editor_widget (editor);
+       selection = e_editor_widget_get_selection (widget);
+
+       e_editor_selection_replace_caret_word (selection, word);
+}
+
+static void
+editor_inline_spelling_suggestions (EEditor *editor,
+                                    EnchantDict *dictionary)
+{
+       EEditorWidget *widget;
+       EEditorSelection *selection;
+       WebKitSpellChecker *checker;
+       GtkActionGroup *action_group;
+       GtkUIManager *manager;
+       gchar **suggestions;
+       const gchar *path;
+       gchar *word;
+       guint count = 0;
+       guint length;
+       guint merge_id;
+       guint threshold;
+       gint ii;
+
+       widget = e_editor_get_editor_widget (editor);
+       selection = e_editor_widget_get_selection (widget);
+       checker = WEBKIT_SPELL_CHECKER (webkit_get_text_checker ());
+
+       word = e_editor_selection_get_caret_word (selection);
+       if (!word || !*word) {
+               return;
+       }
+
+       suggestions = webkit_spell_checker_get_guesses_for_word (checker, word, NULL);
+
+       path = "/context-menu/context-spell-suggest/";
+       manager = e_editor_get_ui_manager (editor);
+       action_group = editor->priv->suggestion_actions;
+       merge_id = editor->priv->spell_suggestions_merge_id;
+
+       length = 0;
+       while (suggestions && suggestions[length]) {
+               length++;
+       }
+
+       /* Calculate how many suggestions to put directly in the
+        * context menu.  The rest will go in a secondary menu. */
+       if (length <= MAX_LEVEL1_SUGGESTIONS) {
+               threshold = length;
+       } else if (length - MAX_LEVEL1_SUGGESTIONS < MIN_LEVEL2_SUGGESTIONS) {
+               threshold = length;
+       } else {
+               threshold = MAX_LEVEL1_SUGGESTIONS;
+       }
+
+       ii = 0;
+       for (ii = 0; suggestions && suggestions[ii]; ii++) {
+               gchar *suggestion = suggestions[ii];
+               gchar *action_name;
+               gchar *action_label;
+               GtkAction *action;
+               GtkWidget *child;
+               GSList *proxies;
+
+               /* Once we reach the threshold, put all subsequent
+                * spelling suggestions in a secondary menu. */
+               if (count == threshold)
+                       path = "/context-menu/context-more-suggestions-menu/";
+
+               /* Action name just needs to be unique. */
+               action_name = g_strdup_printf ("suggest-%d", count++);
+
+               action_label = g_markup_printf_escaped (
+                       "<b>%s</b>", suggestion);
+
+               action = gtk_action_new (
+                       action_name, action_label, NULL, NULL);
+
+               g_object_set_data_full (
+                       G_OBJECT (action), "word",
+                       g_strdup (suggestion), g_free);
+
+               g_signal_connect (
+                       action, "activate", G_CALLBACK (
+                       action_context_spell_suggest_cb), editor);
+
+               gtk_action_group_add_action (action_group, action);
+
+               gtk_ui_manager_add_ui (
+                       manager, merge_id, path,
+                       action_name, action_name,
+                       GTK_UI_MANAGER_AUTO, FALSE);
+
+               /* XXX GtkAction offers no support for Pango markup,
+                *     so we have to manually set "use-markup" on the
+                *     child of the proxy widget. */
+               gtk_ui_manager_ensure_update (manager);
+               proxies = gtk_action_get_proxies (action);
+               child = gtk_bin_get_child (proxies->data);
+               g_object_set (child, "use-markup", TRUE, NULL);
+
+               g_free (action_name);
+               g_free (action_label);
+       }
+
+       g_free (word);
+       g_strfreev (suggestions);
+}
+
+/* Helper for editor_update_actions() */
+static void
+editor_spell_checkers_foreach (EnchantDict *dictionary,
+                               EEditor *editor)
+{
+       EEditorSpellChecker *checker;
+       EEditorWidget *widget;
+       EEditorSelection *selection;
+       const gchar *language_code;
+       GtkActionGroup *action_group;
+       GtkUIManager *manager;
+       gchar **suggestions;
+       gchar *path;
+       gchar *word;
+       gint ii = 0;
+       guint merge_id;
+
+       language_code = e_editor_spell_checker_get_dict_code (dictionary);
+
+       widget = e_editor_get_editor_widget (editor);
+       selection = e_editor_widget_get_selection (widget);
+       checker = E_EDITOR_SPELL_CHECKER (webkit_get_text_checker ());
+       word = e_editor_selection_get_caret_word (selection);
+       if (!word || !*word) {
+               return;
+       }
+
+       suggestions = webkit_spell_checker_get_guesses_for_word (
+                       WEBKIT_SPELL_CHECKER (checker), word, NULL);
+
+       manager = e_editor_get_ui_manager (editor);
+       action_group = editor->priv->suggestion_actions;
+       merge_id = editor->priv->spell_suggestions_merge_id;
+
+       path = g_strdup_printf (
+               "/context-menu/context-spell-suggest/"
+               "context-spell-suggest-%s-menu", language_code);
+
+       for (ii = 0; suggestions && suggestions[ii]; ii++) {
+               gchar *suggestion = suggestions[ii];
+               gchar *action_name;
+               gchar *action_label;
+               GtkAction *action;
+               GtkWidget *child;
+               GSList *proxies;
+
+               /* Action name just needs to be unique. */
+               action_name = g_strdup_printf (
+                       "suggest-%s-%d", language_code, ii);
+
+               action_label = g_markup_printf_escaped (
+                       "<b>%s</b>", suggestion);
+
+               action = gtk_action_new (
+                       action_name, action_label, NULL, NULL);
+
+               g_object_set_data_full (
+                       G_OBJECT (action), "word",
+                       g_strdup (suggestion), g_free);
+
+               g_signal_connect (
+                       action, "activate", G_CALLBACK (
+                       action_context_spell_suggest_cb), editor);
+
+               gtk_action_group_add_action (action_group, action);
+
+               gtk_ui_manager_add_ui (
+                       manager, merge_id, path,
+                       action_name, action_name,
+                       GTK_UI_MANAGER_AUTO, FALSE);
+
+               /* XXX GtkAction offers no supports for Pango markup,
+                *     so we have to manually set "use-markup" on the
+                *     child of the proxy widget. */
+               gtk_ui_manager_ensure_update (manager);
+               proxies = gtk_action_get_proxies (action);
+               child = gtk_bin_get_child (proxies->data);
+               g_object_set (child, "use-markup", TRUE, NULL);
+
+               g_free (action_name);
+               g_free (action_label);
+       }
+
+       g_free (path);
+       g_free (word);
+       g_strfreev (suggestions);
+}
+
 static void
 editor_update_actions (EEditor *editor,
                       GdkEventButton *event)
 {
        WebKitWebView *webview;
+       WebKitSpellChecker *checker;
        WebKitHitTestResult *hit_test;
        WebKitHitTestResultContext context;
        WebKitDOMNode *node;
        EEditorWidget *widget;
+       EEditorSelection *selection;
        GtkUIManager *manager;
        GtkActionGroup *action_group;
        GList *list;
        gboolean visible;
        guint merge_id;
+       gint loc, len;
 
        widget = e_editor_get_editor_widget (editor);
        webview = WEBKIT_WEB_VIEW (widget);
@@ -126,18 +364,16 @@ editor_update_actions (EEditor *editor,
 
        /********************** Spell Check Suggestions **********************/
 
-       /* FIXME WEBKIT No spellcheching for now
-       object = html->engine->cursor->object;
        action_group = editor->priv->suggestion_actions;
 
-       // Remove the old content from the context menu.
+       /* Remove the old content from the context menu. */
        merge_id = editor->priv->spell_suggestions_merge_id;
        if (merge_id > 0) {
                gtk_ui_manager_remove_ui (manager, merge_id);
                editor->priv->spell_suggestions_merge_id = 0;
        }
 
-       // Clear the action group for spelling suggestions.
+       /* Clear the action group for spelling suggestions. */
        list = gtk_action_group_list_actions (action_group);
        while (list != NULL) {
                GtkAction *action = list->data;
@@ -146,31 +382,88 @@ editor_update_actions (EEditor *editor,
                list = g_list_delete_link (list, list);
        }
 
-       // Decide if we should show spell checking items.
-       visible =
-               !html_engine_is_selection_active (html->engine) &&
-               object != NULL && html_object_is_text (object) &&
-               !html_engine_spell_word_is_valid (html->engine);
+       /* Decide if we should show spell checking items. */
+       checker = WEBKIT_SPELL_CHECKER (webkit_get_text_checker ());
+       selection = e_editor_widget_get_selection (widget);
+       visible = FALSE;
+       if ((g_list_length (editor->priv->active_dictionaries) > 0) &&
+           e_editor_selection_has_text (selection)) {
+               gchar *word = e_editor_selection_get_caret_word (selection);
+               if (word && *word) {
+                       webkit_spell_checker_check_spelling_of_string (
+                               checker, word, &loc, &len);
+                       visible = (loc > -1);
+               } else {
+                       visible = FALSE;
+               }
+               g_free (word);
+       }
+
        action_group = editor->priv->spell_check_actions;
        gtk_action_group_set_visible (action_group, visible);
 
-       // Exit early if spell checking items are invisible.
-       if (!visible)
+       /* Exit early if spell checking items are invisible. */
+       if (!visible) {
                return;
+       }
 
-       list = editor->priv->active_spell_checkers;
+       list = editor->priv->active_dictionaries;
        merge_id = gtk_ui_manager_new_merge_id (manager);
        editor->priv->spell_suggestions_merge_id = merge_id;
 
-       // Handle a single active language as a special case.
+       /* Handle a single active language as a special case. */
        if (g_list_length (list) == 1) {
                editor_inline_spelling_suggestions (editor, list->data);
                return;
        }
 
-       // Add actions and context menu content for active languages
+       /* Add actions and context menu content for active languages */
        g_list_foreach (list, (GFunc) editor_spell_checkers_foreach, editor);
-       */
+}
+
+static void
+editor_spell_languages_changed (EEditor *editor,
+                               GList *dictionaries)
+{
+       WebKitSpellChecker *checker;
+       WebKitWebSettings *settings;
+       GString *languages;
+       const GList *iter;
+
+       languages = g_string_new ("");
+
+       /* Join the languages codes to comma-separated list */
+       for (iter = dictionaries; iter; iter = iter->next) {
+               EnchantDict *dictionary = iter->data;
+
+               if (iter != dictionaries) {
+                       g_string_append (languages, ",");
+               }
+
+               g_string_append (
+                       languages,
+                       e_editor_spell_checker_get_dict_code (dictionary));
+       }
+
+       /* Set the languages for spell-checker to use for suggestions etc. */
+       checker = WEBKIT_SPELL_CHECKER (webkit_get_text_checker());
+       webkit_spell_checker_update_spell_checking_languages (checker, languages->str);
+
+       /* Set the languages for webview to highlight misspelled words */
+       settings = webkit_web_view_get_settings (
+                       WEBKIT_WEB_VIEW (editor->priv->editor_widget));
+       g_object_set (
+               G_OBJECT (settings),
+               "spell-checking-languages", languages->str,
+               NULL);
+
+       if (editor->priv->spell_check_dialog != NULL) {
+               e_editor_spell_check_dialog_set_dictionaries (
+                       E_EDITOR_SPELL_CHECK_DIALOG (editor->priv->spell_check_dialog),
+                       dictionaries);
+       }
+
+       g_string_free (languages, TRUE);
 }
 
 static gboolean
@@ -261,6 +554,7 @@ editor_constructed (GObject *object)
 {
        EEditor *editor = E_EDITOR (object);
        EEditorPrivate *priv = editor->priv;
+       GtkIMMulticontext *im_context;
 
        GtkWidget *widget;
        GtkToolbar *toolbar;
@@ -364,69 +658,15 @@ editor_constructed (GObject *object)
        priv->size_combo_box = g_object_ref (widget);
        gtk_widget_show_all (GTK_WIDGET (tool_item));
 
-       /* Initialize painters (requires "edit_area"). */
-
-       /* FIXME WEBKIT
-       html = e_editor_widget_get_html (E_EDITOR_WIDGET (editor));
-       gtk_widget_ensure_style (GTK_WIDGET (html));
-       priv->html_painter = g_object_ref (html->engine->painter);
-       priv->plain_painter = html_plain_painter_new (priv->edit_area, TRUE);
-       */
-
        /* Add input methods to the context menu. */
-
-       /* FIXME WEBKIT
        widget = e_editor_get_managed_widget (
                editor, "/context-menu/context-input-methods-menu");
        widget = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
+       g_object_get (
+               G_OBJECT (priv->editor_widget), "im-context", &im_context, NULL);
        gtk_im_multicontext_append_menuitems (
-               GTK_IM_MULTICONTEXT (html->priv->im_context),
+               GTK_IM_MULTICONTEXT (im_context),
                GTK_MENU_SHELL (widget));
-       */
-
-       /* Configure color stuff. */
-
-       /* FIXME WEBKIT
-       priv->palette = gtkhtml_color_palette_new ();
-       priv->text_color = gtkhtml_color_state_new ();
-
-       gtkhtml_color_state_set_default_label (
-               priv->text_color, _("Automatic"));
-       gtkhtml_color_state_set_palette (
-               priv->text_color, priv->palette);
-       */
-
-       /* Text color widgets share state. */
-
-       /* FIXME WEBKIT
-       widget = priv->color_combo_box;
-       gtkhtml_color_combo_set_state (
-               GTKHTML_COLOR_COMBO (widget), priv->text_color);
-
-       widget = WIDGET (TEXT_PROPERTIES_COLOR_COMBO);
-       gtkhtml_color_combo_set_state (
-               GTKHTML_COLOR_COMBO (widget), priv->text_color);
-       */
-
-       /* These color widgets share a custom color palette. */
-
-       /* FIXME WEBKIT
-       widget = WIDGET (CELL_PROPERTIES_COLOR_COMBO);
-       gtkhtml_color_combo_set_palette (
-               GTKHTML_COLOR_COMBO (widget), priv->palette);
-
-       widget = WIDGET (PAGE_PROPERTIES_BACKGROUND_COLOR_COMBO);
-       gtkhtml_color_combo_set_palette (
-               GTKHTML_COLOR_COMBO (widget), priv->palette);
-
-       widget = WIDGET (PAGE_PROPERTIES_LINK_COLOR_COMBO);
-       gtkhtml_color_combo_set_palette (
-               GTKHTML_COLOR_COMBO (widget), priv->palette);
-
-       widget = WIDGET (TABLE_PROPERTIES_COLOR_COMBO);
-       gtkhtml_color_combo_set_palette (
-               GTKHTML_COLOR_COMBO (widget), priv->palette);
-               */
 }
 
 static void
@@ -435,16 +675,6 @@ editor_dispose (GObject *object)
        EEditor *editor = E_EDITOR (object);
        EEditorPrivate *priv = editor->priv;
 
-       /* Disconnect signal handlers from the color
-        * state object since it may live on. */
-       /* FIXME WEBKIT
-       if (priv->text_color != NULL) {
-               g_signal_handlers_disconnect_matched (
-                       priv->text_color, G_SIGNAL_MATCH_DATA,
-                       0, 0, NULL, NULL, editor);
-       }
-       */
-
        g_clear_object (&priv->manager);
        g_clear_object (&priv->manager);
        g_clear_object (&priv->core_actions);
@@ -456,15 +686,8 @@ editor_dispose (GObject *object)
        g_clear_object (&priv->suggestion_actions);
        g_clear_object (&priv->builder);
 
-       /* FIXME WEBKIT
-       g_hash_table_remove_all (priv->available_spell_checkers);
-
-       g_list_foreach (
-               priv->active_spell_checkers,
-               (GFunc) g_object_unref, NULL);
-       g_list_free (priv->active_spell_checkers);
-       priv->active_spell_checkers = NULL;
-       */
+       g_list_free (priv->active_dictionaries);
+       priv->active_dictionaries = NULL;
 
        g_clear_object (&priv->main_menu);
        g_clear_object (&priv->main_toolbar);
@@ -477,11 +700,6 @@ editor_dispose (GObject *object)
        g_clear_object (&priv->size_combo_box);
        g_clear_object (&priv->style_combo_box);
        g_clear_object (&priv->scrolled_window);
-
-       /* FIXME WEBKIT
-       DISPOSE (priv->palette);
-       DISPOSE (priv->text_color);
-       */
 }
 
 static void
@@ -498,6 +716,7 @@ e_editor_class_init (EEditorClass *klass)
        object_class->dispose = editor_dispose;
 
        klass->update_actions = editor_update_actions;
+       klass->spell_languages_changed = editor_spell_languages_changed;
 
        g_object_class_install_property (
                object_class,
@@ -518,6 +737,16 @@ e_editor_class_init (EEditorClass *klass)
                g_cclosure_marshal_VOID__BOXED,
                G_TYPE_NONE, 1,
                GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+       signals[SPELL_LANGUAGES_CHANGED] = g_signal_new (
+               "spell-languages-changed",
+               G_OBJECT_CLASS_TYPE (klass),
+               G_SIGNAL_RUN_LAST,
+               G_STRUCT_OFFSET (EEditorClass, spell_languages_changed),
+               NULL, NULL,
+               g_cclosure_marshal_VOID__POINTER,
+               G_TYPE_NONE, 1,
+               G_TYPE_POINTER);
 }
 
 static void
@@ -755,3 +984,24 @@ e_editor_save (EEditor *editor,
 
        return TRUE;
 }
+
+void
+e_editor_emit_spell_languages_changed (EEditor *editor)
+{
+       GList *dictionaries, *iter;
+
+       g_return_if_fail (editor != NULL);
+
+       dictionaries = NULL;
+       for (iter = editor->priv->active_dictionaries; iter; iter = g_list_next (iter)) {
+               EnchantDict *dictionary = iter->data;
+
+               dictionaries = g_list_prepend (dictionaries, dictionary);
+       }
+
+       dictionaries = g_list_reverse (dictionaries);
+
+       g_signal_emit (editor, signals[SPELL_LANGUAGES_CHANGED], 0, dictionaries);
+
+       g_list_free (dictionaries);
+}
diff --git a/e-util/e-editor.h b/e-util/e-editor.h
index b39e6a1..1dcfdd0 100644
--- a/e-util/e-editor.h
+++ b/e-util/e-editor.h
@@ -61,6 +61,10 @@ struct _EEditorClass {
 
        void            (*update_actions)       (EEditor *editor,
                                                 GdkEventButton *event);
+
+       void            (*spell_languages_changed)
+                                               (EEditor *editor,
+                                                GList *dictionaries);
 };
 
 GType          e_editor_get_type               (void);
@@ -83,6 +87,9 @@ const gchar * e_editor_get_filename           (EEditor *editor);
 void           e_editor_set_filename           (EEditor *editor,
                                                 const gchar *filename);
 
+void           e_editor_emit_spell_languages_changed
+                                               (EEditor *editor);
+
 
 /*****************************************************************************
  * High-Level Editing Interface
diff --git a/e-util/e-util.h b/e-util/e-util.h
index 7d72a1c..90b9396 100644
--- a/e-util/e-util.h
+++ b/e-util/e-util.h
@@ -103,6 +103,8 @@
 #include <e-util/e-editor-paragraph-dialog.h>
 #include <e-util/e-editor-replace-dialog.h>
 #include <e-util/e-editor-selection.h>
+#include <e-util/e-editor-spell-check-dialog.h>
+#include <e-util/e-editor-spell-checker.h>
 #include <e-util/e-editor-table-dialog.h>
 #include <e-util/e-editor-text-dialog.h>
 #include <e-util/e-editor-utils.h>
@@ -187,8 +189,6 @@
 #include <e-util/e-source-selector-dialog.h>
 #include <e-util/e-source-selector.h>
 #include <e-util/e-source-util.h>
-#include <e-util/e-spell-dialog.h>
-#include <e-util/e-spell-dictionary.h>
 #include <e-util/e-spell-entry.h>
 #include <e-util/e-stock-request.h>
 #include <e-util/e-table-click-to-add.h>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]