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