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