| /* |
| * Copyright (C) 2007 Alp Toker <alp@atoker.com> |
| * Copyright (C) 2008 Nuanti Ltd. |
| * Copyright (C) 2009 Diego Escalante Urrelo <diegoe@gnome.org> |
| * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. |
| * Copyright (C) 2009, 2010 Igalia S.L. |
| * Copyright (C) 2010, Martin Robinson <mrobinson@webkit.org> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include "config.h" |
| #include "EditorClientGtk.h" |
| |
| #include "DataObjectGtk.h" |
| #include "DumpRenderTreeSupportGtk.h" |
| #include "Editor.h" |
| #include "EventNames.h" |
| #include "FocusController.h" |
| #include "Frame.h" |
| #include <glib.h> |
| #include "KeyboardEvent.h" |
| #include "markup.h" |
| #include "NotImplemented.h" |
| #include "Page.h" |
| #include "PasteboardHelper.h" |
| #include "PlatformKeyboardEvent.h" |
| #include "UndoStep.h" |
| #include "WebKitDOMBinding.h" |
| #include "WebKitDOMCSSStyleDeclarationPrivate.h" |
| #include "WebKitDOMHTMLElementPrivate.h" |
| #include "WebKitDOMNodePrivate.h" |
| #include "WebKitDOMRangePrivate.h" |
| #include "WindowsKeyboardCodes.h" |
| #include "webkitglobals.h" |
| #include "webkitglobalsprivate.h" |
| #include "webkitmarshal.h" |
| #include "webkitspellchecker.h" |
| #include "webkitwebsettingsprivate.h" |
| #include "webkitwebviewprivate.h" |
| #include <wtf/text/CString.h> |
| |
| // Arbitrary depth limit for the undo stack, to keep it from using |
| // unbounded memory. This is the maximum number of distinct undoable |
| // actions -- unbroken stretches of typed characters are coalesced |
| // into a single action. |
| #define maximumUndoStackDepth 1000 |
| |
| using namespace WebCore; |
| |
| namespace WebKit { |
| |
| void EditorClient::willSetInputMethodState() |
| { |
| } |
| |
| void EditorClient::setInputMethodState(bool active) |
| { |
| m_webView->priv->imFilter.setEnabled(active); |
| } |
| |
| bool EditorClient::shouldShowUnicodeMenu() |
| { |
| if (gtk_major_version > 2 || (gtk_major_version == 2 && gtk_minor_version >= 10)) { |
| GtkSettings* settings = gtk_widget_get_settings(GTK_WIDGET(m_webView)); |
| if (!settings) |
| return true; |
| |
| gboolean enabled; |
| g_object_get(settings, "gtk-show-unicode-menu", &enabled, NULL); |
| return enabled; |
| } |
| |
| return true; |
| } |
| |
| bool EditorClient::shouldDeleteRange(Range* range) |
| { |
| gboolean accept = TRUE; |
| GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range))); |
| g_signal_emit_by_name(m_webView, "should-delete-range", kitRange.get(), &accept); |
| return accept; |
| } |
| |
| bool EditorClient::shouldShowDeleteInterface(HTMLElement* element) |
| { |
| gboolean accept = FALSE; |
| GRefPtr<WebKitDOMHTMLElement> kitElement(adoptGRef(kit(element))); |
| g_signal_emit_by_name(m_webView, "should-show-delete-interface-for-element", kitElement.get(), &accept); |
| return accept; |
| } |
| |
| bool EditorClient::isContinuousSpellCheckingEnabled() |
| { |
| WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView); |
| |
| gboolean enabled; |
| g_object_get(settings, "enable-spell-checking", &enabled, NULL); |
| |
| return enabled; |
| } |
| |
| bool EditorClient::isGrammarCheckingEnabled() |
| { |
| notImplemented(); |
| return false; |
| } |
| |
| int EditorClient::spellCheckerDocumentTag() |
| { |
| notImplemented(); |
| return 0; |
| } |
| |
| bool EditorClient::shouldBeginEditing(WebCore::Range* range) |
| { |
| gboolean accept = TRUE; |
| GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range))); |
| g_signal_emit_by_name(m_webView, "should-begin-editing", kitRange.get(), &accept); |
| return accept; |
| } |
| |
| bool EditorClient::shouldEndEditing(WebCore::Range* range) |
| { |
| gboolean accept = TRUE; |
| GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range))); |
| g_signal_emit_by_name(m_webView, "should-end-editing", kitRange.get(), &accept); |
| return accept; |
| } |
| |
| static WebKitInsertAction kit(EditorInsertAction action) |
| { |
| switch (action) { |
| case EditorInsertActionTyped: |
| return WEBKIT_INSERT_ACTION_TYPED; |
| case EditorInsertActionPasted: |
| return WEBKIT_INSERT_ACTION_PASTED; |
| case EditorInsertActionDropped: |
| return WEBKIT_INSERT_ACTION_DROPPED; |
| } |
| ASSERT_NOT_REACHED(); |
| return WEBKIT_INSERT_ACTION_TYPED; |
| } |
| |
| bool EditorClient::shouldInsertText(const String& string, Range* range, EditorInsertAction action) |
| { |
| gboolean accept = TRUE; |
| GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range))); |
| g_signal_emit_by_name(m_webView, "should-insert-text", string.utf8().data(), kitRange.get(), kit(action), &accept); |
| return accept; |
| } |
| |
| static WebKitSelectionAffinity kit(EAffinity affinity) |
| { |
| switch (affinity) { |
| case UPSTREAM: |
| return WEBKIT_SELECTION_AFFINITY_UPSTREAM; |
| case DOWNSTREAM: |
| return WEBKIT_SELECTION_AFFINITY_DOWNSTREAM; |
| } |
| ASSERT_NOT_REACHED(); |
| return WEBKIT_SELECTION_AFFINITY_UPSTREAM; |
| } |
| |
| bool EditorClient::shouldChangeSelectedRange(Range* fromRange, Range* toRange, EAffinity affinity, bool stillSelecting) |
| { |
| gboolean accept = TRUE; |
| GRefPtr<WebKitDOMRange> kitFromRange(fromRange ? adoptGRef(kit(fromRange)) : 0); |
| GRefPtr<WebKitDOMRange> kitToRange(toRange ? adoptGRef(kit(toRange)) : 0); |
| g_signal_emit_by_name(m_webView, "should-change-selected-range", kitFromRange.get(), kitToRange.get(), |
| kit(affinity), stillSelecting, &accept); |
| return accept; |
| } |
| |
| bool EditorClient::shouldApplyStyle(WebCore::StylePropertySet* set, WebCore::Range* range) |
| { |
| gboolean accept = TRUE; |
| GRefPtr<WebKitDOMCSSStyleDeclaration> kitDeclaration(kit(set->ensureCSSStyleDeclaration())); |
| GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range))); |
| g_signal_emit_by_name(m_webView, "should-apply-style", kitDeclaration.get(), kitRange.get(), &accept); |
| return accept; |
| } |
| |
| bool EditorClient::shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*) |
| { |
| notImplemented(); |
| return true; |
| } |
| |
| void EditorClient::didBeginEditing() |
| { |
| g_signal_emit_by_name(m_webView, "editing-began"); |
| } |
| |
| void EditorClient::respondToChangedContents() |
| { |
| g_signal_emit_by_name(m_webView, "user-changed-contents"); |
| } |
| |
| static WebKitWebView* viewSettingClipboard = 0; |
| static void collapseSelection(GtkClipboard* clipboard, WebKitWebView* webView) |
| { |
| if (viewSettingClipboard && viewSettingClipboard == webView) |
| return; |
| |
| WebCore::Page* corePage = core(webView); |
| if (!corePage || !corePage->focusController()) |
| return; |
| |
| Frame* frame = corePage->focusController()->focusedOrMainFrame(); |
| |
| // Collapse the selection without clearing it |
| ASSERT(frame); |
| frame->selection()->setBase(frame->selection()->extent(), frame->selection()->affinity()); |
| } |
| |
| #if PLATFORM(X11) |
| static void setSelectionPrimaryClipboardIfNeeded(WebKitWebView* webView) |
| { |
| if (!gtk_widget_has_screen(GTK_WIDGET(webView))) |
| return; |
| |
| GtkClipboard* clipboard = gtk_widget_get_clipboard(GTK_WIDGET(webView), GDK_SELECTION_PRIMARY); |
| DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard); |
| WebCore::Page* corePage = core(webView); |
| Frame* targetFrame = corePage->focusController()->focusedOrMainFrame(); |
| |
| if (!targetFrame->selection()->isRange()) |
| return; |
| |
| dataObject->clearAll(); |
| dataObject->setRange(targetFrame->selection()->toNormalizedRange()); |
| |
| viewSettingClipboard = webView; |
| GClosure* callback = g_cclosure_new_object(G_CALLBACK(collapseSelection), G_OBJECT(webView)); |
| g_closure_set_marshal(callback, g_cclosure_marshal_VOID__VOID); |
| PasteboardHelper::defaultPasteboardHelper()->writeClipboardContents(clipboard, PasteboardHelper::DoNotIncludeSmartPaste, callback); |
| viewSettingClipboard = 0; |
| } |
| #endif |
| |
| void EditorClient::respondToChangedSelection(Frame* frame) |
| { |
| g_signal_emit_by_name(m_webView, "selection-changed"); |
| |
| if (!frame) |
| return; |
| |
| #if PLATFORM(X11) |
| setSelectionPrimaryClipboardIfNeeded(m_webView); |
| #endif |
| |
| if (!frame->editor()->hasComposition() || frame->editor()->ignoreCompositionSelectionChange()) |
| return; |
| |
| unsigned start; |
| unsigned end; |
| if (!frame->editor()->getCompositionSelection(start, end)) |
| m_webView->priv->imFilter.resetContext(); |
| } |
| |
| void EditorClient::didEndEditing() |
| { |
| g_signal_emit_by_name(m_webView, "editing-ended"); |
| } |
| |
| void EditorClient::didWriteSelectionToPasteboard() |
| { |
| notImplemented(); |
| } |
| |
| void EditorClient::didSetSelectionTypesForPasteboard() |
| { |
| notImplemented(); |
| } |
| |
| void EditorClient::registerUndoStep(WTF::PassRefPtr<WebCore::UndoStep> step) |
| { |
| if (undoStack.size() == maximumUndoStackDepth) |
| undoStack.removeFirst(); |
| if (!m_isInRedo) |
| redoStack.clear(); |
| undoStack.append(step); |
| } |
| |
| void EditorClient::registerRedoStep(WTF::PassRefPtr<WebCore::UndoStep> step) |
| { |
| redoStack.append(step); |
| } |
| |
| void EditorClient::clearUndoRedoOperations() |
| { |
| undoStack.clear(); |
| redoStack.clear(); |
| } |
| |
| bool EditorClient::canCopyCut(WebCore::Frame*, bool defaultValue) const |
| { |
| return defaultValue; |
| } |
| |
| bool EditorClient::canPaste(WebCore::Frame*, bool defaultValue) const |
| { |
| return defaultValue; |
| } |
| |
| bool EditorClient::canUndo() const |
| { |
| return !undoStack.isEmpty(); |
| } |
| |
| bool EditorClient::canRedo() const |
| { |
| return !redoStack.isEmpty(); |
| } |
| |
| void EditorClient::undo() |
| { |
| if (canUndo()) { |
| RefPtr<WebCore::UndoStep> step(*(--undoStack.end())); |
| undoStack.remove(--undoStack.end()); |
| // unapply will call us back to push this command onto the redo stack. |
| step->unapply(); |
| } |
| } |
| |
| void EditorClient::redo() |
| { |
| if (canRedo()) { |
| RefPtr<WebCore::UndoStep> step(*(--redoStack.end())); |
| redoStack.remove(--redoStack.end()); |
| |
| ASSERT(!m_isInRedo); |
| m_isInRedo = true; |
| // reapply will call us back to push this command onto the undo stack. |
| step->reapply(); |
| m_isInRedo = false; |
| } |
| } |
| |
| bool EditorClient::shouldInsertNode(Node* node, Range* range, EditorInsertAction action) |
| { |
| gboolean accept = TRUE; |
| GRefPtr<WebKitDOMRange> kitRange(adoptGRef(kit(range))); |
| GRefPtr<WebKitDOMNode> kitNode(adoptGRef(kit(node))); |
| g_signal_emit_by_name(m_webView, "should-insert-node", kitNode.get(), kitRange.get(), kit(action), &accept); |
| return accept; |
| } |
| |
| void EditorClient::pageDestroyed() |
| { |
| delete this; |
| } |
| |
| void EditorClient::setSmartInsertDeleteEnabled(bool enabled) |
| { |
| m_smartInsertDeleteEnabled = enabled; |
| } |
| |
| bool EditorClient::smartInsertDeleteEnabled() |
| { |
| return m_smartInsertDeleteEnabled; |
| } |
| |
| bool EditorClient::isSelectTrailingWhitespaceEnabled() |
| { |
| if (!DumpRenderTreeSupportGtk::dumpRenderTreeModeEnabled()) |
| return false; |
| return DumpRenderTreeSupportGtk::selectTrailingWhitespaceEnabled(); |
| } |
| |
| void EditorClient::toggleContinuousSpellChecking() |
| { |
| WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView); |
| |
| gboolean enabled; |
| g_object_get(settings, "enable-spell-checking", &enabled, NULL); |
| |
| g_object_set(settings, "enable-spell-checking", !enabled, NULL); |
| } |
| |
| void EditorClient::toggleGrammarChecking() |
| { |
| } |
| |
| bool EditorClient::executePendingEditorCommands(Frame* frame, bool allowTextInsertion) |
| { |
| Vector<Editor::Command> commands; |
| for (size_t i = 0; i < m_pendingEditorCommands.size(); i++) { |
| Editor::Command command = frame->editor()->command(m_pendingEditorCommands.at(i).utf8().data()); |
| if (command.isTextInsertion() && !allowTextInsertion) |
| return false; |
| |
| commands.append(command); |
| } |
| |
| bool success = true; |
| for (size_t i = 0; i < commands.size(); i++) { |
| if (!commands.at(i).execute()) { |
| success = false; |
| break; |
| } |
| } |
| |
| m_pendingEditorCommands.clear(); |
| return success; |
| } |
| |
| static bool keyboardEventHadCompositionResults(KeyboardEvent* event) |
| { |
| if (event->type() != eventNames().keydownEvent) |
| return false; |
| |
| const PlatformKeyboardEvent* platformEvent = event->keyEvent(); |
| if (!platformEvent) |
| return false; |
| |
| return platformEvent->compositionResults().compositionUpdated(); |
| } |
| |
| void EditorClient::handleKeyboardEvent(KeyboardEvent* event) |
| { |
| Node* node = event->target()->toNode(); |
| ASSERT(node); |
| Frame* frame = node->document()->frame(); |
| ASSERT(frame); |
| |
| const PlatformKeyboardEvent* platformEvent = event->keyEvent(); |
| if (!platformEvent) |
| return; |
| |
| if (keyboardEventHadCompositionResults(event)) |
| return; |
| |
| KeyBindingTranslator::EventType type = event->type() == eventNames().keydownEvent ? |
| KeyBindingTranslator::KeyDown : KeyBindingTranslator::KeyPress; |
| m_keyBindingTranslator.getEditorCommandsForKeyEvent(platformEvent->gdkEventKey(), type, m_pendingEditorCommands); |
| if (m_pendingEditorCommands.size() > 0) { |
| |
| // During RawKeyDown events if an editor command will insert text, defer |
| // the insertion until the keypress event. We want keydown to bubble up |
| // through the DOM first. |
| if (platformEvent->type() == PlatformEvent::RawKeyDown) { |
| if (executePendingEditorCommands(frame, false)) |
| event->setDefaultHandled(); |
| |
| return; |
| } |
| |
| // Only allow text insertion commands if the current node is editable. |
| if (executePendingEditorCommands(frame, frame->editor()->canEdit())) { |
| event->setDefaultHandled(); |
| return; |
| } |
| } |
| |
| // Don't allow text insertion for nodes that cannot edit. |
| if (!frame->editor()->canEdit()) |
| return; |
| |
| // This is just a normal text insertion, so wait to execute the insertion |
| // until a keypress event happens. This will ensure that the insertion will not |
| // be reflected in the contents of the field until the keyup DOM event. |
| if (event->type() != eventNames().keypressEvent) |
| return; |
| |
| // Don't insert null or control characters as they can result in unexpected behaviour |
| if (event->charCode() < ' ') |
| return; |
| |
| // Don't insert anything if a modifier is pressed |
| if (platformEvent->ctrlKey() || platformEvent->altKey()) |
| return; |
| |
| if (frame->editor()->insertText(platformEvent->text(), event)) |
| event->setDefaultHandled(); |
| } |
| |
| void EditorClient::handleInputMethodKeydown(KeyboardEvent* event) |
| { |
| // Input method results are handled in handleKeyboardEvent, so that we can wait |
| // to trigger composition updates until after the keydown event handler. This better |
| // matches other browsers. |
| const PlatformKeyboardEvent* platformEvent = event->keyEvent(); |
| if (platformEvent && platformEvent->compositionResults().compositionUpdated()) |
| event->preventDefault(); |
| } |
| |
| EditorClient::EditorClient(WebKitWebView* webView) |
| : m_isInRedo(false) |
| #if ENABLE(SPELLCHECK) |
| , m_textCheckerClient(WEBKIT_SPELL_CHECKER(webkit_get_text_checker())) |
| #endif |
| , m_webView(webView) |
| , m_smartInsertDeleteEnabled(false) |
| { |
| } |
| |
| EditorClient::~EditorClient() |
| { |
| } |
| |
| void EditorClient::textFieldDidBeginEditing(Element*) |
| { |
| } |
| |
| void EditorClient::textFieldDidEndEditing(Element*) |
| { |
| } |
| |
| void EditorClient::textDidChangeInTextField(Element*) |
| { |
| } |
| |
| bool EditorClient::doTextFieldCommandFromEvent(Element*, KeyboardEvent*) |
| { |
| return false; |
| } |
| |
| void EditorClient::textWillBeDeletedInTextField(Element*) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClient::textDidChangeInTextArea(Element*) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClient::updateSpellingUIWithGrammarString(const String&, const GrammarDetail&) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClient::updateSpellingUIWithMisspelledWord(const String&) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClient::showSpellingUI(bool) |
| { |
| notImplemented(); |
| } |
| |
| bool EditorClient::spellingUIIsShowing() |
| { |
| notImplemented(); |
| return false; |
| } |
| |
| bool EditorClient::supportsGlobalSelection() |
| { |
| #if PLATFORM(X11) |
| return true; |
| #else |
| return false; |
| #endif |
| } |
| |
| } |