2010-05-19 Sheriff Bot <webkit.review.bot@gmail.com>
[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 static void backspaceCallback(GtkWidget* widget, EditorClient* client)
91 {
92     g_signal_stop_emission_by_name(widget, "backspace");
93     client->addPendingEditorCommand("DeleteBackward");
94 }
95
96 static void selectAllCallback(GtkWidget* widget, gboolean select, EditorClient* client)
97 {
98     g_signal_stop_emission_by_name(widget, "select-all");
99     client->addPendingEditorCommand(select ? "SelectAll" : "Unselect");
100 }
101
102 static void cutClipboardCallback(GtkWidget* widget, EditorClient* client)
103 {
104     g_signal_stop_emission_by_name(widget, "cut-clipboard");
105     client->addPendingEditorCommand("Cut");
106 }
107
108 static void copyClipboardCallback(GtkWidget* widget, EditorClient* client)
109 {
110     g_signal_stop_emission_by_name(widget, "copy-clipboard");
111     client->addPendingEditorCommand("Copy");
112 }
113
114 static void pasteClipboardCallback(GtkWidget* widget, EditorClient* client)
115 {
116     g_signal_stop_emission_by_name(widget, "paste-clipboard");
117     client->addPendingEditorCommand("Paste");
118 }
119
120 static const char* const gtkDeleteCommands[][2] = {
121     { "DeleteBackward",               "DeleteForward"                        }, // Characters
122     { "DeleteWordBackward",           "DeleteWordForward"                    }, // Word ends
123     { "DeleteWordBackward",           "DeleteWordForward"                    }, // Words
124     { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Lines
125     { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Line ends
126     { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraph ends
127     { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraphs
128     { 0,                              0                                      } // Whitespace (M-\ in Emacs)
129 };
130
131 static void deleteFromCursorCallback(GtkWidget* widget, GtkDeleteType deleteType, gint count, EditorClient* client)
132 {
133     g_signal_stop_emission_by_name(widget, "delete-from-cursor");
134     int direction = count > 0 ? 1 : 0;
135
136     // Ensuring that deleteType <= G_N_ELEMENTS here results in a compiler warning
137     // that the condition is always true.
138
139     if (deleteType == GTK_DELETE_WORDS) {
140         if (!direction) {
141             client->addPendingEditorCommand("MoveWordForward");
142             client->addPendingEditorCommand("MoveWordBackward");
143         } else {
144             client->addPendingEditorCommand("MoveWordBackward");
145             client->addPendingEditorCommand("MoveWordForward");
146         }
147     } else if (deleteType == GTK_DELETE_DISPLAY_LINES) {
148         if (!direction)
149             client->addPendingEditorCommand("MoveToBeginningOfLine");
150         else
151             client->addPendingEditorCommand("MoveToEndOfLine");
152     } else if (deleteType == GTK_DELETE_PARAGRAPHS) {
153         if (!direction)
154             client->addPendingEditorCommand("MoveToBeginningOfParagraph");
155         else
156             client->addPendingEditorCommand("MoveToEndOfParagraph");
157     }
158
159     const char* rawCommand = gtkDeleteCommands[deleteType][direction];
160     if (!rawCommand)
161       return;
162
163     for (int i = 0; i < abs(count); i++)
164         client->addPendingEditorCommand(rawCommand);
165 }
166
167 static const char* const gtkMoveCommands[][4] = {
168     { "MoveBackward",                                   "MoveForward",
169       "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Forward/backward grapheme
170     { "MoveBackward",                                   "MoveForward",
171       "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Left/right grapheme
172     { "MoveWordBackward",                               "MoveWordForward",
173       "MoveWordBackwardAndModifySelection",             "MoveWordForwardAndModifySelection"         }, // Forward/backward word
174     { "MoveUp",                                         "MoveDown",
175       "MoveUpAndModifySelection",                       "MoveDownAndModifySelection"                }, // Up/down line
176     { "MoveToBeginningOfLine",                          "MoveToEndOfLine",
177       "MoveToBeginningOfLineAndModifySelection",        "MoveToEndOfLineAndModifySelection"         }, // Up/down line ends
178     { "MoveParagraphForward",                           "MoveParagraphBackward",
179       "MoveParagraphForwardAndModifySelection",         "MoveParagraphBackwardAndModifySelection"   }, // Up/down paragraphs
180     { "MoveToBeginningOfParagraph",                     "MoveToEndOfParagraph",
181       "MoveToBeginningOfParagraphAndModifySelection",   "MoveToEndOfParagraphAndModifySelection"    }, // Up/down paragraph ends.
182     { "MovePageUp",                                     "MovePageDown",
183       "MovePageUpAndModifySelection",                   "MovePageDownAndModifySelection"            }, // Up/down page
184     { "MoveToBeginningOfDocument",                      "MoveToEndOfDocument",
185       "MoveToBeginningOfDocumentAndModifySelection",    "MoveToEndOfDocumentAndModifySelection"     }, // Begin/end of buffer
186     { 0,                                                0,
187       0,                                                0                                           } // Horizontal page movement
188 };
189
190 static void moveCursorCallback(GtkWidget* widget, GtkMovementStep step, gint count, gboolean extendSelection, EditorClient* client)
191 {
192     g_signal_stop_emission_by_name(widget, "move-cursor");
193     int direction = count > 0 ? 1 : 0;
194     if (extendSelection)
195         direction += 2;
196
197     if (static_cast<unsigned>(step) >= G_N_ELEMENTS(gtkMoveCommands))
198         return;
199
200     const char* rawCommand = gtkMoveCommands[step][direction];
201     if (!rawCommand)
202         return;
203
204     for (int i = 0; i < abs(count); i++)
205         client->addPendingEditorCommand(rawCommand);
206 }
207
208 void EditorClient::updatePendingComposition(const gchar* newComposition)
209 {
210     // The IMContext may signal more than one completed composition in a row,
211     // in which case we want to append them, rather than overwrite the old one.
212     if (!m_pendingComposition)
213         m_pendingComposition.set(g_strdup(newComposition));
214     else
215         m_pendingComposition.set(g_strconcat(m_pendingComposition.get(), newComposition, NULL));
216 }
217
218 void EditorClient::setInputMethodState(bool active)
219 {
220     WebKitWebViewPrivate* priv = m_webView->priv;
221
222     if (active)
223         gtk_im_context_focus_in(priv->imContext);
224     else
225         gtk_im_context_focus_out(priv->imContext);
226
227 #ifdef MAEMO_CHANGES
228     if (active)
229         hildon_gtk_im_context_show(priv->imContext);
230     else
231         hildon_gtk_im_context_hide(priv->imContext);
232 #endif
233 }
234
235 bool EditorClient::shouldDeleteRange(Range*)
236 {
237     notImplemented();
238     return true;
239 }
240
241 bool EditorClient::shouldShowDeleteInterface(HTMLElement*)
242 {
243     return false;
244 }
245
246 bool EditorClient::isContinuousSpellCheckingEnabled()
247 {
248     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
249
250     gboolean enabled;
251     g_object_get(settings, "enable-spell-checking", &enabled, NULL);
252
253     return enabled;
254 }
255
256 bool EditorClient::isGrammarCheckingEnabled()
257 {
258     notImplemented();
259     return false;
260 }
261
262 int EditorClient::spellCheckerDocumentTag()
263 {
264     notImplemented();
265     return 0;
266 }
267
268 bool EditorClient::shouldBeginEditing(WebCore::Range*)
269 {
270     clearPendingComposition();
271
272     notImplemented();
273     return true;
274 }
275
276 bool EditorClient::shouldEndEditing(WebCore::Range*)
277 {
278     clearPendingComposition();
279
280     notImplemented();
281     return true;
282 }
283
284 bool EditorClient::shouldInsertText(const String&, Range*, EditorInsertAction)
285 {
286     notImplemented();
287     return true;
288 }
289
290 bool EditorClient::shouldChangeSelectedRange(Range*, Range*, EAffinity, bool)
291 {
292     notImplemented();
293     return true;
294 }
295
296 bool EditorClient::shouldApplyStyle(WebCore::CSSStyleDeclaration*, WebCore::Range*)
297 {
298     notImplemented();
299     return true;
300 }
301
302 bool EditorClient::shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*)
303 {
304     notImplemented();
305     return true;
306 }
307
308 void EditorClient::didBeginEditing()
309 {
310     notImplemented();
311 }
312
313 void EditorClient::respondToChangedContents()
314 {
315     notImplemented();
316 }
317
318 static WebKitWebView* viewSettingClipboard = 0;
319 static void collapseSelection(GtkClipboard* clipboard, WebKitWebView* webView)
320 {
321     if (viewSettingClipboard && viewSettingClipboard == webView)
322         return;
323
324     WebCore::Page* corePage = core(webView);
325     if (!corePage || !corePage->focusController())
326         return;
327
328     Frame* frame = corePage->focusController()->focusedOrMainFrame();
329
330     // Collapse the selection without clearing it
331     ASSERT(frame);
332     frame->selection()->setBase(frame->selection()->extent(), frame->selection()->affinity());
333 }
334
335 void EditorClient::respondToChangedSelection()
336 {
337     WebKitWebViewPrivate* priv = m_webView->priv;
338     WebCore::Page* corePage = core(m_webView);
339     Frame* targetFrame = corePage->focusController()->focusedOrMainFrame();
340
341     if (!targetFrame)
342         return;
343
344     if (targetFrame->editor()->ignoreCompositionSelectionChange())
345         return;
346
347 #if PLATFORM(X11)
348     GtkClipboard* clipboard = gtk_widget_get_clipboard(GTK_WIDGET(m_webView), GDK_SELECTION_PRIMARY);
349     DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
350
351     if (targetFrame->selection()->isRange()) {
352         dataObject->clear();
353         dataObject->setRange(targetFrame->selection()->toNormalizedRange());
354
355         viewSettingClipboard = m_webView;
356         GClosure* callback = g_cclosure_new_object(G_CALLBACK(collapseSelection), G_OBJECT(m_webView));
357         g_closure_set_marshal(callback, g_cclosure_marshal_VOID__VOID);
358         pasteboardHelperInstance()->writeClipboardContents(clipboard, callback);
359         viewSettingClipboard = 0;
360     }
361 #endif
362
363     if (!targetFrame->editor()->hasComposition())
364         return;
365
366     unsigned start;
367     unsigned end;
368     if (!targetFrame->editor()->getCompositionSelection(start, end)) {
369         // gtk_im_context_reset() clears the composition for us.
370         gtk_im_context_reset(priv->imContext);
371         targetFrame->editor()->confirmCompositionWithoutDisturbingSelection();
372     }
373 }
374
375 void EditorClient::didEndEditing()
376 {
377     notImplemented();
378 }
379
380 void EditorClient::didWriteSelectionToPasteboard()
381 {
382     notImplemented();
383 }
384
385 void EditorClient::didSetSelectionTypesForPasteboard()
386 {
387     notImplemented();
388 }
389
390 bool EditorClient::isEditable()
391 {
392     return webkit_web_view_get_editable(m_webView);
393 }
394
395 void EditorClient::registerCommandForUndo(WTF::PassRefPtr<WebCore::EditCommand> command)
396 {
397     if (undoStack.size() == maximumUndoStackDepth)
398         undoStack.removeFirst();
399     if (!m_isInRedo)
400         redoStack.clear();
401     undoStack.append(command);
402 }
403
404 void EditorClient::registerCommandForRedo(WTF::PassRefPtr<WebCore::EditCommand> command)
405 {
406     redoStack.append(command);
407 }
408
409 void EditorClient::clearUndoRedoOperations()
410 {
411     undoStack.clear();
412     redoStack.clear();
413 }
414
415 bool EditorClient::canUndo() const
416 {
417     return !undoStack.isEmpty();
418 }
419
420 bool EditorClient::canRedo() const
421 {
422     return !redoStack.isEmpty();
423 }
424
425 void EditorClient::undo()
426 {
427     if (canUndo()) {
428         RefPtr<WebCore::EditCommand> command(*(--undoStack.end()));
429         undoStack.remove(--undoStack.end());
430         // unapply will call us back to push this command onto the redo stack.
431         command->unapply();
432     }
433 }
434
435 void EditorClient::redo()
436 {
437     if (canRedo()) {
438         RefPtr<WebCore::EditCommand> command(*(--redoStack.end()));
439         redoStack.remove(--redoStack.end());
440
441         ASSERT(!m_isInRedo);
442         m_isInRedo = true;
443         // reapply will call us back to push this command onto the undo stack.
444         command->reapply();
445         m_isInRedo = false;
446     }
447 }
448
449 bool EditorClient::shouldInsertNode(Node*, Range*, EditorInsertAction)
450 {
451     notImplemented();
452     return true;
453 }
454
455 void EditorClient::pageDestroyed()
456 {
457     delete this;
458 }
459
460 bool EditorClient::smartInsertDeleteEnabled()
461 {
462     notImplemented();
463     return false;
464 }
465
466 bool EditorClient::isSelectTrailingWhitespaceEnabled()
467 {
468     notImplemented();
469     return false;
470 }
471
472 void EditorClient::toggleContinuousSpellChecking()
473 {
474     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
475
476     gboolean enabled;
477     g_object_get(settings, "enable-spell-checking", &enabled, NULL);
478
479     g_object_set(settings, "enable-spell-checking", !enabled, NULL);
480 }
481
482 void EditorClient::toggleGrammarChecking()
483 {
484 }
485
486 static const unsigned CtrlKey = 1 << 0;
487 static const unsigned AltKey = 1 << 1;
488 static const unsigned ShiftKey = 1 << 2;
489
490 struct KeyDownEntry {
491     unsigned virtualKey;
492     unsigned modifiers;
493     const char* name;
494 };
495
496 struct KeyPressEntry {
497     unsigned charCode;
498     unsigned modifiers;
499     const char* name;
500 };
501
502 static const KeyDownEntry keyDownEntries[] = {
503     { 'B',       CtrlKey,            "ToggleBold"                                  },
504     { 'I',       CtrlKey,            "ToggleItalic"                                },
505     { VK_ESCAPE, 0,                  "Cancel"                                      },
506     { VK_OEM_PERIOD, CtrlKey,        "Cancel"                                      },
507     { VK_TAB,    0,                  "InsertTab"                                   },
508     { VK_TAB,    ShiftKey,           "InsertBacktab"                               },
509     { VK_RETURN, 0,                  "InsertNewline"                               },
510     { VK_RETURN, CtrlKey,            "InsertNewline"                               },
511     { VK_RETURN, AltKey,             "InsertNewline"                               },
512     { VK_RETURN, AltKey | ShiftKey,  "InsertNewline"                               },
513 };
514
515 static const KeyPressEntry keyPressEntries[] = {
516     { '\t',   0,                  "InsertTab"                                   },
517     { '\t',   ShiftKey,           "InsertBacktab"                               },
518     { '\r',   0,                  "InsertNewline"                               },
519     { '\r',   CtrlKey,            "InsertNewline"                               },
520     { '\r',   AltKey,             "InsertNewline"                               },
521     { '\r',   AltKey | ShiftKey,  "InsertNewline"                               },
522 };
523
524 void EditorClient::generateEditorCommands(const KeyboardEvent* event)
525 {
526     ASSERT(event->type() == eventNames().keydownEvent || event->type() == eventNames().keypressEvent);
527
528     m_pendingEditorCommands.clear();
529
530     // First try to interpret the command as a native GTK+ key binding.
531     gtk_bindings_activate_event(GTK_OBJECT(m_nativeWidget.get()), event->keyEvent()->gdkEventKey());
532     if (m_pendingEditorCommands.size() > 0)
533         return;
534
535     static HashMap<int, const char*> keyDownCommandsMap;
536     static HashMap<int, const char*> keyPressCommandsMap;
537
538     if (keyDownCommandsMap.isEmpty()) {
539         for (unsigned i = 0; i < G_N_ELEMENTS(keyDownEntries); i++)
540             keyDownCommandsMap.set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name);
541
542         for (unsigned i = 0; i < G_N_ELEMENTS(keyPressEntries); i++)
543             keyPressCommandsMap.set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name);
544     }
545
546     unsigned modifiers = 0;
547     if (event->shiftKey())
548         modifiers |= ShiftKey;
549     if (event->altKey())
550         modifiers |= AltKey;
551     if (event->ctrlKey())
552         modifiers |= CtrlKey;
553
554
555     if (event->type() == eventNames().keydownEvent) {
556         int mapKey = modifiers << 16 | event->keyCode();
557         if (mapKey)
558             m_pendingEditorCommands.append(keyDownCommandsMap.get(mapKey));
559         return;
560     }
561
562     int mapKey = modifiers << 16 | event->charCode();
563     if (mapKey)
564         m_pendingEditorCommands.append(keyPressCommandsMap.get(mapKey));
565 }
566
567 bool EditorClient::executePendingEditorCommands(Frame* frame, bool allowTextInsertion)
568 {
569     Vector<Editor::Command> commands;
570     for (size_t i = 0; i < m_pendingEditorCommands.size(); i++) {
571         Editor::Command command = frame->editor()->command(m_pendingEditorCommands.at(i));
572         if (command.isTextInsertion() && !allowTextInsertion)
573             return false;
574
575         commands.append(command);
576     }
577
578     bool success = true;
579     for (size_t i = 0; i < commands.size(); i++) {
580         if (!commands.at(i).execute()) {
581             success = false;
582             break;
583         }
584     }
585
586     m_pendingEditorCommands.clear();
587
588     // If we successfully completed all editor commands, then
589     // this signals a canceling of the composition.
590     if (success)
591         clearPendingComposition();
592
593     return success;
594 }
595
596 void EditorClient::handleKeyboardEvent(KeyboardEvent* event)
597 {
598     Node* node = event->target()->toNode();
599     ASSERT(node);
600     Frame* frame = node->document()->frame();
601     ASSERT(frame);
602
603     const PlatformKeyboardEvent* platformEvent = event->keyEvent();
604     if (!platformEvent)
605         return;
606
607     // Don't allow editor commands or text insertion for nodes that
608     // cannot edit, unless we are in caret mode.
609     if (!frame->editor()->canEdit() && !(frame->settings() && frame->settings()->caretBrowsingEnabled()))
610         return;
611
612     generateEditorCommands(event);
613     if (m_pendingEditorCommands.size() > 0) {
614
615         // During RawKeyDown events if an editor command will insert text, defer
616         // the insertion until the keypress event. We want keydown to bubble up
617         // through the DOM first.
618         if (platformEvent->type() == PlatformKeyboardEvent::RawKeyDown) {
619             if (executePendingEditorCommands(frame, false))
620                 event->setDefaultHandled();
621
622             return;
623         }
624
625         if (executePendingEditorCommands(frame, true)) {
626             event->setDefaultHandled();
627             return;
628         }
629     }
630
631     // This is just a normal text insertion, so wait to execute the insertion
632     // until a keypress event happens. This will ensure that the insertion will not
633     // be reflected in the contents of the field until the keyup DOM event.
634     if (event->type() == eventNames().keypressEvent) {
635
636         // If we have a pending composition at this point, it happened while
637         // filtering a keypress, so we treat it as a normal text insertion.
638         // This will also ensure that if the keypress event handler changed the
639         // currently focused node, the text is still inserted into the original
640         // node (insertText() has this logic, but confirmComposition() does not).
641         if (m_pendingComposition) {
642             frame->editor()->insertText(String::fromUTF8(m_pendingComposition.get()), event);
643             clearPendingComposition();
644             event->setDefaultHandled();
645
646         } else {
647             // Don't insert null or control characters as they can result in unexpected behaviour
648             if (event->charCode() < ' ')
649                 return;
650
651             // Don't insert anything if a modifier is pressed
652             if (platformEvent->ctrlKey() || platformEvent->altKey())
653                 return;
654
655             if (frame->editor()->insertText(platformEvent->text(), event))
656                 event->setDefaultHandled();
657         }
658     }
659 }
660
661 void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
662 {
663     Frame* targetFrame = core(m_webView)->focusController()->focusedOrMainFrame();
664     if (!targetFrame || !targetFrame->editor()->canEdit())
665         return;
666
667     WebKitWebViewPrivate* priv = m_webView->priv;
668
669
670     // Some IM contexts (e.g. 'simple') will act as if they filter every
671     // keystroke and just issue a 'commit' signal during handling. In situations
672     // where the 'commit' signal happens during filtering and there is no active
673     // composition, act as if the keystroke was not filtered. The one exception to
674     // this is when the keyval parameter of the GdkKeyEvent is 0, which is often
675     // a key event sent by the IM context for committing the current composition.
676
677     // Here is a typical sequence of events for the 'simple' context:
678     // 1. GDK key press event -> webkit_web_view_key_press_event
679     // 2. Keydown event -> EditorClient::handleInputMethodKeydown
680     //     gtk_im_context_filter_keypress returns true, but there is a pending
681     //     composition so event->preventDefault is not called (below).
682     // 3. Keydown event bubbles through the DOM
683     // 4. Keydown event -> EditorClient::handleKeyboardEvent
684     //     No action taken.
685     // 4. GDK key release event -> webkit_web_view_key_release_event
686     // 5. gtk_im_context_filter_keypress is called on the release event.
687     //     Simple does not filter most key releases, so the event continues.
688     // 6. Keypress event bubbles through the DOM.
689     // 7. Keypress event -> EditorClient::handleKeyboardEvent
690     //     pending composition is inserted.
691     // 8. Keyup event bubbles through the DOM.
692     // 9. Keyup event -> EditorClient::handleKeyboardEvent
693     //     No action taken.
694
695     // There are two situations where we do filter the keystroke:
696     // 1. The IMContext instructed us to filter and we have no pending composition.
697     // 2. The IMContext did not instruct us to filter, but the keystroke caused a
698     //    composition in progress to finish. It seems that sometimes SCIM will finish
699     //    a composition and not mark the keystroke as filtered.
700     m_treatContextCommitAsKeyEvent = (!targetFrame->editor()->hasComposition())
701          && event->keyEvent()->gdkEventKey()->keyval;
702     clearPendingComposition();
703     if ((gtk_im_context_filter_keypress(priv->imContext, event->keyEvent()->gdkEventKey()) && !m_pendingComposition)
704         || (!m_treatContextCommitAsKeyEvent && !targetFrame->editor()->hasComposition()))
705         event->preventDefault();
706
707     m_treatContextCommitAsKeyEvent = false;
708 }
709
710 EditorClient::EditorClient(WebKitWebView* webView)
711     : m_isInRedo(false)
712     , m_webView(webView)
713     , m_treatContextCommitAsKeyEvent(false)
714     , m_nativeWidget(gtk_text_view_new())
715 {
716     WebKitWebViewPrivate* priv = m_webView->priv;
717     g_signal_connect(priv->imContext, "commit", G_CALLBACK(imContextCommitted), this);
718     g_signal_connect(priv->imContext, "preedit-changed", G_CALLBACK(imContextPreeditChanged), this);
719
720     g_signal_connect(m_nativeWidget.get(), "backspace", G_CALLBACK(backspaceCallback), this);
721     g_signal_connect(m_nativeWidget.get(), "cut-clipboard", G_CALLBACK(cutClipboardCallback), this);
722     g_signal_connect(m_nativeWidget.get(), "copy-clipboard", G_CALLBACK(copyClipboardCallback), this);
723     g_signal_connect(m_nativeWidget.get(), "paste-clipboard", G_CALLBACK(pasteClipboardCallback), this);
724     g_signal_connect(m_nativeWidget.get(), "select-all", G_CALLBACK(selectAllCallback), this);
725     g_signal_connect(m_nativeWidget.get(), "move-cursor", G_CALLBACK(moveCursorCallback), this);
726     g_signal_connect(m_nativeWidget.get(), "delete-from-cursor", G_CALLBACK(deleteFromCursorCallback), this);
727 }
728
729 EditorClient::~EditorClient()
730 {
731     WebKitWebViewPrivate* priv = m_webView->priv;
732     g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextCommitted, this);
733     g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextPreeditChanged, this);
734 }
735
736 void EditorClient::textFieldDidBeginEditing(Element*)
737 {
738 }
739
740 void EditorClient::textFieldDidEndEditing(Element*)
741 {
742 }
743
744 void EditorClient::textDidChangeInTextField(Element*)
745 {
746 }
747
748 bool EditorClient::doTextFieldCommandFromEvent(Element*, KeyboardEvent*)
749 {
750     return false;
751 }
752
753 void EditorClient::textWillBeDeletedInTextField(Element*)
754 {
755     notImplemented();
756 }
757
758 void EditorClient::textDidChangeInTextArea(Element*)
759 {
760     notImplemented();
761 }
762
763 void EditorClient::ignoreWordInSpellDocument(const String& text)
764 {
765     GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
766
767     for (; dicts; dicts = dicts->next) {
768         EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
769
770         enchant_dict_add_to_session(dict, text.utf8().data(), -1);
771     }
772 }
773
774 void EditorClient::learnWord(const String& text)
775 {
776     GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
777
778     for (; dicts; dicts = dicts->next) {
779         EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
780
781         enchant_dict_add_to_personal(dict, text.utf8().data(), -1);
782     }
783 }
784
785 void EditorClient::checkSpellingOfString(const UChar* text, int length, int* misspellingLocation, int* misspellingLength)
786 {
787     GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
788     if (!dicts)
789         return;
790
791     gchar* ctext = g_utf16_to_utf8(const_cast<gunichar2*>(text), length, 0, 0, 0);
792     int utflen = g_utf8_strlen(ctext, -1);
793
794     PangoLanguage* language = pango_language_get_default();
795     PangoLogAttr* attrs = g_new(PangoLogAttr, utflen+1);
796
797     // pango_get_log_attrs uses an aditional position at the end of the text.
798     pango_get_log_attrs(ctext, -1, -1, language, attrs, utflen+1);
799
800     for (int i = 0; i < length+1; i++) {
801         // We go through each character until we find an is_word_start,
802         // then we get into an inner loop to find the is_word_end corresponding
803         // to it.
804         if (attrs[i].is_word_start) {
805             int start = i;
806             int end = i;
807             int wordLength;
808
809             while (attrs[end].is_word_end < 1)
810                 end++;
811
812             wordLength = end - start;
813             // Set the iterator to be at the current word end, so we don't
814             // check characters twice.
815             i = end;
816
817             for (; dicts; dicts = dicts->next) {
818                 EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
819                 gchar* cstart = g_utf8_offset_to_pointer(ctext, start);
820                 gint bytes = static_cast<gint>(g_utf8_offset_to_pointer(ctext, end) - cstart);
821                 gchar* word = g_new0(gchar, bytes+1);
822                 int result;
823
824                 g_utf8_strncpy(word, cstart, end - start);
825
826                 result = enchant_dict_check(dict, word, -1);
827                 g_free(word);
828                 if (result) {
829                     *misspellingLocation = start;
830                     *misspellingLength = wordLength;
831                 } else {
832                     // Stop checking, this word is ok in at least one dict.
833                     *misspellingLocation = -1;
834                     *misspellingLength = 0;
835                     break;
836                 }
837             }
838         }
839     }
840
841     g_free(attrs);
842     g_free(ctext);
843 }
844
845 String EditorClient::getAutoCorrectSuggestionForMisspelledWord(const String& inputWord)
846 {
847     // This method can be implemented using customized algorithms for the particular browser.
848     // Currently, it computes an empty string.
849     return String();
850 }
851
852 void EditorClient::checkGrammarOfString(const UChar*, int, Vector<GrammarDetail>&, int*, int*)
853 {
854     notImplemented();
855 }
856
857 void EditorClient::updateSpellingUIWithGrammarString(const String&, const GrammarDetail&)
858 {
859     notImplemented();
860 }
861
862 void EditorClient::updateSpellingUIWithMisspelledWord(const String&)
863 {
864     notImplemented();
865 }
866
867 void EditorClient::showSpellingUI(bool)
868 {
869     notImplemented();
870 }
871
872 bool EditorClient::spellingUIIsShowing()
873 {
874     notImplemented();
875     return false;
876 }
877
878 void EditorClient::getGuessesForWord(const String& word, WTF::Vector<String>& guesses)
879 {
880     GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
881     guesses.clear();
882
883     for (; dicts; dicts = dicts->next) {
884         size_t numberOfSuggestions;
885         size_t i;
886
887         EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
888         gchar** suggestions = enchant_dict_suggest(dict, word.utf8().data(), -1, &numberOfSuggestions);
889
890         for (i = 0; i < numberOfSuggestions && i < 10; i++)
891             guesses.append(String::fromUTF8(suggestions[i]));
892
893         if (numberOfSuggestions > 0)
894             enchant_dict_free_suggestions(dict, suggestions);
895     }
896 }
897
898 }