[GTK] REGRESSION(r116135): Keys that confirm composition trigger a default action
[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 "DumpRenderTreeSupportGtk.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 "PasteboardHelper.h"
39 #include "PlatformKeyboardEvent.h"
40 #include "UndoStep.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 "webkitglobals.h"
48 #include "webkitglobalsprivate.h"
49 #include "webkitmarshal.h"
50 #include "webkitspellchecker.h"
51 #include "webkitwebsettingsprivate.h"
52 #include "webkitwebviewprivate.h"
53 #include <wtf/text/CString.h>
54
55 // Arbitrary depth limit for the undo stack, to keep it from using
56 // unbounded memory.  This is the maximum number of distinct undoable
57 // actions -- unbroken stretches of typed characters are coalesced
58 // into a single action.
59 #define maximumUndoStackDepth 1000
60
61 using namespace WebCore;
62
63 namespace WebKit {
64
65 void EditorClient::willSetInputMethodState()
66 {
67 }
68
69 void EditorClient::setInputMethodState(bool active)
70 {
71     m_webView->priv->imFilter.setEnabled(active);
72 }
73
74 bool EditorClient::shouldShowUnicodeMenu()
75 {
76     if (gtk_major_version > 2 || (gtk_major_version == 2 && gtk_minor_version >= 10)) {
77         GtkSettings* settings = gtk_widget_get_settings(GTK_WIDGET(m_webView));
78         if (!settings)
79             return true;
80
81         gboolean enabled;
82         g_object_get(settings, "gtk-show-unicode-menu", &enabled, NULL);
83         return enabled;
84     }
85
86     return true;
87 }
88
89 bool EditorClient::shouldDeleteRange(Range* range)
90 {
91     gboolean accept = TRUE;
92     GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range)));
93     g_signal_emit_by_name(m_webView, "should-delete-range", kitRange.get(), &accept);
94     return accept;
95 }
96
97 bool EditorClient::shouldShowDeleteInterface(HTMLElement* element)
98 {
99     gboolean accept = TRUE;
100     GRefPtr<WebKitDOMHTMLElement> kitElement(adoptGRef(kit(element)));
101     g_signal_emit_by_name(m_webView, "should-show-delete-interface-for-element", kitElement.get(), &accept);
102     return accept;
103 }
104
105 bool EditorClient::isContinuousSpellCheckingEnabled()
106 {
107     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
108
109     gboolean enabled;
110     g_object_get(settings, "enable-spell-checking", &enabled, NULL);
111
112     return enabled;
113 }
114
115 bool EditorClient::isGrammarCheckingEnabled()
116 {
117     notImplemented();
118     return false;
119 }
120
121 int EditorClient::spellCheckerDocumentTag()
122 {
123     notImplemented();
124     return 0;
125 }
126
127 bool EditorClient::shouldBeginEditing(WebCore::Range* range)
128 {
129     gboolean accept = TRUE;
130     GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range)));
131     g_signal_emit_by_name(m_webView, "should-begin-editing", kitRange.get(), &accept);
132     return accept;
133 }
134
135 bool EditorClient::shouldEndEditing(WebCore::Range* range)
136 {
137     gboolean accept = TRUE;
138     GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range)));
139     g_signal_emit_by_name(m_webView, "should-end-editing", kitRange.get(), &accept);
140     return accept;
141 }
142
143 static WebKitInsertAction kit(EditorInsertAction action)
144 {
145     switch (action) {
146     case EditorInsertActionTyped:
147         return WEBKIT_INSERT_ACTION_TYPED;
148     case EditorInsertActionPasted:
149         return WEBKIT_INSERT_ACTION_PASTED;
150     case EditorInsertActionDropped:
151         return WEBKIT_INSERT_ACTION_DROPPED;
152     }
153     ASSERT_NOT_REACHED();
154     return WEBKIT_INSERT_ACTION_TYPED;
155 }
156
157 bool EditorClient::shouldInsertText(const String& string, Range* range, EditorInsertAction action)
158 {
159     gboolean accept = TRUE;
160     GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range)));
161     g_signal_emit_by_name(m_webView, "should-insert-text", string.utf8().data(), kitRange.get(), kit(action), &accept);
162     return accept;
163 }
164
165 static WebKitSelectionAffinity kit(EAffinity affinity)
166 {
167     switch (affinity) {
168     case UPSTREAM:
169         return WEBKIT_SELECTION_AFFINITY_UPSTREAM;
170     case DOWNSTREAM:
171         return WEBKIT_SELECTION_AFFINITY_DOWNSTREAM;
172     }
173     ASSERT_NOT_REACHED();
174     return WEBKIT_SELECTION_AFFINITY_UPSTREAM;
175 }
176
177 bool EditorClient::shouldChangeSelectedRange(Range* fromRange, Range* toRange, EAffinity affinity, bool stillSelecting)
178 {
179     gboolean accept = TRUE;
180     GRefPtr<WebKitDOMRange> kitFromRange(fromRange ? adoptGRef(kit(fromRange)) : 0);
181     GRefPtr<WebKitDOMRange> kitToRange(toRange ? adoptGRef(kit(toRange)) : 0);
182     g_signal_emit_by_name(m_webView, "should-change-selected-range", kitFromRange.get(), kitToRange.get(),
183                           kit(affinity), stillSelecting, &accept);
184     return accept;
185 }
186
187 bool EditorClient::shouldApplyStyle(WebCore::StylePropertySet* set, WebCore::Range* range)
188 {
189     gboolean accept = TRUE;
190     GRefPtr<WebKitDOMCSSStyleDeclaration> kitDeclaration(kit(set->ensureCSSStyleDeclaration()));
191     GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range)));
192     g_signal_emit_by_name(m_webView, "should-apply-style", kitDeclaration.get(), kitRange.get(), &accept);
193     return accept;
194 }
195
196 bool EditorClient::shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*)
197 {
198     notImplemented();
199     return true;
200 }
201
202 void EditorClient::didBeginEditing()
203 {
204     g_signal_emit_by_name(m_webView, "editing-began");
205 }
206
207 void EditorClient::respondToChangedContents()
208 {
209     g_signal_emit_by_name(m_webView, "user-changed-contents");
210 }
211
212 static WebKitWebView* viewSettingClipboard = 0;
213 static void collapseSelection(GtkClipboard* clipboard, WebKitWebView* webView)
214 {
215     if (viewSettingClipboard && viewSettingClipboard == webView)
216         return;
217
218     WebCore::Page* corePage = core(webView);
219     if (!corePage || !corePage->focusController())
220         return;
221
222     Frame* frame = corePage->focusController()->focusedOrMainFrame();
223
224     // Collapse the selection without clearing it
225     ASSERT(frame);
226     frame->selection()->setBase(frame->selection()->extent(), frame->selection()->affinity());
227 }
228
229 #if PLATFORM(X11)
230 static void setSelectionPrimaryClipboardIfNeeded(WebKitWebView* webView)
231 {
232     if (!gtk_widget_has_screen(GTK_WIDGET(webView)))
233         return;
234
235     GtkClipboard* clipboard = gtk_widget_get_clipboard(GTK_WIDGET(webView), GDK_SELECTION_PRIMARY);
236     DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
237     WebCore::Page* corePage = core(webView);
238     Frame* targetFrame = corePage->focusController()->focusedOrMainFrame();
239
240     if (!targetFrame->selection()->isRange())
241         return;
242
243     dataObject->clearAll();
244     dataObject->setRange(targetFrame->selection()->toNormalizedRange());
245
246     viewSettingClipboard = webView;
247     GClosure* callback = g_cclosure_new_object(G_CALLBACK(collapseSelection), G_OBJECT(webView));
248     g_closure_set_marshal(callback, g_cclosure_marshal_VOID__VOID);
249     PasteboardHelper::defaultPasteboardHelper()->writeClipboardContents(clipboard, PasteboardHelper::DoNotIncludeSmartPaste, callback);
250     viewSettingClipboard = 0;
251 }
252 #endif
253
254 void EditorClient::respondToChangedSelection(Frame* frame)
255 {
256     g_signal_emit_by_name(m_webView, "selection-changed");
257
258     if (!frame)
259         return;
260
261 #if PLATFORM(X11)
262     setSelectionPrimaryClipboardIfNeeded(m_webView);
263 #endif
264
265     if (!frame->editor()->hasComposition() || frame->editor()->ignoreCompositionSelectionChange())
266         return;
267
268     unsigned start;
269     unsigned end;
270     if (!frame->editor()->getCompositionSelection(start, end))
271         m_webView->priv->imFilter.resetContext();
272 }
273
274 void EditorClient::didEndEditing()
275 {
276     g_signal_emit_by_name(m_webView, "editing-ended");
277 }
278
279 void EditorClient::didWriteSelectionToPasteboard()
280 {
281     notImplemented();
282 }
283
284 void EditorClient::didSetSelectionTypesForPasteboard()
285 {
286     notImplemented();
287 }
288
289 void EditorClient::registerUndoStep(WTF::PassRefPtr<WebCore::UndoStep> step)
290 {
291     if (undoStack.size() == maximumUndoStackDepth)
292         undoStack.removeFirst();
293     if (!m_isInRedo)
294         redoStack.clear();
295     undoStack.append(step);
296 }
297
298 void EditorClient::registerRedoStep(WTF::PassRefPtr<WebCore::UndoStep> step)
299 {
300     redoStack.append(step);
301 }
302
303 void EditorClient::clearUndoRedoOperations()
304 {
305     undoStack.clear();
306     redoStack.clear();
307 }
308
309 bool EditorClient::canCopyCut(WebCore::Frame*, bool defaultValue) const
310 {
311     return defaultValue;
312 }
313
314 bool EditorClient::canPaste(WebCore::Frame*, bool defaultValue) const
315 {
316     return defaultValue;
317 }
318
319 bool EditorClient::canUndo() const
320 {
321     return !undoStack.isEmpty();
322 }
323
324 bool EditorClient::canRedo() const
325 {
326     return !redoStack.isEmpty();
327 }
328
329 void EditorClient::undo()
330 {
331     if (canUndo()) {
332         RefPtr<WebCore::UndoStep> step(*(--undoStack.end()));
333         undoStack.remove(--undoStack.end());
334         // unapply will call us back to push this command onto the redo stack.
335         step->unapply();
336     }
337 }
338
339 void EditorClient::redo()
340 {
341     if (canRedo()) {
342         RefPtr<WebCore::UndoStep> step(*(--redoStack.end()));
343         redoStack.remove(--redoStack.end());
344
345         ASSERT(!m_isInRedo);
346         m_isInRedo = true;
347         // reapply will call us back to push this command onto the undo stack.
348         step->reapply();
349         m_isInRedo = false;
350     }
351 }
352
353 bool EditorClient::shouldInsertNode(Node* node, Range* range, EditorInsertAction action)
354 {
355     gboolean accept = TRUE;
356     GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range)));
357     GRefPtr<WebKitDOMNode> kitNode(adoptGRef(kit(node)));
358     g_signal_emit_by_name(m_webView, "should-insert-node", kitNode.get(), kitRange.get(), kit(action), &accept);
359     return accept;
360 }
361
362 void EditorClient::pageDestroyed()
363 {
364     delete this;
365 }
366
367 void EditorClient::setSmartInsertDeleteEnabled(bool enabled)
368 {
369     m_smartInsertDeleteEnabled = enabled;
370 }
371
372 bool EditorClient::smartInsertDeleteEnabled()
373 {
374     return m_smartInsertDeleteEnabled;
375 }
376
377 bool EditorClient::isSelectTrailingWhitespaceEnabled()
378 {
379     if (!DumpRenderTreeSupportGtk::dumpRenderTreeModeEnabled())
380         return false;
381     return DumpRenderTreeSupportGtk::selectTrailingWhitespaceEnabled();
382 }
383
384 void EditorClient::toggleContinuousSpellChecking()
385 {
386     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
387
388     gboolean enabled;
389     g_object_get(settings, "enable-spell-checking", &enabled, NULL);
390
391     g_object_set(settings, "enable-spell-checking", !enabled, NULL);
392 }
393
394 void EditorClient::toggleGrammarChecking()
395 {
396 }
397
398 bool EditorClient::executePendingEditorCommands(Frame* frame, bool allowTextInsertion)
399 {
400     Vector<Editor::Command> commands;
401     for (size_t i = 0; i < m_pendingEditorCommands.size(); i++) {
402         Editor::Command command = frame->editor()->command(m_pendingEditorCommands.at(i).utf8().data());
403         if (command.isTextInsertion() && !allowTextInsertion)
404             return false;
405
406         commands.append(command);
407     }
408
409     bool success = true;
410     for (size_t i = 0; i < commands.size(); i++) {
411         if (!commands.at(i).execute()) {
412             success = false;
413             break;
414         }
415     }
416
417     m_pendingEditorCommands.clear();
418     return success;
419 }
420
421 static bool keyboardEventHadCompositionResults(KeyboardEvent* event)
422 {
423     if (event->type() != eventNames().keydownEvent)
424         return false;
425
426     const PlatformKeyboardEvent* platformEvent = event->keyEvent();
427     if (!platformEvent)
428         return false;
429
430     return platformEvent->compositionResults().compositionUpdated();
431 }
432
433 void EditorClient::handleKeyboardEvent(KeyboardEvent* event)
434 {
435     Node* node = event->target()->toNode();
436     ASSERT(node);
437     Frame* frame = node->document()->frame();
438     ASSERT(frame);
439
440     const PlatformKeyboardEvent* platformEvent = event->keyEvent();
441     if (!platformEvent)
442         return;
443
444     if (keyboardEventHadCompositionResults(event))
445         return;
446
447     KeyBindingTranslator::EventType type = event->type() == eventNames().keydownEvent ?
448         KeyBindingTranslator::KeyDown : KeyBindingTranslator::KeyPress;
449     m_keyBindingTranslator.getEditorCommandsForKeyEvent(platformEvent->gdkEventKey(), type, m_pendingEditorCommands);
450     if (m_pendingEditorCommands.size() > 0) {
451
452         // During RawKeyDown events if an editor command will insert text, defer
453         // the insertion until the keypress event. We want keydown to bubble up
454         // through the DOM first.
455         if (platformEvent->type() == PlatformEvent::RawKeyDown) {
456             if (executePendingEditorCommands(frame, false))
457                 event->setDefaultHandled();
458
459             return;
460         }
461
462         // Only allow text insertion commands if the current node is editable.
463         if (executePendingEditorCommands(frame, frame->editor()->canEdit())) {
464             event->setDefaultHandled();
465             return;
466         }
467     }
468
469     // Don't allow text insertion for nodes that cannot edit.
470     if (!frame->editor()->canEdit())
471         return;
472
473     // This is just a normal text insertion, so wait to execute the insertion
474     // until a keypress event happens. This will ensure that the insertion will not
475     // be reflected in the contents of the field until the keyup DOM event.
476     if (event->type() != eventNames().keypressEvent)
477         return;
478
479     // Don't insert null or control characters as they can result in unexpected behaviour
480     if (event->charCode() < ' ')
481         return;
482
483     // Don't insert anything if a modifier is pressed
484     if (platformEvent->ctrlKey() || platformEvent->altKey())
485         return;
486
487     if (frame->editor()->insertText(platformEvent->text(), event))
488         event->setDefaultHandled();
489 }
490
491 void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
492 {
493     // Input method results are handled in handleKeyboardEvent, so that we can wait
494     // to trigger composition updates until after the keydown event handler. This better
495     // matches other browsers.
496     const PlatformKeyboardEvent* platformEvent = event->keyEvent();
497     if (platformEvent && platformEvent->compositionResults().compositionUpdated())
498         event->preventDefault();
499 }
500
501 EditorClient::EditorClient(WebKitWebView* webView)
502     : m_isInRedo(false)
503 #if ENABLE(SPELLCHECK)
504     , m_textCheckerClient(WEBKIT_SPELL_CHECKER(webkit_get_text_checker()))
505 #endif
506     , m_webView(webView)
507     , m_smartInsertDeleteEnabled(false)
508 {
509 }
510
511 EditorClient::~EditorClient()
512 {
513 }
514
515 void EditorClient::textFieldDidBeginEditing(Element*)
516 {
517 }
518
519 void EditorClient::textFieldDidEndEditing(Element*)
520 {
521 }
522
523 void EditorClient::textDidChangeInTextField(Element*)
524 {
525 }
526
527 bool EditorClient::doTextFieldCommandFromEvent(Element*, KeyboardEvent*)
528 {
529     return false;
530 }
531
532 void EditorClient::textWillBeDeletedInTextField(Element*)
533 {
534     notImplemented();
535 }
536
537 void EditorClient::textDidChangeInTextArea(Element*)
538 {
539     notImplemented();
540 }
541
542 void EditorClient::updateSpellingUIWithGrammarString(const String&, const GrammarDetail&)
543 {
544     notImplemented();
545 }
546
547 void EditorClient::updateSpellingUIWithMisspelledWord(const String&)
548 {
549     notImplemented();
550 }
551
552 void EditorClient::showSpellingUI(bool)
553 {
554     notImplemented();
555 }
556
557 bool EditorClient::spellingUIIsShowing()
558 {
559     notImplemented();
560     return false;
561 }
562
563 }