2010-12-08 Gustavo Noronha Silva <gustavo.noronha@collabora.co.uk>
[WebKit-https.git] / WebKit / gtk / WebCoreSupport / EditorClientGtk.cpp
index 9294d73..6e31b03 100644 (file)
@@ -3,7 +3,8 @@
  *  Copyright (C) 2008 Nuanti Ltd.
  *  Copyright (C) 2009 Diego Escalante Urrelo <diegoe@gnome.org>
  *  Copyright (C) 2006, 2007 Apple Inc.  All rights reserved.
- *  Copyright (C) 2009, Igalia S.L.
+ *  Copyright (C) 2009, 2010 Igalia S.L.
+ *  Copyright (C) 2010, Martin Robinson <mrobinson@webkit.org>
  *
  *  This library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Lesser General Public
@@ -23,7 +24,7 @@
 #include "config.h"
 #include "EditorClientGtk.h"
 
-#include "CString.h"
+#include "DataObjectGtk.h"
 #include "EditCommand.h"
 #include "Editor.h"
 #include <enchant.h>
 #include "FocusController.h"
 #include "Frame.h"
 #include <glib.h>
-#include "KeyboardCodes.h"
 #include "KeyboardEvent.h"
+#include "markup.h"
 #include "NotImplemented.h"
 #include "Page.h"
+#include "PasteboardHelperGtk.h"
 #include "PlatformKeyboardEvent.h"
-#include "markup.h"
+#include "WebKitDOMBinding.h"
+#include "WebKitDOMCSSStyleDeclarationPrivate.h"
+#include "WebKitDOMHTMLElementPrivate.h"
+#include "WebKitDOMNodePrivate.h"
+#include "WebKitDOMRangePrivate.h"
+#include "WindowsKeyboardCodes.h"
+#include "webkitmarshal.h"
 #include "webkitprivate.h"
+#include "webkitwebviewprivate.h"
+#include <wtf/text/CString.h>
+
+// Arbitrary depth limit for the undo stack, to keep it from using
+// unbounded memory.  This is the maximum number of distinct undoable
+// actions -- unbroken stretches of typed characters are coalesced
+// into a single action.
+#define maximumUndoStackDepth 1000
 
 using namespace WebCore;
 
 namespace WebKit {
 
-static void imContextCommitted(GtkIMContext* context, const gchar* str, EditorClient* client)
+static void imContextCommitted(GtkIMContext* context, const gchar* compositionString, EditorClient* client)
 {
-    Frame* targetFrame = core(client->m_webView)->focusController()->focusedOrMainFrame();
-
-    if (!targetFrame || !targetFrame->editor()->canEdit())
+    Frame* frame = core(client->webView())->focusController()->focusedOrMainFrame();
+    if (!frame || !frame->editor()->canEdit())
         return;
 
-    Editor* editor = targetFrame->editor();
+    // If this signal fires during a keydown event when we are not in the middle
+    // of a composition, then treat this 'commit' as a normal key event and just
+    // change the editable area right before the keypress event.
+    if (client->treatContextCommitAsKeyEvent()) {
+        client->updatePendingComposition(compositionString);
+        return;
+    }
 
-    String commitString = String::fromUTF8(str);
-    editor->confirmComposition(commitString);
+    // If this signal fires during a mousepress event when we are in the middle
+    // of a composition, skip this 'commit' because the composition is already confirmed. 
+    if (client->preventNextCompositionCommit()) 
+        return;
+    frame->editor()->confirmComposition(String::fromUTF8(compositionString));
+    client->clearPendingComposition();
 }
 
 static void imContextPreeditChanged(GtkIMContext* context, EditorClient* client)
 {
-    Frame* frame = core(client->m_webView)->focusController()->focusedOrMainFrame();
-    Editor* editor = frame->editor();
+    Frame* frame = core(client->webView())->focusController()->focusedOrMainFrame();
+    if (!frame || !frame->editor()->canEdit())
+        return;
 
-    gchar* preedit = NULL;
-    gint cursorPos = 0;
     // We ignore the provided PangoAttrList for now.
-    gtk_im_context_get_preedit_string(context, &preedit, NULL, &cursorPos);
-    String preeditString = String::fromUTF8(preedit);
-    g_free(preedit);
-
-    // setComposition() will replace the user selection if passed an empty
-    // preedit. We don't want this to happen.
-    if (preeditString.isEmpty() && !editor->hasComposition())
-        return;
+    GOwnPtr<gchar> newPreedit(0);
+    gtk_im_context_get_preedit_string(context, &newPreedit.outPtr(), 0, 0);
 
+    String preeditString = String::fromUTF8(newPreedit.get());
     Vector<CompositionUnderline> underlines;
     underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
-    editor->setComposition(preeditString, underlines, cursorPos, 0);
+    frame->editor()->setComposition(preeditString, underlines, 0, 0);
+}
+
+static void backspaceCallback(GtkWidget* widget, EditorClient* client)
+{
+    g_signal_stop_emission_by_name(widget, "backspace");
+    client->addPendingEditorCommand("DeleteBackward");
+}
+
+static void selectAllCallback(GtkWidget* widget, gboolean select, EditorClient* client)
+{
+    g_signal_stop_emission_by_name(widget, "select-all");
+    client->addPendingEditorCommand(select ? "SelectAll" : "Unselect");
+}
+
+static void cutClipboardCallback(GtkWidget* widget, EditorClient* client)
+{
+    g_signal_stop_emission_by_name(widget, "cut-clipboard");
+    client->addPendingEditorCommand("Cut");
+}
+
+static void copyClipboardCallback(GtkWidget* widget, EditorClient* client)
+{
+    g_signal_stop_emission_by_name(widget, "copy-clipboard");
+    client->addPendingEditorCommand("Copy");
+}
+
+static void pasteClipboardCallback(GtkWidget* widget, EditorClient* client)
+{
+    g_signal_stop_emission_by_name(widget, "paste-clipboard");
+    client->addPendingEditorCommand("Paste");
+}
+
+static void toggleOverwriteCallback(GtkWidget* widget, EditorClient*)
+{
+    // We don't support toggling the overwrite mode, but the default callback expects
+    // the GtkTextView to have a layout, so we handle this signal just to stop it.
+    g_signal_stop_emission_by_name(widget, "toggle-overwrite");
+}
+
+// GTK+ will still send these signals to the web view. So we can safely stop signal
+// emission without breaking accessibility.
+static void popupMenuCallback(GtkWidget* widget, EditorClient*)
+{
+    g_signal_stop_emission_by_name(widget, "popup-menu");
+}
+
+static void showHelpCallback(GtkWidget* widget, EditorClient*)
+{
+    g_signal_stop_emission_by_name(widget, "show-help");
+}
+
+static const char* const gtkDeleteCommands[][2] = {
+    { "DeleteBackward",               "DeleteForward"                        }, // Characters
+    { "DeleteWordBackward",           "DeleteWordForward"                    }, // Word ends
+    { "DeleteWordBackward",           "DeleteWordForward"                    }, // Words
+    { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Lines
+    { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Line ends
+    { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraph ends
+    { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraphs
+    { 0,                              0                                      } // Whitespace (M-\ in Emacs)
+};
+
+static void deleteFromCursorCallback(GtkWidget* widget, GtkDeleteType deleteType, gint count, EditorClient* client)
+{
+    g_signal_stop_emission_by_name(widget, "delete-from-cursor");
+    int direction = count > 0 ? 1 : 0;
+
+    // Ensuring that deleteType <= G_N_ELEMENTS here results in a compiler warning
+    // that the condition is always true.
+
+    if (deleteType == GTK_DELETE_WORDS) {
+        if (!direction) {
+            client->addPendingEditorCommand("MoveWordForward");
+            client->addPendingEditorCommand("MoveWordBackward");
+        } else {
+            client->addPendingEditorCommand("MoveWordBackward");
+            client->addPendingEditorCommand("MoveWordForward");
+        }
+    } else if (deleteType == GTK_DELETE_DISPLAY_LINES) {
+        if (!direction)
+            client->addPendingEditorCommand("MoveToBeginningOfLine");
+        else
+            client->addPendingEditorCommand("MoveToEndOfLine");
+    } else if (deleteType == GTK_DELETE_PARAGRAPHS) {
+        if (!direction)
+            client->addPendingEditorCommand("MoveToBeginningOfParagraph");
+        else
+            client->addPendingEditorCommand("MoveToEndOfParagraph");
+    }
+
+    const char* rawCommand = gtkDeleteCommands[deleteType][direction];
+    if (!rawCommand)
+      return;
+
+    for (int i = 0; i < abs(count); i++)
+        client->addPendingEditorCommand(rawCommand);
+}
+
+static const char* const gtkMoveCommands[][4] = {
+    { "MoveBackward",                                   "MoveForward",
+      "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Forward/backward grapheme
+    { "MoveBackward",                                   "MoveForward",
+      "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Left/right grapheme
+    { "MoveWordBackward",                               "MoveWordForward",
+      "MoveWordBackwardAndModifySelection",             "MoveWordForwardAndModifySelection"         }, // Forward/backward word
+    { "MoveUp",                                         "MoveDown",
+      "MoveUpAndModifySelection",                       "MoveDownAndModifySelection"                }, // Up/down line
+    { "MoveToBeginningOfLine",                          "MoveToEndOfLine",
+      "MoveToBeginningOfLineAndModifySelection",        "MoveToEndOfLineAndModifySelection"         }, // Up/down line ends
+    { "MoveParagraphForward",                           "MoveParagraphBackward",
+      "MoveParagraphForwardAndModifySelection",         "MoveParagraphBackwardAndModifySelection"   }, // Up/down paragraphs
+    { "MoveToBeginningOfParagraph",                     "MoveToEndOfParagraph",
+      "MoveToBeginningOfParagraphAndModifySelection",   "MoveToEndOfParagraphAndModifySelection"    }, // Up/down paragraph ends.
+    { "MovePageUp",                                     "MovePageDown",
+      "MovePageUpAndModifySelection",                   "MovePageDownAndModifySelection"            }, // Up/down page
+    { "MoveToBeginningOfDocument",                      "MoveToEndOfDocument",
+      "MoveToBeginningOfDocumentAndModifySelection",    "MoveToEndOfDocumentAndModifySelection"     }, // Begin/end of buffer
+    { 0,                                                0,
+      0,                                                0                                           } // Horizontal page movement
+};
+
+static void moveCursorCallback(GtkWidget* widget, GtkMovementStep step, gint count, gboolean extendSelection, EditorClient* client)
+{
+    g_signal_stop_emission_by_name(widget, "move-cursor");
+    int direction = count > 0 ? 1 : 0;
+    if (extendSelection)
+        direction += 2;
+
+    if (static_cast<unsigned>(step) >= G_N_ELEMENTS(gtkMoveCommands))
+        return;
+
+    const char* rawCommand = gtkMoveCommands[step][direction];
+    if (!rawCommand)
+        return;
+
+    if (isSpatialNavigationEnabled(core(client->webView())->focusController()->focusedOrMainFrame()) && step == 1) {
+        if (direction == 1)
+            rawCommand = "MoveRight";
+        else if (!direction)
+            rawCommand = "MoveLeft";
+    }
+
+    for (int i = 0; i < abs(count); i++)
+        client->addPendingEditorCommand(rawCommand);
+}
+
+void EditorClient::updatePendingComposition(const gchar* newComposition)
+{
+    // The IMContext may signal more than one completed composition in a row,
+    // in which case we want to append them, rather than overwrite the old one.
+    if (!m_pendingComposition)
+        m_pendingComposition.set(g_strdup(newComposition));
+    else
+        m_pendingComposition.set(g_strconcat(m_pendingComposition.get(), newComposition, NULL));
+}
+
+void EditorClient::willSetInputMethodState()
+{
 }
 
 void EditorClient::setInputMethodState(bool active)
@@ -83,27 +261,32 @@ void EditorClient::setInputMethodState(bool active)
     WebKitWebViewPrivate* priv = m_webView->priv;
 
     if (active)
-        gtk_im_context_focus_in(priv->imContext);
+        gtk_im_context_focus_in(priv->imContext.get());
     else
-        gtk_im_context_focus_out(priv->imContext);
+        gtk_im_context_focus_out(priv->imContext.get());
 
 #ifdef MAEMO_CHANGES
     if (active)
-        hildon_gtk_im_context_show(priv->imContext);
+        hildon_gtk_im_context_show(priv->imContext.get());
     else
-        hildon_gtk_im_context_hide(priv->imContext);
+        hildon_gtk_im_context_hide(priv->imContext.get());
 #endif
 }
 
-bool EditorClient::shouldDeleteRange(Range*)
+bool EditorClient::shouldDeleteRange(Range* range)
 {
-    notImplemented();
-    return true;
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMRange> kitRange(adoptPlatformRef(kit(range)));
+    g_signal_emit_by_name(m_webView, "should-delete-range", kitRange.get(), &accept);
+    return accept;
 }
 
-bool EditorClient::shouldShowDeleteInterface(HTMLElement*)
+bool EditorClient::shouldShowDeleteInterface(HTMLElement* element)
 {
-    return false;
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMHTMLElement> kitElement(adoptPlatformRef(kit(element)));
+    g_signal_emit_by_name(m_webView, "should-show-delete-interface-for-element", kitElement.get(), &accept);
+    return accept;
 }
 
 bool EditorClient::isContinuousSpellCheckingEnabled()
@@ -128,34 +311,77 @@ int EditorClient::spellCheckerDocumentTag()
     return 0;
 }
 
-bool EditorClient::shouldBeginEditing(WebCore::Range*)
+bool EditorClient::shouldBeginEditing(WebCore::Range* range)
 {
-    notImplemented();
-    return true;
+    clearPendingComposition();
+
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMRange> kitRange(adoptPlatformRef(kit(range)));
+    g_signal_emit_by_name(m_webView, "should-begin-editing", kitRange.get(), &accept);
+    return accept;
 }
 
-bool EditorClient::shouldEndEditing(WebCore::Range*)
+bool EditorClient::shouldEndEditing(WebCore::Range* range)
 {
-    notImplemented();
-    return true;
+    clearPendingComposition();
+
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMRange> kitRange(adoptPlatformRef(kit(range)));
+    g_signal_emit_by_name(m_webView, "should-end-editing", kitRange.get(), &accept);
+    return accept;
 }
 
-bool EditorClient::shouldInsertText(const String&, Range*, EditorInsertAction)
+static WebKitInsertAction kit(EditorInsertAction action)
 {
-    notImplemented();
-    return true;
+    switch (action) {
+    case EditorInsertActionTyped:
+        return WEBKIT_INSERT_ACTION_TYPED;
+    case EditorInsertActionPasted:
+        return WEBKIT_INSERT_ACTION_PASTED;
+    case EditorInsertActionDropped:
+        return WEBKIT_INSERT_ACTION_DROPPED;
+    }
+    ASSERT_NOT_REACHED();
+    return WEBKIT_INSERT_ACTION_TYPED;
 }
 
-bool EditorClient::shouldChangeSelectedRange(Range*, Range*, EAffinity, bool)
+bool EditorClient::shouldInsertText(const String& string, Range* range, EditorInsertAction action)
 {
-    notImplemented();
-    return true;
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMRange> kitRange(adoptPlatformRef(kit(range)));
+    g_signal_emit_by_name(m_webView, "should-insert-text", string.utf8().data(), kitRange.get(), kit(action), &accept);
+    return accept;
 }
 
-bool EditorClient::shouldApplyStyle(WebCore::CSSStyleDeclaration*, WebCore::Range*)
+static WebKitSelectionAffinity kit(EAffinity affinity)
 {
-    notImplemented();
-    return true;
+    switch (affinity) {
+    case UPSTREAM:
+        return WEBKIT_SELECTION_AFFINITY_UPSTREAM;
+    case DOWNSTREAM:
+        return WEBKIT_SELECTION_AFFINITY_DOWNSTREAM;
+    }
+    ASSERT_NOT_REACHED();
+    return WEBKIT_SELECTION_AFFINITY_UPSTREAM;
+}
+
+bool EditorClient::shouldChangeSelectedRange(Range* fromRange, Range* toRange, EAffinity affinity, bool stillSelecting)
+{
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMRange> kitFromRange(fromRange ? adoptPlatformRef(kit(fromRange)) : 0);
+    PlatformRefPtr<WebKitDOMRange> kitToRange(toRange ? adoptPlatformRef(kit(toRange)) : 0);
+    g_signal_emit_by_name(m_webView, "should-change-selected-range", kitFromRange.get(), kitToRange.get(),
+                          kit(affinity), stillSelecting, &accept);
+    return accept;
+}
+
+bool EditorClient::shouldApplyStyle(WebCore::CSSStyleDeclaration* declaration, WebCore::Range* range)
+{
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMCSSStyleDeclaration> kitDeclaration(kit(declaration));
+    PlatformRefPtr<WebKitDOMRange> kitRange(adoptPlatformRef(kit(range)));
+    g_signal_emit_by_name(m_webView, "should-apply-style", kitDeclaration.get(), kitRange.get(), &accept);
+    return accept;
 }
 
 bool EditorClient::shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*)
@@ -166,43 +392,63 @@ bool EditorClient::shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*)
 
 void EditorClient::didBeginEditing()
 {
-    notImplemented();
+    g_signal_emit_by_name(m_webView, "editing-began");
 }
 
 void EditorClient::respondToChangedContents()
 {
-    notImplemented();
+    g_signal_emit_by_name(m_webView, "user-changed-contents");
 }
 
-static void clipboard_get_contents_cb(GtkClipboard* clipboard, GtkSelectionData* selection_data, guint info, gpointer data)
+static WebKitWebView* viewSettingClipboard = 0;
+static void collapseSelection(GtkClipboard* clipboard, WebKitWebView* webView)
 {
-    WebKitWebView* webView = reinterpret_cast<WebKitWebView*>(data);
-    Frame* frame = core(webView)->focusController()->focusedOrMainFrame();
-    PassRefPtr<Range> selectedRange = frame->selection()->toNormalizedRange();
+    if (viewSettingClipboard && viewSettingClipboard == webView)
+        return;
 
-    if (static_cast<gint>(info) == WEBKIT_WEB_VIEW_TARGET_INFO_HTML) {
-        String markup = createMarkup(selectedRange.get(), 0, AnnotateForInterchange);
-        gtk_selection_data_set(selection_data, selection_data->target, 8,
-                               reinterpret_cast<const guchar*>(markup.utf8().data()), markup.utf8().length());
-    } else {
-        String text = selectedRange->text();
-        gtk_selection_data_set_text(selection_data, text.utf8().data(), text.utf8().length());
-    }
-}
+    WebCore::Page* corePage = core(webView);
+    if (!corePage || !corePage->focusController())
+        return;
 
-static void clipboard_clear_contents_cb(GtkClipboard* clipboard, gpointer data)
-{
-    WebKitWebView* webView = reinterpret_cast<WebKitWebView*>(data);
-    Frame* frame = core(webView)->focusController()->focusedOrMainFrame();
+    Frame* frame = corePage->focusController()->focusedOrMainFrame();
 
     // Collapse the selection without clearing it
+    ASSERT(frame);
     frame->selection()->setBase(frame->selection()->extent(), frame->selection()->affinity());
 }
 
+#if PLATFORM(X11)
+static void setSelectionPrimaryClipboardIfNeeded(WebKitWebView* webView)
+{
+    if (!gtk_widget_has_screen(GTK_WIDGET(webView)))
+        return;
+
+    GtkClipboard* clipboard = gtk_widget_get_clipboard(GTK_WIDGET(webView), GDK_SELECTION_PRIMARY);
+    DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
+    WebCore::Page* corePage = core(webView);
+    Frame* targetFrame = corePage->focusController()->focusedOrMainFrame();
+
+    if (!targetFrame->selection()->isRange())
+        return;
+
+    dataObject->clear();
+    dataObject->setRange(targetFrame->selection()->toNormalizedRange());
+
+    viewSettingClipboard = webView;
+    GClosure* callback = g_cclosure_new_object(G_CALLBACK(collapseSelection), G_OBJECT(webView));
+    g_closure_set_marshal(callback, g_cclosure_marshal_VOID__VOID);
+    pasteboardHelperInstance()->writeClipboardContents(clipboard, callback);
+    viewSettingClipboard = 0;
+}
+#endif
+
 void EditorClient::respondToChangedSelection()
 {
+    g_signal_emit_by_name(m_webView, "selection-changed");
+
     WebKitWebViewPrivate* priv = m_webView->priv;
-    Frame* targetFrame = core(m_webView)->focusController()->focusedOrMainFrame();
+    WebCore::Page* corePage = core(m_webView);
+    Frame* targetFrame = corePage->focusController()->focusedOrMainFrame();
 
     if (!targetFrame)
         return;
@@ -210,16 +456,9 @@ void EditorClient::respondToChangedSelection()
     if (targetFrame->editor()->ignoreCompositionSelectionChange())
         return;
 
-    GtkClipboard* clipboard = gtk_widget_get_clipboard(GTK_WIDGET(m_webView), GDK_SELECTION_PRIMARY);
-    if (targetFrame->selection()->isRange()) {
-        GtkTargetList* targetList = webkit_web_view_get_copy_target_list(m_webView);
-        gint targetCount;
-        GtkTargetEntry* targets = gtk_target_table_new_from_list(targetList, &targetCount);
-        gtk_clipboard_set_with_owner(clipboard, targets, targetCount,
-                                     clipboard_get_contents_cb, clipboard_clear_contents_cb, G_OBJECT(m_webView));
-        gtk_target_table_free(targets, targetCount);
-    } else if (gtk_clipboard_get_owner(clipboard) == G_OBJECT(m_webView))
-        gtk_clipboard_clear(clipboard);
+#if PLATFORM(X11)
+    setSelectionPrimaryClipboardIfNeeded(m_webView);
+#endif
 
     if (!targetFrame->editor()->hasComposition())
         return;
@@ -228,14 +467,14 @@ void EditorClient::respondToChangedSelection()
     unsigned end;
     if (!targetFrame->editor()->getCompositionSelection(start, end)) {
         // gtk_im_context_reset() clears the composition for us.
-        gtk_im_context_reset(priv->imContext);
+        gtk_im_context_reset(priv->imContext.get());
         targetFrame->editor()->confirmCompositionWithoutDisturbingSelection();
     }
 }
 
 void EditorClient::didEndEditing()
 {
-    notImplemented();
+    g_signal_emit_by_name(m_webView, "editing-ended");
 }
 
 void EditorClient::didWriteSelectionToPasteboard()
@@ -253,47 +492,67 @@ bool EditorClient::isEditable()
     return webkit_web_view_get_editable(m_webView);
 }
 
-void EditorClient::registerCommandForUndo(WTF::PassRefPtr<WebCore::EditCommand>)
+void EditorClient::registerCommandForUndo(WTF::PassRefPtr<WebCore::EditCommand> command)
 {
-    notImplemented();
+    if (undoStack.size() == maximumUndoStackDepth)
+        undoStack.removeFirst();
+    if (!m_isInRedo)
+        redoStack.clear();
+    undoStack.append(command);
 }
 
-void EditorClient::registerCommandForRedo(WTF::PassRefPtr<WebCore::EditCommand>)
+void EditorClient::registerCommandForRedo(WTF::PassRefPtr<WebCore::EditCommand> command)
 {
-    notImplemented();
+    redoStack.append(command);
 }
 
 void EditorClient::clearUndoRedoOperations()
 {
-    notImplemented();
+    undoStack.clear();
+    redoStack.clear();
 }
 
 bool EditorClient::canUndo() const
 {
-    notImplemented();
-    return false;
+    return !undoStack.isEmpty();
 }
 
 bool EditorClient::canRedo() const
 {
-    notImplemented();
-    return false;
+    return !redoStack.isEmpty();
 }
 
 void EditorClient::undo()
 {
-    notImplemented();
+    if (canUndo()) {
+        RefPtr<WebCore::EditCommand> command(*(--undoStack.end()));
+        undoStack.remove(--undoStack.end());
+        // unapply will call us back to push this command onto the redo stack.
+        command->unapply();
+    }
 }
 
 void EditorClient::redo()
 {
-    notImplemented();
+    if (canRedo()) {
+        RefPtr<WebCore::EditCommand> command(*(--redoStack.end()));
+        redoStack.remove(--redoStack.end());
+
+        ASSERT(!m_isInRedo);
+        m_isInRedo = true;
+        // reapply will call us back to push this command onto the undo stack.
+        command->reapply();
+        m_isInRedo = false;
+    }
 }
 
-bool EditorClient::shouldInsertNode(Node*, Range*, EditorInsertAction)
+bool EditorClient::shouldInsertNode(Node* node, Range* range, EditorInsertAction action)
 {
-    notImplemented();
-    return true;
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMRange> kitRange(adoptPlatformRef(kit(range)));
+    PlatformRefPtr<WebKitDOMNode> kitNode(adoptPlatformRef(kit(node)));
+    g_signal_emit_by_name(m_webView, "should-insert-node", kitNode.get(), kitRange.get(), kit(action), &accept);
+    return accept;
 }
 
 void EditorClient::pageDestroyed()
@@ -344,41 +603,8 @@ struct KeyPressEntry {
 };
 
 static const KeyDownEntry keyDownEntries[] = {
-    { VK_LEFT,   0,                  "MoveLeft"                                    },
-    { VK_LEFT,   ShiftKey,           "MoveLeftAndModifySelection"                  },
-    { VK_LEFT,   CtrlKey,            "MoveWordLeft"                                },
-    { VK_LEFT,   CtrlKey | ShiftKey, "MoveWordLeftAndModifySelection"              },
-    { VK_RIGHT,  0,                  "MoveRight"                                   },
-    { VK_RIGHT,  ShiftKey,           "MoveRightAndModifySelection"                 },
-    { VK_RIGHT,  CtrlKey,            "MoveWordRight"                               },
-    { VK_RIGHT,  CtrlKey | ShiftKey, "MoveWordRightAndModifySelection"             },
-    { VK_UP,     0,                  "MoveUp"                                      },
-    { VK_UP,     ShiftKey,           "MoveUpAndModifySelection"                    },
-    { VK_PRIOR,  ShiftKey,           "MovePageUpAndModifySelection"                },
-    { VK_DOWN,   0,                  "MoveDown"                                    },
-    { VK_DOWN,   ShiftKey,           "MoveDownAndModifySelection"                  },
-    { VK_NEXT,   ShiftKey,           "MovePageDownAndModifySelection"              },
-    { VK_PRIOR,  0,                  "MovePageUp"                                  },
-    { VK_NEXT,   0,                  "MovePageDown"                                },
-    { VK_HOME,   0,                  "MoveToBeginningOfLine"                       },
-    { VK_HOME,   ShiftKey,           "MoveToBeginningOfLineAndModifySelection"     },
-    { VK_HOME,   CtrlKey,            "MoveToBeginningOfDocument"                   },
-    { VK_HOME,   CtrlKey | ShiftKey, "MoveToBeginningOfDocumentAndModifySelection" },
-
-    { VK_END,    0,                  "MoveToEndOfLine"                             },
-    { VK_END,    ShiftKey,           "MoveToEndOfLineAndModifySelection"           },
-    { VK_END,    CtrlKey,            "MoveToEndOfDocument"                         },
-    { VK_END,    CtrlKey | ShiftKey, "MoveToEndOfDocumentAndModifySelection"       },
-
-    { VK_BACK,   0,                  "DeleteBackward"                              },
-    { VK_BACK,   ShiftKey,           "DeleteBackward"                              },
-    { VK_DELETE, 0,                  "DeleteForward"                               },
-    { VK_BACK,   CtrlKey,            "DeleteWordBackward"                          },
-    { VK_DELETE, CtrlKey,            "DeleteWordForward"                           },
-
     { 'B',       CtrlKey,            "ToggleBold"                                  },
     { 'I',       CtrlKey,            "ToggleItalic"                                },
-
     { VK_ESCAPE, 0,                  "Cancel"                                      },
     { VK_OEM_PERIOD, CtrlKey,        "Cancel"                                      },
     { VK_TAB,    0,                  "InsertTab"                                   },
@@ -387,11 +613,6 @@ static const KeyDownEntry keyDownEntries[] = {
     { VK_RETURN, CtrlKey,            "InsertNewline"                               },
     { VK_RETURN, AltKey,             "InsertNewline"                               },
     { VK_RETURN, AltKey | ShiftKey,  "InsertNewline"                               },
-
-    // It's not quite clear whether Undo/Redo should be handled
-    // in the application or in WebKit. We chose WebKit.
-    { 'Z',       CtrlKey,            "Undo"                                        },
-    { 'Z',       CtrlKey | ShiftKey, "Redo"                                        },
 };
 
 static const KeyPressEntry keyPressEntries[] = {
@@ -403,136 +624,247 @@ static const KeyPressEntry keyPressEntries[] = {
     { '\r',   AltKey | ShiftKey,  "InsertNewline"                               },
 };
 
-static const char* interpretKeyEvent(const KeyboardEvent* evt)
+void EditorClient::generateEditorCommands(const KeyboardEvent* event)
 {
-    ASSERT(evt->type() == eventNames().keydownEvent || evt->type() == eventNames().keypressEvent);
+    ASSERT(event->type() == eventNames().keydownEvent || event->type() == eventNames().keypressEvent);
+
+    m_pendingEditorCommands.clear();
 
-    static HashMap<int, const char*>* keyDownCommandsMap = 0;
-    static HashMap<int, const char*>* keyPressCommandsMap = 0;
+    // First try to interpret the command as a native GTK+ key binding.
+#ifdef GTK_API_VERSION_2
+    gtk_bindings_activate_event(GTK_OBJECT(m_nativeWidget.get()), event->keyEvent()->gdkEventKey());
+#else
+    gtk_bindings_activate_event(G_OBJECT(m_nativeWidget.get()), event->keyEvent()->gdkEventKey());
+#endif
+    if (m_pendingEditorCommands.size() > 0)
+        return;
 
-    if (!keyDownCommandsMap) {
-        keyDownCommandsMap = new HashMap<int, const char*>;
-        keyPressCommandsMap = new HashMap<int, const char*>;
+    static HashMap<int, const char*> keyDownCommandsMap;
+    static HashMap<int, const char*> keyPressCommandsMap;
 
+    if (keyDownCommandsMap.isEmpty()) {
         for (unsigned i = 0; i < G_N_ELEMENTS(keyDownEntries); i++)
-            keyDownCommandsMap->set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name);
+            keyDownCommandsMap.set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name);
 
         for (unsigned i = 0; i < G_N_ELEMENTS(keyPressEntries); i++)
-            keyPressCommandsMap->set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name);
+            keyPressCommandsMap.set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name);
     }
 
     unsigned modifiers = 0;
-    if (evt->shiftKey())
+    if (event->shiftKey())
         modifiers |= ShiftKey;
-    if (evt->altKey())
+    if (event->altKey())
         modifiers |= AltKey;
-    if (evt->ctrlKey())
+    if (event->ctrlKey())
         modifiers |= CtrlKey;
 
-    if (evt->type() == eventNames().keydownEvent) {
-        int mapKey = modifiers << 16 | evt->keyCode();
-        return mapKey ? keyDownCommandsMap->get(mapKey) : 0;
+
+    if (event->type() == eventNames().keydownEvent) {
+        int mapKey = modifiers << 16 | event->keyCode();
+        if (mapKey)
+            m_pendingEditorCommands.append(keyDownCommandsMap.get(mapKey));
+        return;
     }
 
-    int mapKey = modifiers << 16 | evt->charCode();
-    return mapKey ? keyPressCommandsMap->get(mapKey) : 0;
+    int mapKey = modifiers << 16 | event->charCode();
+    if (mapKey)
+        m_pendingEditorCommands.append(keyPressCommandsMap.get(mapKey));
 }
 
-static bool handleEditingKeyboardEvent(KeyboardEvent* evt)
+bool EditorClient::executePendingEditorCommands(Frame* frame, bool allowTextInsertion)
 {
-    Node* node = evt->target()->toNode();
+    Vector<Editor::Command> commands;
+    for (size_t i = 0; i < m_pendingEditorCommands.size(); i++) {
+        Editor::Command command = frame->editor()->command(m_pendingEditorCommands.at(i));
+        if (command.isTextInsertion() && !allowTextInsertion)
+            return false;
+
+        commands.append(command);
+    }
+
+    bool success = true;
+    for (size_t i = 0; i < commands.size(); i++) {
+        if (!commands.at(i).execute()) {
+            success = false;
+            break;
+        }
+    }
+
+    m_pendingEditorCommands.clear();
+
+    // If we successfully completed all editor commands, then
+    // this signals a canceling of the composition.
+    if (success)
+        clearPendingComposition();
+
+    return success;
+}
+
+void EditorClient::handleKeyboardEvent(KeyboardEvent* event)
+{
+    Node* node = event->target()->toNode();
     ASSERT(node);
     Frame* frame = node->document()->frame();
     ASSERT(frame);
 
-    const PlatformKeyboardEvent* keyEvent = evt->keyEvent();
-    if (!keyEvent)
-        return false;
-
-    bool caretBrowsing = frame->settings()->caretBrowsingEnabled();
-    if (caretBrowsing) {
-        switch (keyEvent->windowsVirtualKeyCode()) {
-            case VK_LEFT:
-                frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
-                        SelectionController::LEFT,
-                        keyEvent->ctrlKey() ? WordGranularity : CharacterGranularity,
-                        true);
-                return true;
-            case VK_RIGHT:
-                frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
-                        SelectionController::RIGHT,
-                        keyEvent->ctrlKey() ? WordGranularity : CharacterGranularity,
-                        true);
-                return true;
-            case VK_UP:
-                frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
-                        SelectionController::BACKWARD,
-                        keyEvent->ctrlKey() ? ParagraphGranularity : LineGranularity,
-                        true);
-                return true;
-            case VK_DOWN:
-                frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
-                        SelectionController::FORWARD,
-                        keyEvent->ctrlKey() ? ParagraphGranularity : LineGranularity,
-                        true);
-                return true;
-        }
-    }
+    const PlatformKeyboardEvent* platformEvent = event->keyEvent();
+    if (!platformEvent)
+        return;
 
-    Editor::Command command = frame->editor()->command(interpretKeyEvent(evt));
+    generateEditorCommands(event);
+    if (m_pendingEditorCommands.size() > 0) {
 
-    if (keyEvent->type() == PlatformKeyboardEvent::RawKeyDown) {
-        // WebKit doesn't have enough information about mode to decide how commands that just insert text if executed via Editor should be treated,
-        // so we leave it upon WebCore to either handle them immediately (e.g. Tab that changes focus) or let a keypress event be generated
-        // (e.g. Tab that inserts a Tab character, or Enter).
-        return !command.isTextInsertion() && command.execute(evt);
-    }
+        // During RawKeyDown events if an editor command will insert text, defer
+        // the insertion until the keypress event. We want keydown to bubble up
+        // through the DOM first.
+        if (platformEvent->type() == PlatformKeyboardEvent::RawKeyDown) {
+            if (executePendingEditorCommands(frame, false))
+                event->setDefaultHandled();
 
-    if (command.execute(evt))
-        return true;
+            return;
+        }
 
-    // Don't insert null or control characters as they can result in unexpected behaviour
-    if (evt->charCode() < ' ')
-        return false;
+        // Only allow text insertion commands if the current node is editable.
+        if (executePendingEditorCommands(frame, frame->editor()->canEdit())) {
+            event->setDefaultHandled();
+            return;
+        }
+    }
 
-    // Don't insert anything if a modifier is pressed
-    if (keyEvent->ctrlKey() || keyEvent->altKey())
-        return false;
+    // Don't allow text insertion for nodes that cannot edit.
+    if (!frame->editor()->canEdit())
+        return;
 
-    return frame->editor()->insertText(evt->keyEvent()->text(), evt);
+    // This is just a normal text insertion, so wait to execute the insertion
+    // until a keypress event happens. This will ensure that the insertion will not
+    // be reflected in the contents of the field until the keyup DOM event.
+    if (event->type() == eventNames().keypressEvent) {
+
+        // If we have a pending composition at this point, it happened while
+        // filtering a keypress, so we treat it as a normal text insertion.
+        // This will also ensure that if the keypress event handler changed the
+        // currently focused node, the text is still inserted into the original
+        // node (insertText() has this logic, but confirmComposition() does not).
+        if (m_pendingComposition) {
+            frame->editor()->insertText(String::fromUTF8(m_pendingComposition.get()), event);
+            clearPendingComposition();
+            event->setDefaultHandled();
+
+        } else {
+            // Don't insert null or control characters as they can result in unexpected behaviour
+            if (event->charCode() < ' ')
+                return;
+
+            // Don't insert anything if a modifier is pressed
+            if (platformEvent->ctrlKey() || platformEvent->altKey())
+                return;
+
+            if (frame->editor()->insertText(platformEvent->text(), event))
+                event->setDefaultHandled();
+        }
+    }
 }
 
-void EditorClient::handleKeyboardEvent(KeyboardEvent* event)
+void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
 {
-    if (handleEditingKeyboardEvent(event))
-        event->setDefaultHandled();
+    Frame* targetFrame = core(m_webView)->focusController()->focusedOrMainFrame();
+    if (!targetFrame || !targetFrame->editor()->canEdit())
+        return;
+
+    WebKitWebViewPrivate* priv = m_webView->priv;
+
+    m_preventNextCompositionCommit = false;
+
+    // Some IM contexts (e.g. 'simple') will act as if they filter every
+    // keystroke and just issue a 'commit' signal during handling. In situations
+    // where the 'commit' signal happens during filtering and there is no active
+    // composition, act as if the keystroke was not filtered. The one exception to
+    // this is when the keyval parameter of the GdkKeyEvent is 0, which is often
+    // a key event sent by the IM context for committing the current composition.
+
+    // Here is a typical sequence of events for the 'simple' context:
+    // 1. GDK key press event -> webkit_web_view_key_press_event
+    // 2. Keydown event -> EditorClient::handleInputMethodKeydown
+    //     gtk_im_context_filter_keypress returns true, but there is a pending
+    //     composition so event->preventDefault is not called (below).
+    // 3. Keydown event bubbles through the DOM
+    // 4. Keydown event -> EditorClient::handleKeyboardEvent
+    //     No action taken.
+    // 4. GDK key release event -> webkit_web_view_key_release_event
+    // 5. gtk_im_context_filter_keypress is called on the release event.
+    //     Simple does not filter most key releases, so the event continues.
+    // 6. Keypress event bubbles through the DOM.
+    // 7. Keypress event -> EditorClient::handleKeyboardEvent
+    //     pending composition is inserted.
+    // 8. Keyup event bubbles through the DOM.
+    // 9. Keyup event -> EditorClient::handleKeyboardEvent
+    //     No action taken.
+
+    // There are two situations where we do filter the keystroke:
+    // 1. The IMContext instructed us to filter and we have no pending composition.
+    // 2. The IMContext did not instruct us to filter, but the keystroke caused a
+    //    composition in progress to finish. It seems that sometimes SCIM will finish
+    //    a composition and not mark the keystroke as filtered.
+    m_treatContextCommitAsKeyEvent = (!targetFrame->editor()->hasComposition())
+         && event->keyEvent()->gdkEventKey()->keyval;
+    clearPendingComposition();
+    if ((gtk_im_context_filter_keypress(priv->imContext.get(), event->keyEvent()->gdkEventKey()) && !m_pendingComposition)
+        || (!m_treatContextCommitAsKeyEvent && !targetFrame->editor()->hasComposition()))
+        event->preventDefault();
+
+    m_treatContextCommitAsKeyEvent = false;
 }
 
-void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
+void EditorClient::handleInputMethodMousePress()
 {
     Frame* targetFrame = core(m_webView)->focusController()->focusedOrMainFrame();
+
     if (!targetFrame || !targetFrame->editor()->canEdit())
         return;
 
     WebKitWebViewPrivate* priv = m_webView->priv;
-    // TODO: Dispatch IE-compatible text input events for IM events.
-    if (gtk_im_context_filter_keypress(priv->imContext, event->keyEvent()->gdkEventKey()))
-        event->setDefaultHandled();
+
+    // When a mouse press fires, the commit signal happens during a composition.
+    // In this case, if the focused node is changed, the commit signal happens in a diffrent node.
+    // Therefore, we need to confirm the current compositon and ignore the next commit signal. 
+    GOwnPtr<gchar> newPreedit(0);
+    gtk_im_context_get_preedit_string(priv->imContext.get(), &newPreedit.outPtr(), 0, 0);
+    
+    if (g_utf8_strlen(newPreedit.get(), -1)) {
+        targetFrame->editor()->confirmComposition();
+        m_preventNextCompositionCommit = true;
+        gtk_im_context_reset(priv->imContext.get());
+    } 
 }
 
 EditorClient::EditorClient(WebKitWebView* webView)
-    : m_webView(webView)
+    : m_isInRedo(false)
+    , m_webView(webView)
+    , m_preventNextCompositionCommit(false)
+    , m_treatContextCommitAsKeyEvent(false)
+    , m_nativeWidget(gtk_text_view_new())
 {
     WebKitWebViewPrivate* priv = m_webView->priv;
-    g_signal_connect(priv->imContext, "commit", G_CALLBACK(imContextCommitted), this);
-    g_signal_connect(priv->imContext, "preedit-changed", G_CALLBACK(imContextPreeditChanged), this);
+    g_signal_connect(priv->imContext.get(), "commit", G_CALLBACK(imContextCommitted), this);
+    g_signal_connect(priv->imContext.get(), "preedit-changed", G_CALLBACK(imContextPreeditChanged), this);
+
+    g_signal_connect(m_nativeWidget.get(), "backspace", G_CALLBACK(backspaceCallback), this);
+    g_signal_connect(m_nativeWidget.get(), "cut-clipboard", G_CALLBACK(cutClipboardCallback), this);
+    g_signal_connect(m_nativeWidget.get(), "copy-clipboard", G_CALLBACK(copyClipboardCallback), this);
+    g_signal_connect(m_nativeWidget.get(), "paste-clipboard", G_CALLBACK(pasteClipboardCallback), this);
+    g_signal_connect(m_nativeWidget.get(), "select-all", G_CALLBACK(selectAllCallback), this);
+    g_signal_connect(m_nativeWidget.get(), "move-cursor", G_CALLBACK(moveCursorCallback), this);
+    g_signal_connect(m_nativeWidget.get(), "delete-from-cursor", G_CALLBACK(deleteFromCursorCallback), this);
+    g_signal_connect(m_nativeWidget.get(), "toggle-overwrite", G_CALLBACK(toggleOverwriteCallback), this);
+    g_signal_connect(m_nativeWidget.get(), "popup-menu", G_CALLBACK(popupMenuCallback), this);
+    g_signal_connect(m_nativeWidget.get(), "show-help", G_CALLBACK(showHelpCallback), this);
 }
 
 EditorClient::~EditorClient()
 {
     WebKitWebViewPrivate* priv = m_webView->priv;
-    g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextCommitted, this);
-    g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextPreeditChanged, this);
+    g_signal_handlers_disconnect_by_func(priv->imContext.get(), (gpointer)imContextCommitted, this);
+    g_signal_handlers_disconnect_by_func(priv->imContext.get(), (gpointer)imContextPreeditChanged, this);
 }
 
 void EditorClient::textFieldDidBeginEditing(Element*)
@@ -564,28 +896,32 @@ void EditorClient::textDidChangeInTextArea(Element*)
 
 void EditorClient::ignoreWordInSpellDocument(const String& text)
 {
-    GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
+    GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
 
-    for (; langs; langs = langs->next) {
-        SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
+    for (; dicts; dicts = dicts->next) {
+        EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
 
-        enchant_dict_add_to_session(lang->speller, text.utf8().data(), -1);
+        enchant_dict_add_to_session(dict, text.utf8().data(), -1);
     }
 }
 
 void EditorClient::learnWord(const String& text)
 {
-    GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
+    GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
 
-    for (; langs; langs = langs->next) {
-        SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
+    for (; dicts; dicts = dicts->next) {
+        EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
 
-        enchant_dict_add_to_personal(lang->speller, text.utf8().data(), -1);
+        enchant_dict_add_to_personal(dict, text.utf8().data(), -1);
     }
 }
 
 void EditorClient::checkSpellingOfString(const UChar* text, int length, int* misspellingLocation, int* misspellingLength)
 {
+    GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
+    if (!dicts)
+        return;
+
     gchar* ctext = g_utf16_to_utf8(const_cast<gunichar2*>(text), length, 0, 0, 0);
     int utflen = g_utf8_strlen(ctext, -1);
 
@@ -603,7 +939,6 @@ void EditorClient::checkSpellingOfString(const UChar* text, int length, int* mis
             int start = i;
             int end = i;
             int wordLength;
-            GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
 
             while (attrs[end].is_word_end < 1)
                 end++;
@@ -613,8 +948,8 @@ void EditorClient::checkSpellingOfString(const UChar* text, int length, int* mis
             // check characters twice.
             i = end;
 
-            for (; langs; langs = langs->next) {
-                SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
+            for (; dicts; dicts = dicts->next) {
+                EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
                 gchar* cstart = g_utf8_offset_to_pointer(ctext, start);
                 gint bytes = static_cast<gint>(g_utf8_offset_to_pointer(ctext, end) - cstart);
                 gchar* word = g_new0(gchar, bytes+1);
@@ -622,7 +957,7 @@ void EditorClient::checkSpellingOfString(const UChar* text, int length, int* mis
 
                 g_utf8_strncpy(word, cstart, end - start);
 
-                result = enchant_dict_check(lang->speller, word, -1);
+                result = enchant_dict_check(dict, word, -1);
                 g_free(word);
                 if (result) {
                     *misspellingLocation = start;
@@ -674,23 +1009,23 @@ bool EditorClient::spellingUIIsShowing()
     return false;
 }
 
-void EditorClient::getGuessesForWord(const String& word, WTF::Vector<String>& guesses)
+void EditorClient::getGuessesForWord(const String& word, const String& context, WTF::Vector<String>& guesses)
 {
-    GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
+    GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
     guesses.clear();
 
-    for (; langs; langs = langs->next) {
+    for (; dicts; dicts = dicts->next) {
         size_t numberOfSuggestions;
         size_t i;
 
-        SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
-        gchar** suggestions = enchant_dict_suggest(lang->speller, word.utf8().data(), -1, &numberOfSuggestions);
+        EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
+        gchar** suggestions = enchant_dict_suggest(dict, word.utf8().data(), -1, &numberOfSuggestions);
 
         for (i = 0; i < numberOfSuggestions && i < 10; i++)
             guesses.append(String::fromUTF8(suggestions[i]));
 
         if (numberOfSuggestions > 0)
-            enchant_dict_free_suggestions(lang->speller, suggestions);
+            enchant_dict_free_suggestions(dict, suggestions);
     }
 }