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