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