• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
imContextCommitted(GtkIMContext * context,const gchar * str,EditorClient * client)52 static void imContextCommitted(GtkIMContext* context, const gchar* str, EditorClient* client)
53 {
54     Frame* targetFrame = core(client->m_webView)->focusController()->focusedOrMainFrame();
55 
56     if (!targetFrame || !targetFrame->editor()->canEdit())
57         return;
58 
59     Editor* editor = targetFrame->editor();
60 
61     String commitString = String::fromUTF8(str);
62     editor->confirmComposition(commitString);
63 }
64 
imContextPreeditChanged(GtkIMContext * context,EditorClient * client)65 static void imContextPreeditChanged(GtkIMContext* context, EditorClient* client)
66 {
67     Frame* frame = core(client->m_webView)->focusController()->focusedOrMainFrame();
68     Editor* editor = frame->editor();
69 
70     gchar* preedit = NULL;
71     gint cursorPos = 0;
72     // We ignore the provided PangoAttrList for now.
73     gtk_im_context_get_preedit_string(context, &preedit, NULL, &cursorPos);
74     String preeditString = String::fromUTF8(preedit);
75     g_free(preedit);
76 
77     // setComposition() will replace the user selection if passed an empty
78     // preedit. We don't want this to happen.
79     if (preeditString.isEmpty() && !editor->hasComposition())
80         return;
81 
82     Vector<CompositionUnderline> underlines;
83     underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
84     editor->setComposition(preeditString, underlines, cursorPos, 0);
85 }
86 
setInputMethodState(bool active)87 void EditorClient::setInputMethodState(bool active)
88 {
89     WebKitWebViewPrivate* priv = m_webView->priv;
90 
91     if (active)
92         gtk_im_context_focus_in(priv->imContext);
93     else
94         gtk_im_context_focus_out(priv->imContext);
95 
96 #ifdef MAEMO_CHANGES
97     if (active)
98         hildon_gtk_im_context_show(priv->imContext);
99     else
100         hildon_gtk_im_context_hide(priv->imContext);
101 #endif
102 }
103 
shouldDeleteRange(Range *)104 bool EditorClient::shouldDeleteRange(Range*)
105 {
106     notImplemented();
107     return true;
108 }
109 
shouldShowDeleteInterface(HTMLElement *)110 bool EditorClient::shouldShowDeleteInterface(HTMLElement*)
111 {
112     return false;
113 }
114 
isContinuousSpellCheckingEnabled()115 bool EditorClient::isContinuousSpellCheckingEnabled()
116 {
117     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
118 
119     gboolean enabled;
120     g_object_get(settings, "enable-spell-checking", &enabled, NULL);
121 
122     return enabled;
123 }
124 
isGrammarCheckingEnabled()125 bool EditorClient::isGrammarCheckingEnabled()
126 {
127     notImplemented();
128     return false;
129 }
130 
spellCheckerDocumentTag()131 int EditorClient::spellCheckerDocumentTag()
132 {
133     notImplemented();
134     return 0;
135 }
136 
shouldBeginEditing(WebCore::Range *)137 bool EditorClient::shouldBeginEditing(WebCore::Range*)
138 {
139     notImplemented();
140     return true;
141 }
142 
shouldEndEditing(WebCore::Range *)143 bool EditorClient::shouldEndEditing(WebCore::Range*)
144 {
145     notImplemented();
146     return true;
147 }
148 
shouldInsertText(const String &,Range *,EditorInsertAction)149 bool EditorClient::shouldInsertText(const String&, Range*, EditorInsertAction)
150 {
151     notImplemented();
152     return true;
153 }
154 
shouldChangeSelectedRange(Range *,Range *,EAffinity,bool)155 bool EditorClient::shouldChangeSelectedRange(Range*, Range*, EAffinity, bool)
156 {
157     notImplemented();
158     return true;
159 }
160 
shouldApplyStyle(WebCore::CSSStyleDeclaration *,WebCore::Range *)161 bool EditorClient::shouldApplyStyle(WebCore::CSSStyleDeclaration*, WebCore::Range*)
162 {
163     notImplemented();
164     return true;
165 }
166 
shouldMoveRangeAfterDelete(WebCore::Range *,WebCore::Range *)167 bool EditorClient::shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*)
168 {
169     notImplemented();
170     return true;
171 }
172 
didBeginEditing()173 void EditorClient::didBeginEditing()
174 {
175     notImplemented();
176 }
177 
respondToChangedContents()178 void EditorClient::respondToChangedContents()
179 {
180     notImplemented();
181 }
182 
clipboard_get_contents_cb(GtkClipboard * clipboard,GtkSelectionData * selection_data,guint info,gpointer data)183 static void clipboard_get_contents_cb(GtkClipboard* clipboard, GtkSelectionData* selection_data, guint info, gpointer data)
184 {
185     WebKitWebView* webView = reinterpret_cast<WebKitWebView*>(data);
186     Frame* frame = core(webView)->focusController()->focusedOrMainFrame();
187     PassRefPtr<Range> selectedRange = frame->selection()->toNormalizedRange();
188 
189     if (static_cast<gint>(info) == WEBKIT_WEB_VIEW_TARGET_INFO_HTML) {
190         String markup = createMarkup(selectedRange.get(), 0, AnnotateForInterchange);
191         gtk_selection_data_set(selection_data, selection_data->target, 8,
192                                reinterpret_cast<const guchar*>(markup.utf8().data()), markup.utf8().length());
193     } else {
194         String text = selectedRange->text();
195         gtk_selection_data_set_text(selection_data, text.utf8().data(), text.utf8().length());
196     }
197 }
198 
clipboard_clear_contents_cb(GtkClipboard * clipboard,gpointer data)199 static void clipboard_clear_contents_cb(GtkClipboard* clipboard, gpointer data)
200 {
201     WebKitWebView* webView = reinterpret_cast<WebKitWebView*>(data);
202     Frame* frame = core(webView)->focusController()->focusedOrMainFrame();
203 
204     // Collapse the selection without clearing it
205     frame->selection()->setBase(frame->selection()->extent(), frame->selection()->affinity());
206 }
207 
respondToChangedSelection()208 void EditorClient::respondToChangedSelection()
209 {
210     WebKitWebViewPrivate* priv = m_webView->priv;
211     Frame* targetFrame = core(m_webView)->focusController()->focusedOrMainFrame();
212 
213     if (!targetFrame)
214         return;
215 
216     if (targetFrame->editor()->ignoreCompositionSelectionChange())
217         return;
218 
219     GtkClipboard* clipboard = gtk_widget_get_clipboard(GTK_WIDGET(m_webView), GDK_SELECTION_PRIMARY);
220     if (targetFrame->selection()->isRange()) {
221         GtkTargetList* targetList = webkit_web_view_get_copy_target_list(m_webView);
222         gint targetCount;
223         GtkTargetEntry* targets = gtk_target_table_new_from_list(targetList, &targetCount);
224         gtk_clipboard_set_with_owner(clipboard, targets, targetCount,
225                                      clipboard_get_contents_cb, clipboard_clear_contents_cb, G_OBJECT(m_webView));
226         gtk_target_table_free(targets, targetCount);
227     } else if (gtk_clipboard_get_owner(clipboard) == G_OBJECT(m_webView))
228         gtk_clipboard_clear(clipboard);
229 
230     if (!targetFrame->editor()->hasComposition())
231         return;
232 
233     unsigned start;
234     unsigned end;
235     if (!targetFrame->editor()->getCompositionSelection(start, end)) {
236         // gtk_im_context_reset() clears the composition for us.
237         gtk_im_context_reset(priv->imContext);
238         targetFrame->editor()->confirmCompositionWithoutDisturbingSelection();
239     }
240 }
241 
didEndEditing()242 void EditorClient::didEndEditing()
243 {
244     notImplemented();
245 }
246 
didWriteSelectionToPasteboard()247 void EditorClient::didWriteSelectionToPasteboard()
248 {
249     notImplemented();
250 }
251 
didSetSelectionTypesForPasteboard()252 void EditorClient::didSetSelectionTypesForPasteboard()
253 {
254     notImplemented();
255 }
256 
isEditable()257 bool EditorClient::isEditable()
258 {
259     return webkit_web_view_get_editable(m_webView);
260 }
261 
registerCommandForUndo(WTF::PassRefPtr<WebCore::EditCommand> command)262 void EditorClient::registerCommandForUndo(WTF::PassRefPtr<WebCore::EditCommand> command)
263 {
264     if (undoStack.size() == maximumUndoStackDepth)
265         undoStack.removeFirst();
266     if (!m_isInRedo)
267         redoStack.clear();
268     undoStack.append(command);
269 }
270 
registerCommandForRedo(WTF::PassRefPtr<WebCore::EditCommand> command)271 void EditorClient::registerCommandForRedo(WTF::PassRefPtr<WebCore::EditCommand> command)
272 {
273     redoStack.append(command);
274 }
275 
clearUndoRedoOperations()276 void EditorClient::clearUndoRedoOperations()
277 {
278     undoStack.clear();
279     redoStack.clear();
280 }
281 
canUndo() const282 bool EditorClient::canUndo() const
283 {
284     return !undoStack.isEmpty();
285 }
286 
canRedo() const287 bool EditorClient::canRedo() const
288 {
289     return !redoStack.isEmpty();
290 }
291 
undo()292 void EditorClient::undo()
293 {
294     if (canUndo()) {
295         RefPtr<WebCore::EditCommand> command(*(--undoStack.end()));
296         undoStack.remove(--undoStack.end());
297         // unapply will call us back to push this command onto the redo stack.
298         command->unapply();
299     }
300 }
301 
redo()302 void EditorClient::redo()
303 {
304     if (canRedo()) {
305         RefPtr<WebCore::EditCommand> command(*(--redoStack.end()));
306         redoStack.remove(--redoStack.end());
307 
308         ASSERT(!m_isInRedo);
309         m_isInRedo = true;
310         // reapply will call us back to push this command onto the undo stack.
311         command->reapply();
312         m_isInRedo = false;
313     }
314 }
315 
shouldInsertNode(Node *,Range *,EditorInsertAction)316 bool EditorClient::shouldInsertNode(Node*, Range*, EditorInsertAction)
317 {
318     notImplemented();
319     return true;
320 }
321 
pageDestroyed()322 void EditorClient::pageDestroyed()
323 {
324     delete this;
325 }
326 
smartInsertDeleteEnabled()327 bool EditorClient::smartInsertDeleteEnabled()
328 {
329     notImplemented();
330     return false;
331 }
332 
isSelectTrailingWhitespaceEnabled()333 bool EditorClient::isSelectTrailingWhitespaceEnabled()
334 {
335     notImplemented();
336     return false;
337 }
338 
toggleContinuousSpellChecking()339 void EditorClient::toggleContinuousSpellChecking()
340 {
341     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
342 
343     gboolean enabled;
344     g_object_get(settings, "enable-spell-checking", &enabled, NULL);
345 
346     g_object_set(settings, "enable-spell-checking", !enabled, NULL);
347 }
348 
toggleGrammarChecking()349 void EditorClient::toggleGrammarChecking()
350 {
351 }
352 
353 static const unsigned CtrlKey = 1 << 0;
354 static const unsigned AltKey = 1 << 1;
355 static const unsigned ShiftKey = 1 << 2;
356 
357 struct KeyDownEntry {
358     unsigned virtualKey;
359     unsigned modifiers;
360     const char* name;
361 };
362 
363 struct KeyPressEntry {
364     unsigned charCode;
365     unsigned modifiers;
366     const char* name;
367 };
368 
369 static const KeyDownEntry keyDownEntries[] = {
370     { VK_LEFT,   0,                  "MoveLeft"                                    },
371     { VK_LEFT,   ShiftKey,           "MoveLeftAndModifySelection"                  },
372     { VK_LEFT,   CtrlKey,            "MoveWordLeft"                                },
373     { VK_LEFT,   CtrlKey | ShiftKey, "MoveWordLeftAndModifySelection"              },
374     { VK_RIGHT,  0,                  "MoveRight"                                   },
375     { VK_RIGHT,  ShiftKey,           "MoveRightAndModifySelection"                 },
376     { VK_RIGHT,  CtrlKey,            "MoveWordRight"                               },
377     { VK_RIGHT,  CtrlKey | ShiftKey, "MoveWordRightAndModifySelection"             },
378     { VK_UP,     0,                  "MoveUp"                                      },
379     { VK_UP,     ShiftKey,           "MoveUpAndModifySelection"                    },
380     { VK_PRIOR,  ShiftKey,           "MovePageUpAndModifySelection"                },
381     { VK_DOWN,   0,                  "MoveDown"                                    },
382     { VK_DOWN,   ShiftKey,           "MoveDownAndModifySelection"                  },
383     { VK_NEXT,   ShiftKey,           "MovePageDownAndModifySelection"              },
384     { VK_PRIOR,  0,                  "MovePageUp"                                  },
385     { VK_NEXT,   0,                  "MovePageDown"                                },
386     { VK_HOME,   0,                  "MoveToBeginningOfLine"                       },
387     { VK_HOME,   ShiftKey,           "MoveToBeginningOfLineAndModifySelection"     },
388     { VK_HOME,   CtrlKey,            "MoveToBeginningOfDocument"                   },
389     { VK_HOME,   CtrlKey | ShiftKey, "MoveToBeginningOfDocumentAndModifySelection" },
390 
391     { VK_END,    0,                  "MoveToEndOfLine"                             },
392     { VK_END,    ShiftKey,           "MoveToEndOfLineAndModifySelection"           },
393     { VK_END,    CtrlKey,            "MoveToEndOfDocument"                         },
394     { VK_END,    CtrlKey | ShiftKey, "MoveToEndOfDocumentAndModifySelection"       },
395 
396     { VK_BACK,   0,                  "DeleteBackward"                              },
397     { VK_BACK,   ShiftKey,           "DeleteBackward"                              },
398     { VK_DELETE, 0,                  "DeleteForward"                               },
399     { VK_BACK,   CtrlKey,            "DeleteWordBackward"                          },
400     { VK_DELETE, CtrlKey,            "DeleteWordForward"                           },
401 
402     { 'B',       CtrlKey,            "ToggleBold"                                  },
403     { 'I',       CtrlKey,            "ToggleItalic"                                },
404 
405     { VK_ESCAPE, 0,                  "Cancel"                                      },
406     { VK_OEM_PERIOD, CtrlKey,        "Cancel"                                      },
407     { VK_TAB,    0,                  "InsertTab"                                   },
408     { VK_TAB,    ShiftKey,           "InsertBacktab"                               },
409     { VK_RETURN, 0,                  "InsertNewline"                               },
410     { VK_RETURN, CtrlKey,            "InsertNewline"                               },
411     { VK_RETURN, AltKey,             "InsertNewline"                               },
412     { VK_RETURN, AltKey | ShiftKey,  "InsertNewline"                               },
413 
414     // It's not quite clear whether Undo/Redo should be handled
415     // in the application or in WebKit. We chose WebKit.
416     { 'Z',       CtrlKey,            "Undo"                                        },
417     { 'Z',       CtrlKey | ShiftKey, "Redo"                                        },
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 
interpretKeyEvent(const KeyboardEvent * evt)429 static const char* interpretKeyEvent(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 
handleEditingKeyboardEvent(KeyboardEvent * evt)464 static bool handleEditingKeyboardEvent(KeyboardEvent* evt)
465 {
466     Node* node = evt->target()->toNode();
467     ASSERT(node);
468     Frame* frame = node->document()->frame();
469     ASSERT(frame);
470 
471     const PlatformKeyboardEvent* keyEvent = evt->keyEvent();
472     if (!keyEvent)
473         return false;
474 
475     bool caretBrowsing = frame->settings()->caretBrowsingEnabled();
476     if (caretBrowsing) {
477         switch (keyEvent->windowsVirtualKeyCode()) {
478             case VK_LEFT:
479                 frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
480                         SelectionController::LEFT,
481                         keyEvent->ctrlKey() ? WordGranularity : CharacterGranularity,
482                         true);
483                 return true;
484             case VK_RIGHT:
485                 frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
486                         SelectionController::RIGHT,
487                         keyEvent->ctrlKey() ? WordGranularity : CharacterGranularity,
488                         true);
489                 return true;
490             case VK_UP:
491                 frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
492                         SelectionController::BACKWARD,
493                         keyEvent->ctrlKey() ? ParagraphGranularity : LineGranularity,
494                         true);
495                 return true;
496             case VK_DOWN:
497                 frame->selection()->modify(keyEvent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
498                         SelectionController::FORWARD,
499                         keyEvent->ctrlKey() ? ParagraphGranularity : LineGranularity,
500                         true);
501                 return true;
502         }
503     }
504 
505     Editor::Command command = frame->editor()->command(interpretKeyEvent(evt));
506 
507     if (keyEvent->type() == PlatformKeyboardEvent::RawKeyDown) {
508         // WebKit doesn't have enough information about mode to decide how commands that just insert text if executed via Editor should be treated,
509         // so we leave it upon WebCore to either handle them immediately (e.g. Tab that changes focus) or let a keypress event be generated
510         // (e.g. Tab that inserts a Tab character, or Enter).
511         return !command.isTextInsertion() && command.execute(evt);
512     }
513 
514     if (command.execute(evt))
515         return true;
516 
517     // Don't insert null or control characters as they can result in unexpected behaviour
518     if (evt->charCode() < ' ')
519         return false;
520 
521     // Don't insert anything if a modifier is pressed
522     if (keyEvent->ctrlKey() || keyEvent->altKey())
523         return false;
524 
525     return frame->editor()->insertText(evt->keyEvent()->text(), evt);
526 }
527 
handleKeyboardEvent(KeyboardEvent * event)528 void EditorClient::handleKeyboardEvent(KeyboardEvent* event)
529 {
530     if (handleEditingKeyboardEvent(event))
531         event->setDefaultHandled();
532 }
533 
handleInputMethodKeydown(KeyboardEvent * event)534 void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
535 {
536     Frame* targetFrame = core(m_webView)->focusController()->focusedOrMainFrame();
537     if (!targetFrame || !targetFrame->editor()->canEdit())
538         return;
539 
540     WebKitWebViewPrivate* priv = m_webView->priv;
541     // TODO: Dispatch IE-compatible text input events for IM events.
542     if (gtk_im_context_filter_keypress(priv->imContext, event->keyEvent()->gdkEventKey()))
543         event->setDefaultHandled();
544 }
545 
EditorClient(WebKitWebView * webView)546 EditorClient::EditorClient(WebKitWebView* webView)
547     : m_isInRedo(false)
548     , m_webView(webView)
549 {
550     WebKitWebViewPrivate* priv = m_webView->priv;
551     g_signal_connect(priv->imContext, "commit", G_CALLBACK(imContextCommitted), this);
552     g_signal_connect(priv->imContext, "preedit-changed", G_CALLBACK(imContextPreeditChanged), this);
553 }
554 
~EditorClient()555 EditorClient::~EditorClient()
556 {
557     WebKitWebViewPrivate* priv = m_webView->priv;
558     g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextCommitted, this);
559     g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextPreeditChanged, this);
560 }
561 
textFieldDidBeginEditing(Element *)562 void EditorClient::textFieldDidBeginEditing(Element*)
563 {
564 }
565 
textFieldDidEndEditing(Element *)566 void EditorClient::textFieldDidEndEditing(Element*)
567 {
568 }
569 
textDidChangeInTextField(Element *)570 void EditorClient::textDidChangeInTextField(Element*)
571 {
572 }
573 
doTextFieldCommandFromEvent(Element *,KeyboardEvent *)574 bool EditorClient::doTextFieldCommandFromEvent(Element*, KeyboardEvent*)
575 {
576     return false;
577 }
578 
textWillBeDeletedInTextField(Element *)579 void EditorClient::textWillBeDeletedInTextField(Element*)
580 {
581     notImplemented();
582 }
583 
textDidChangeInTextArea(Element *)584 void EditorClient::textDidChangeInTextArea(Element*)
585 {
586     notImplemented();
587 }
588 
ignoreWordInSpellDocument(const String & text)589 void EditorClient::ignoreWordInSpellDocument(const String& text)
590 {
591     GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
592 
593     for (; langs; langs = langs->next) {
594         SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
595 
596         enchant_dict_add_to_session(lang->speller, text.utf8().data(), -1);
597     }
598 }
599 
learnWord(const String & text)600 void EditorClient::learnWord(const String& text)
601 {
602     GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
603 
604     for (; langs; langs = langs->next) {
605         SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
606 
607         enchant_dict_add_to_personal(lang->speller, text.utf8().data(), -1);
608     }
609 }
610 
checkSpellingOfString(const UChar * text,int length,int * misspellingLocation,int * misspellingLength)611 void EditorClient::checkSpellingOfString(const UChar* text, int length, int* misspellingLocation, int* misspellingLength)
612 {
613     gchar* ctext = g_utf16_to_utf8(const_cast<gunichar2*>(text), length, 0, 0, 0);
614     int utflen = g_utf8_strlen(ctext, -1);
615 
616     PangoLanguage* language = pango_language_get_default();
617     PangoLogAttr* attrs = g_new(PangoLogAttr, utflen+1);
618 
619     // pango_get_log_attrs uses an aditional position at the end of the text.
620     pango_get_log_attrs(ctext, -1, -1, language, attrs, utflen+1);
621 
622     for (int i = 0; i < length+1; i++) {
623         // We go through each character until we find an is_word_start,
624         // then we get into an inner loop to find the is_word_end corresponding
625         // to it.
626         if (attrs[i].is_word_start) {
627             int start = i;
628             int end = i;
629             int wordLength;
630             GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
631 
632             while (attrs[end].is_word_end < 1)
633                 end++;
634 
635             wordLength = end - start;
636             // Set the iterator to be at the current word end, so we don't
637             // check characters twice.
638             i = end;
639 
640             for (; langs; langs = langs->next) {
641                 SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
642                 gchar* cstart = g_utf8_offset_to_pointer(ctext, start);
643                 gint bytes = static_cast<gint>(g_utf8_offset_to_pointer(ctext, end) - cstart);
644                 gchar* word = g_new0(gchar, bytes+1);
645                 int result;
646 
647                 g_utf8_strncpy(word, cstart, end - start);
648 
649                 result = enchant_dict_check(lang->speller, word, -1);
650                 g_free(word);
651                 if (result) {
652                     *misspellingLocation = start;
653                     *misspellingLength = wordLength;
654                 } else {
655                     // Stop checking, this word is ok in at least one dict.
656                     *misspellingLocation = -1;
657                     *misspellingLength = 0;
658                     break;
659                 }
660             }
661         }
662     }
663 
664     g_free(attrs);
665     g_free(ctext);
666 }
667 
getAutoCorrectSuggestionForMisspelledWord(const String & inputWord)668 String EditorClient::getAutoCorrectSuggestionForMisspelledWord(const String& inputWord)
669 {
670     // This method can be implemented using customized algorithms for the particular browser.
671     // Currently, it computes an empty string.
672     return String();
673 }
674 
checkGrammarOfString(const UChar *,int,Vector<GrammarDetail> &,int *,int *)675 void EditorClient::checkGrammarOfString(const UChar*, int, Vector<GrammarDetail>&, int*, int*)
676 {
677     notImplemented();
678 }
679 
updateSpellingUIWithGrammarString(const String &,const GrammarDetail &)680 void EditorClient::updateSpellingUIWithGrammarString(const String&, const GrammarDetail&)
681 {
682     notImplemented();
683 }
684 
updateSpellingUIWithMisspelledWord(const String &)685 void EditorClient::updateSpellingUIWithMisspelledWord(const String&)
686 {
687     notImplemented();
688 }
689 
showSpellingUI(bool)690 void EditorClient::showSpellingUI(bool)
691 {
692     notImplemented();
693 }
694 
spellingUIIsShowing()695 bool EditorClient::spellingUIIsShowing()
696 {
697     notImplemented();
698     return false;
699 }
700 
getGuessesForWord(const String & word,WTF::Vector<String> & guesses)701 void EditorClient::getGuessesForWord(const String& word, WTF::Vector<String>& guesses)
702 {
703     GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
704     guesses.clear();
705 
706     for (; langs; langs = langs->next) {
707         size_t numberOfSuggestions;
708         size_t i;
709 
710         SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
711         gchar** suggestions = enchant_dict_suggest(lang->speller, word.utf8().data(), -1, &numberOfSuggestions);
712 
713         for (i = 0; i < numberOfSuggestions && i < 10; i++)
714             guesses.append(String::fromUTF8(suggestions[i]));
715 
716         if (numberOfSuggestions > 0)
717             enchant_dict_free_suggestions(lang->speller, suggestions);
718     }
719 }
720 
721 }
722