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