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