51172b4faf22436cde08b96501df6c63e31dd99e
[WebKit-https.git] / WebKit / gtk / WebCoreSupport / EditorClientGtk.cpp
1 /*
2  *  Copyright (C) 2007 Alp Toker <alp@atoker.com>
3  *  Copyright (C) 2008 Nuanti Ltd.
4  *  Copyright (C) 2009 Diego Escalante Urrelo <diegoe@gnome.org>
5  *  Copyright (C) 2006, 2007 Apple Inc.  All rights reserved.
6  *  Copyright (C) 2009, Igalia S.L.
7  *  Copyright (C) 2010, Martin Robinson <mrobinson@webkit.org>
8  *
9  *  This library is free software; you can redistribute it and/or
10  *  modify it under the terms of the GNU Lesser General Public
11  *  License as published by the Free Software Foundation; either
12  *  version 2 of the License, or (at your option) any later version.
13  *
14  *  This library is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  *  Lesser General Public License for more details.
18  *
19  *  You should have received a copy of the GNU Lesser General Public
20  *  License along with this library; if not, write to the Free Software
21  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22  */
23
24 #include "config.h"
25 #include "EditorClientGtk.h"
26
27 #include "DataObjectGtk.h"
28 #include "EditCommand.h"
29 #include "Editor.h"
30 #include <enchant.h>
31 #include "EventNames.h"
32 #include "FocusController.h"
33 #include "Frame.h"
34 #include <glib.h>
35 #include "KeyboardEvent.h"
36 #include "markup.h"
37 #include "NotImplemented.h"
38 #include "Page.h"
39 #include "PasteboardHelperGtk.h"
40 #include "PlatformKeyboardEvent.h"
41 #include "WindowsKeyboardCodes.h"
42 #include "webkitmarshal.h"
43 #include "webkitprivate.h"
44 #include <wtf/text/CString.h>
45
46 // Arbitrary depth limit for the undo stack, to keep it from using
47 // unbounded memory.  This is the maximum number of distinct undoable
48 // actions -- unbroken stretches of typed characters are coalesced
49 // into a single action.
50 #define maximumUndoStackDepth 1000
51
52 using namespace WebCore;
53
54 namespace WebKit {
55
56 static void imContextCommitted(GtkIMContext* context, const gchar* compositionString, EditorClient* client)
57 {
58     Frame* frame = core(client->webView())->focusController()->focusedOrMainFrame();
59     if (!frame || !frame->editor()->canEdit())
60         return;
61
62     // If this signal fires during a keydown event when we are not in the middle
63     // of a composition, then treat this 'commit' as a normal key event and just
64     // change the editable area right before the keypress event.
65     if (client->treatContextCommitAsKeyEvent()) {
66         client->updatePendingComposition(compositionString);
67         return;
68     }
69
70     frame->editor()->confirmComposition(String::fromUTF8(compositionString));
71     client->clearPendingComposition();
72 }
73
74 static void imContextPreeditChanged(GtkIMContext* context, EditorClient* client)
75 {
76     Frame* frame = core(client->webView())->focusController()->focusedOrMainFrame();
77     if (!frame || !frame->editor()->canEdit())
78         return;
79
80     // We ignore the provided PangoAttrList for now.
81     GOwnPtr<gchar> newPreedit(0);
82     gtk_im_context_get_preedit_string(context, &newPreedit.outPtr(), 0, 0);
83
84     String preeditString = String::fromUTF8(newPreedit.get());
85     Vector<CompositionUnderline> underlines;
86     underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
87     frame->editor()->setComposition(preeditString, underlines, 0, 0);
88 }
89
90 void EditorClient::updatePendingComposition(const gchar* newComposition)
91 {
92     // The IMContext may signal more than one completed composition in a row,
93     // in which case we want to append them, rather than overwrite the old one.
94     if (!m_pendingComposition)
95         m_pendingComposition.set(g_strdup(newComposition));
96     else
97         m_pendingComposition.set(g_strconcat(m_pendingComposition.get(), newComposition, NULL));
98 }
99
100 void EditorClient::setInputMethodState(bool active)
101 {
102     WebKitWebViewPrivate* priv = m_webView->priv;
103
104     if (active)
105         gtk_im_context_focus_in(priv->imContext);
106     else
107         gtk_im_context_focus_out(priv->imContext);
108
109 #ifdef MAEMO_CHANGES
110     if (active)
111         hildon_gtk_im_context_show(priv->imContext);
112     else
113         hildon_gtk_im_context_hide(priv->imContext);
114 #endif
115 }
116
117 bool EditorClient::shouldDeleteRange(Range*)
118 {
119     notImplemented();
120     return true;
121 }
122
123 bool EditorClient::shouldShowDeleteInterface(HTMLElement*)
124 {
125     return false;
126 }
127
128 bool EditorClient::isContinuousSpellCheckingEnabled()
129 {
130     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
131
132     gboolean enabled;
133     g_object_get(settings, "enable-spell-checking", &enabled, NULL);
134
135     return enabled;
136 }
137
138 bool EditorClient::isGrammarCheckingEnabled()
139 {
140     notImplemented();
141     return false;
142 }
143
144 int EditorClient::spellCheckerDocumentTag()
145 {
146     notImplemented();
147     return 0;
148 }
149
150 bool EditorClient::shouldBeginEditing(WebCore::Range*)
151 {
152     clearPendingComposition();
153
154     notImplemented();
155     return true;
156 }
157
158 bool EditorClient::shouldEndEditing(WebCore::Range*)
159 {
160     clearPendingComposition();
161
162     notImplemented();
163     return true;
164 }
165
166 bool EditorClient::shouldInsertText(const String&, Range*, EditorInsertAction)
167 {
168     notImplemented();
169     return true;
170 }
171
172 bool EditorClient::shouldChangeSelectedRange(Range*, Range*, EAffinity, bool)
173 {
174     notImplemented();
175     return true;
176 }
177
178 bool EditorClient::shouldApplyStyle(WebCore::CSSStyleDeclaration*, WebCore::Range*)
179 {
180     notImplemented();
181     return true;
182 }
183
184 bool EditorClient::shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*)
185 {
186     notImplemented();
187     return true;
188 }
189
190 void EditorClient::didBeginEditing()
191 {
192     notImplemented();
193 }
194
195 void EditorClient::respondToChangedContents()
196 {
197     notImplemented();
198 }
199
200 static WebKitWebView* viewSettingClipboard = 0;
201 static void collapseSelection(GtkClipboard* clipboard, WebKitWebView* webView)
202 {
203     if (viewSettingClipboard && viewSettingClipboard == webView)
204         return;
205
206     WebCore::Page* corePage = core(webView);
207     if (!corePage || !corePage->focusController())
208         return;
209
210     Frame* frame = corePage->focusController()->focusedOrMainFrame();
211
212     // Collapse the selection without clearing it
213     ASSERT(frame);
214     frame->selection()->setBase(frame->selection()->extent(), frame->selection()->affinity());
215 }
216
217 void EditorClient::respondToChangedSelection()
218 {
219     WebKitWebViewPrivate* priv = m_webView->priv;
220     WebCore::Page* corePage = core(m_webView);
221     Frame* targetFrame = corePage->focusController()->focusedOrMainFrame();
222
223     if (!targetFrame)
224         return;
225
226     if (targetFrame->editor()->ignoreCompositionSelectionChange())
227         return;
228
229 #if PLATFORM(X11)
230     GtkClipboard* clipboard = gtk_widget_get_clipboard(GTK_WIDGET(m_webView), GDK_SELECTION_PRIMARY);
231     DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
232
233     if (targetFrame->selection()->isRange()) {
234         dataObject->clear();
235         dataObject->setRange(targetFrame->selection()->toNormalizedRange());
236
237         viewSettingClipboard = m_webView;
238         GClosure* callback = g_cclosure_new_object(G_CALLBACK(collapseSelection), G_OBJECT(m_webView));
239         g_closure_set_marshal(callback, g_cclosure_marshal_VOID__VOID);
240         pasteboardHelperInstance()->writeClipboardContents(clipboard, callback);
241         viewSettingClipboard = 0;
242     }
243 #endif
244
245     if (!targetFrame->editor()->hasComposition())
246         return;
247
248     unsigned start;
249     unsigned end;
250     if (!targetFrame->editor()->getCompositionSelection(start, end)) {
251         // gtk_im_context_reset() clears the composition for us.
252         gtk_im_context_reset(priv->imContext);
253         targetFrame->editor()->confirmCompositionWithoutDisturbingSelection();
254     }
255 }
256
257 void EditorClient::didEndEditing()
258 {
259     notImplemented();
260 }
261
262 void EditorClient::didWriteSelectionToPasteboard()
263 {
264     notImplemented();
265 }
266
267 void EditorClient::didSetSelectionTypesForPasteboard()
268 {
269     notImplemented();
270 }
271
272 bool EditorClient::isEditable()
273 {
274     return webkit_web_view_get_editable(m_webView);
275 }
276
277 void EditorClient::registerCommandForUndo(WTF::PassRefPtr<WebCore::EditCommand> command)
278 {
279     if (undoStack.size() == maximumUndoStackDepth)
280         undoStack.removeFirst();
281     if (!m_isInRedo)
282         redoStack.clear();
283     undoStack.append(command);
284 }
285
286 void EditorClient::registerCommandForRedo(WTF::PassRefPtr<WebCore::EditCommand> command)
287 {
288     redoStack.append(command);
289 }
290
291 void EditorClient::clearUndoRedoOperations()
292 {
293     undoStack.clear();
294     redoStack.clear();
295 }
296
297 bool EditorClient::canUndo() const
298 {
299     return !undoStack.isEmpty();
300 }
301
302 bool EditorClient::canRedo() const
303 {
304     return !redoStack.isEmpty();
305 }
306
307 void EditorClient::undo()
308 {
309     if (canUndo()) {
310         RefPtr<WebCore::EditCommand> command(*(--undoStack.end()));
311         undoStack.remove(--undoStack.end());
312         // unapply will call us back to push this command onto the redo stack.
313         command->unapply();
314     }
315 }
316
317 void EditorClient::redo()
318 {
319     if (canRedo()) {
320         RefPtr<WebCore::EditCommand> command(*(--redoStack.end()));
321         redoStack.remove(--redoStack.end());
322
323         ASSERT(!m_isInRedo);
324         m_isInRedo = true;
325         // reapply will call us back to push this command onto the undo stack.
326         command->reapply();
327         m_isInRedo = false;
328     }
329 }
330
331 bool EditorClient::shouldInsertNode(Node*, Range*, EditorInsertAction)
332 {
333     notImplemented();
334     return true;
335 }
336
337 void EditorClient::pageDestroyed()
338 {
339     delete this;
340 }
341
342 bool EditorClient::smartInsertDeleteEnabled()
343 {
344     notImplemented();
345     return false;
346 }
347
348 bool EditorClient::isSelectTrailingWhitespaceEnabled()
349 {
350     notImplemented();
351     return false;
352 }
353
354 void EditorClient::toggleContinuousSpellChecking()
355 {
356     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
357
358     gboolean enabled;
359     g_object_get(settings, "enable-spell-checking", &enabled, NULL);
360
361     g_object_set(settings, "enable-spell-checking", !enabled, NULL);
362 }
363
364 void EditorClient::toggleGrammarChecking()
365 {
366 }
367
368 static const unsigned CtrlKey = 1 << 0;
369 static const unsigned AltKey = 1 << 1;
370 static const unsigned ShiftKey = 1 << 2;
371
372 struct KeyDownEntry {
373     unsigned virtualKey;
374     unsigned modifiers;
375     const char* name;
376 };
377
378 struct KeyPressEntry {
379     unsigned charCode;
380     unsigned modifiers;
381     const char* name;
382 };
383
384 static const KeyDownEntry keyDownEntries[] = {
385     { VK_LEFT,   0,                  "MoveLeft"                                    },
386     { VK_LEFT,   ShiftKey,           "MoveLeftAndModifySelection"                  },
387     { VK_LEFT,   CtrlKey,            "MoveWordLeft"                                },
388     { VK_LEFT,   CtrlKey | ShiftKey, "MoveWordLeftAndModifySelection"              },
389     { VK_RIGHT,  0,                  "MoveRight"                                   },
390     { VK_RIGHT,  ShiftKey,           "MoveRightAndModifySelection"                 },
391     { VK_RIGHT,  CtrlKey,            "MoveWordRight"                               },
392     { VK_RIGHT,  CtrlKey | ShiftKey, "MoveWordRightAndModifySelection"             },
393     { VK_UP,     0,                  "MoveUp"                                      },
394     { VK_UP,     ShiftKey,           "MoveUpAndModifySelection"                    },
395     { VK_PRIOR,  ShiftKey,           "MovePageUpAndModifySelection"                },
396     { VK_DOWN,   0,                  "MoveDown"                                    },
397     { VK_DOWN,   ShiftKey,           "MoveDownAndModifySelection"                  },
398     { VK_NEXT,   ShiftKey,           "MovePageDownAndModifySelection"              },
399     { VK_PRIOR,  0,                  "MovePageUp"                                  },
400     { VK_NEXT,   0,                  "MovePageDown"                                },
401     { VK_HOME,   0,                  "MoveToBeginningOfLine"                       },
402     { VK_HOME,   ShiftKey,           "MoveToBeginningOfLineAndModifySelection"     },
403     { VK_HOME,   CtrlKey,            "MoveToBeginningOfDocument"                   },
404     { VK_HOME,   CtrlKey | ShiftKey, "MoveToBeginningOfDocumentAndModifySelection" },
405
406     { VK_END,    0,                  "MoveToEndOfLine"                             },
407     { VK_END,    ShiftKey,           "MoveToEndOfLineAndModifySelection"           },
408     { VK_END,    CtrlKey,            "MoveToEndOfDocument"                         },
409     { VK_END,    CtrlKey | ShiftKey, "MoveToEndOfDocumentAndModifySelection"       },
410
411     { VK_BACK,   0,                  "DeleteBackward"                              },
412     { VK_BACK,   ShiftKey,           "DeleteBackward"                              },
413     { VK_DELETE, 0,                  "DeleteForward"                               },
414     { VK_BACK,   CtrlKey,            "DeleteWordBackward"                          },
415     { VK_DELETE, CtrlKey,            "DeleteWordForward"                           },
416
417     { 'B',       CtrlKey,            "ToggleBold"                                  },
418     { 'I',       CtrlKey,            "ToggleItalic"                                },
419
420     { VK_ESCAPE, 0,                  "Cancel"                                      },
421     { VK_OEM_PERIOD, CtrlKey,        "Cancel"                                      },
422     { VK_TAB,    0,                  "InsertTab"                                   },
423     { VK_TAB,    ShiftKey,           "InsertBacktab"                               },
424     { VK_RETURN, 0,                  "InsertNewline"                               },
425     { VK_RETURN, CtrlKey,            "InsertNewline"                               },
426     { VK_RETURN, AltKey,             "InsertNewline"                               },
427     { VK_RETURN, AltKey | ShiftKey,  "InsertNewline"                               },
428 };
429
430 static const KeyPressEntry keyPressEntries[] = {
431     { '\t',   0,                  "InsertTab"                                   },
432     { '\t',   ShiftKey,           "InsertBacktab"                               },
433     { '\r',   0,                  "InsertNewline"                               },
434     { '\r',   CtrlKey,            "InsertNewline"                               },
435     { '\r',   AltKey,             "InsertNewline"                               },
436     { '\r',   AltKey | ShiftKey,  "InsertNewline"                               },
437 };
438
439 static const char* interpretEditorCommandKeyEvent(const KeyboardEvent* evt)
440 {
441     ASSERT(evt->type() == eventNames().keydownEvent || evt->type() == eventNames().keypressEvent);
442
443     static HashMap<int, const char*>* keyDownCommandsMap = 0;
444     static HashMap<int, const char*>* keyPressCommandsMap = 0;
445
446     if (!keyDownCommandsMap) {
447         keyDownCommandsMap = new HashMap<int, const char*>;
448         keyPressCommandsMap = new HashMap<int, const char*>;
449
450         for (unsigned i = 0; i < G_N_ELEMENTS(keyDownEntries); i++)
451             keyDownCommandsMap->set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name);
452
453         for (unsigned i = 0; i < G_N_ELEMENTS(keyPressEntries); i++)
454             keyPressCommandsMap->set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name);
455     }
456
457     unsigned modifiers = 0;
458     if (evt->shiftKey())
459         modifiers |= ShiftKey;
460     if (evt->altKey())
461         modifiers |= AltKey;
462     if (evt->ctrlKey())
463         modifiers |= CtrlKey;
464
465     if (evt->type() == eventNames().keydownEvent) {
466         int mapKey = modifiers << 16 | evt->keyCode();
467         return mapKey ? keyDownCommandsMap->get(mapKey) : 0;
468     }
469
470     int mapKey = modifiers << 16 | evt->charCode();
471     return mapKey ? keyPressCommandsMap->get(mapKey) : 0;
472 }
473
474 void EditorClient::handleKeyboardEvent(KeyboardEvent* event)
475 {
476     Node* node = event->target()->toNode();
477     ASSERT(node);
478     Frame* frame = node->document()->frame();
479     ASSERT(frame);
480
481     const PlatformKeyboardEvent* platformEvent = event->keyEvent();
482     if (!platformEvent)
483         return;
484
485     // Don't allow editor commands or text insertion for nodes that
486     // cannot edit, unless we are in caret mode.
487     if (!frame->editor()->canEdit() && !(frame->settings() && frame->settings()->caretBrowsingEnabled()))
488         return;
489
490     const gchar* editorCommandString = interpretEditorCommandKeyEvent(event);
491     if (editorCommandString) {
492         Editor::Command command = frame->editor()->command(editorCommandString);
493
494         // On editor commands from key down events, we only want to let the event bubble up to
495         // the DOM if it inserts text. If it doesn't insert text (e.g. Tab that changes focus)
496         // we just want WebKit to handle it immediately without a DOM event.
497         if (platformEvent->type() == PlatformKeyboardEvent::RawKeyDown) {
498             if (!command.isTextInsertion() && command.execute(event))
499                 event->setDefaultHandled();
500
501             clearPendingComposition();
502             return;
503         }
504
505         if (command.execute(event)) {
506             clearPendingComposition();
507             event->setDefaultHandled();
508             return;
509         }
510     }
511
512     // This is just a normal text insertion, so wait to execute the insertion
513     // until a keypress event happens. This will ensure that the insertion will not
514     // be reflected in the contents of the field until the keyup DOM event.
515     if (event->type() == eventNames().keypressEvent) {
516
517         // If we have a pending composition at this point, it happened while
518         // filtering a keypress, so we treat it as a normal text insertion.
519         // This will also ensure that if the keypress event handler changed the
520         // currently focused node, the text is still inserted into the original
521         // node (insertText() has this logic, but confirmComposition() does not).
522         if (m_pendingComposition) {
523             frame->editor()->insertText(String::fromUTF8(m_pendingComposition.get()), event);
524             clearPendingComposition();
525             event->setDefaultHandled();
526
527         } else {
528             // Don't insert null or control characters as they can result in unexpected behaviour
529             if (event->charCode() < ' ')
530                 return;
531
532             // Don't insert anything if a modifier is pressed
533             if (platformEvent->ctrlKey() || platformEvent->altKey())
534                 return;
535
536             if (frame->editor()->insertText(platformEvent->text(), event))
537                 event->setDefaultHandled();
538         }
539     }
540 }
541
542 void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
543 {
544     Frame* targetFrame = core(m_webView)->focusController()->focusedOrMainFrame();
545     if (!targetFrame || !targetFrame->editor()->canEdit())
546         return;
547
548     WebKitWebViewPrivate* priv = m_webView->priv;
549
550
551     // Some IM contexts (e.g. 'simple') will act as if they filter every
552     // keystroke and just issue a 'commit' signal during handling. In situations
553     // where the 'commit' signal happens during filtering and there is no active
554     // composition, act as if the keystroke was not filtered. The one exception to
555     // this is when the keyval parameter of the GdkKeyEvent is 0, which is often
556     // a key event sent by the IM context for committing the current composition.
557
558     // Here is a typical sequence of events for the 'simple' context:
559     // 1. GDK key press event -> webkit_web_view_key_press_event
560     // 2. Keydown event -> EditorClient::handleInputMethodKeydown
561     //     gtk_im_context_filter_keypress returns true, but there is a pending
562     //     composition so event->preventDefault is not called (below).
563     // 3. Keydown event bubbles through the DOM
564     // 4. Keydown event -> EditorClient::handleKeyboardEvent
565     //     No action taken.
566     // 4. GDK key release event -> webkit_web_view_key_release_event
567     // 5. gtk_im_context_filter_keypress is called on the release event.
568     //     Simple does not filter most key releases, so the event continues.
569     // 6. Keypress event bubbles through the DOM.
570     // 7. Keypress event -> EditorClient::handleKeyboardEvent
571     //     pending composition is inserted.
572     // 8. Keyup event bubbles through the DOM.
573     // 9. Keyup event -> EditorClient::handleKeyboardEvent
574     //     No action taken.
575
576     // There are two situations where we do filter the keystroke:
577     // 1. The IMContext instructed us to filter and we have no pending composition.
578     // 2. The IMContext did not instruct us to filter, but the keystroke caused a
579     //    composition in progress to finish. It seems that sometimes SCIM will finish
580     //    a composition and not mark the keystroke as filtered.
581     m_treatContextCommitAsKeyEvent = (!targetFrame->editor()->hasComposition())
582          && event->keyEvent()->gdkEventKey()->keyval;
583     clearPendingComposition();
584     if ((gtk_im_context_filter_keypress(priv->imContext, event->keyEvent()->gdkEventKey()) && !m_pendingComposition)
585         || (!m_treatContextCommitAsKeyEvent && !targetFrame->editor()->hasComposition()))
586         event->preventDefault();
587
588     m_treatContextCommitAsKeyEvent = false;
589 }
590
591 EditorClient::EditorClient(WebKitWebView* webView)
592     : m_isInRedo(false)
593     , m_webView(webView)
594     , m_treatContextCommitAsKeyEvent(false)
595 {
596     WebKitWebViewPrivate* priv = m_webView->priv;
597     g_signal_connect(priv->imContext, "commit", G_CALLBACK(imContextCommitted), this);
598     g_signal_connect(priv->imContext, "preedit-changed", G_CALLBACK(imContextPreeditChanged), this);
599 }
600
601 EditorClient::~EditorClient()
602 {
603     WebKitWebViewPrivate* priv = m_webView->priv;
604     g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextCommitted, this);
605     g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextPreeditChanged, this);
606 }
607
608 void EditorClient::textFieldDidBeginEditing(Element*)
609 {
610 }
611
612 void EditorClient::textFieldDidEndEditing(Element*)
613 {
614 }
615
616 void EditorClient::textDidChangeInTextField(Element*)
617 {
618 }
619
620 bool EditorClient::doTextFieldCommandFromEvent(Element*, KeyboardEvent*)
621 {
622     return false;
623 }
624
625 void EditorClient::textWillBeDeletedInTextField(Element*)
626 {
627     notImplemented();
628 }
629
630 void EditorClient::textDidChangeInTextArea(Element*)
631 {
632     notImplemented();
633 }
634
635 void EditorClient::ignoreWordInSpellDocument(const String& text)
636 {
637     GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
638
639     for (; dicts; dicts = dicts->next) {
640         EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
641
642         enchant_dict_add_to_session(dict, text.utf8().data(), -1);
643     }
644 }
645
646 void EditorClient::learnWord(const String& text)
647 {
648     GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
649
650     for (; dicts; dicts = dicts->next) {
651         EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
652
653         enchant_dict_add_to_personal(dict, text.utf8().data(), -1);
654     }
655 }
656
657 void EditorClient::checkSpellingOfString(const UChar* text, int length, int* misspellingLocation, int* misspellingLength)
658 {
659     GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
660     if (!dicts)
661         return;
662
663     gchar* ctext = g_utf16_to_utf8(const_cast<gunichar2*>(text), length, 0, 0, 0);
664     int utflen = g_utf8_strlen(ctext, -1);
665
666     PangoLanguage* language = pango_language_get_default();
667     PangoLogAttr* attrs = g_new(PangoLogAttr, utflen+1);
668
669     // pango_get_log_attrs uses an aditional position at the end of the text.
670     pango_get_log_attrs(ctext, -1, -1, language, attrs, utflen+1);
671
672     for (int i = 0; i < length+1; i++) {
673         // We go through each character until we find an is_word_start,
674         // then we get into an inner loop to find the is_word_end corresponding
675         // to it.
676         if (attrs[i].is_word_start) {
677             int start = i;
678             int end = i;
679             int wordLength;
680
681             while (attrs[end].is_word_end < 1)
682                 end++;
683
684             wordLength = end - start;
685             // Set the iterator to be at the current word end, so we don't
686             // check characters twice.
687             i = end;
688
689             for (; dicts; dicts = dicts->next) {
690                 EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
691                 gchar* cstart = g_utf8_offset_to_pointer(ctext, start);
692                 gint bytes = static_cast<gint>(g_utf8_offset_to_pointer(ctext, end) - cstart);
693                 gchar* word = g_new0(gchar, bytes+1);
694                 int result;
695
696                 g_utf8_strncpy(word, cstart, end - start);
697
698                 result = enchant_dict_check(dict, word, -1);
699                 g_free(word);
700                 if (result) {
701                     *misspellingLocation = start;
702                     *misspellingLength = wordLength;
703                 } else {
704                     // Stop checking, this word is ok in at least one dict.
705                     *misspellingLocation = -1;
706                     *misspellingLength = 0;
707                     break;
708                 }
709             }
710         }
711     }
712
713     g_free(attrs);
714     g_free(ctext);
715 }
716
717 String EditorClient::getAutoCorrectSuggestionForMisspelledWord(const String& inputWord)
718 {
719     // This method can be implemented using customized algorithms for the particular browser.
720     // Currently, it computes an empty string.
721     return String();
722 }
723
724 void EditorClient::checkGrammarOfString(const UChar*, int, Vector<GrammarDetail>&, int*, int*)
725 {
726     notImplemented();
727 }
728
729 void EditorClient::updateSpellingUIWithGrammarString(const String&, const GrammarDetail&)
730 {
731     notImplemented();
732 }
733
734 void EditorClient::updateSpellingUIWithMisspelledWord(const String&)
735 {
736     notImplemented();
737 }
738
739 void EditorClient::showSpellingUI(bool)
740 {
741     notImplemented();
742 }
743
744 bool EditorClient::spellingUIIsShowing()
745 {
746     notImplemented();
747     return false;
748 }
749
750 void EditorClient::getGuessesForWord(const String& word, WTF::Vector<String>& guesses)
751 {
752     GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
753     guesses.clear();
754
755     for (; dicts; dicts = dicts->next) {
756         size_t numberOfSuggestions;
757         size_t i;
758
759         EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
760         gchar** suggestions = enchant_dict_suggest(dict, word.utf8().data(), -1, &numberOfSuggestions);
761
762         for (i = 0; i < numberOfSuggestions && i < 10; i++)
763             guesses.append(String::fromUTF8(suggestions[i]));
764
765         if (numberOfSuggestions > 0)
766             enchant_dict_free_suggestions(dict, suggestions);
767     }
768 }
769
770 }