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