2010-12-08 Gustavo Noronha Silva <gustavo.noronha@collabora.co.uk>
[WebKit-https.git] / WebKit / gtk / WebCoreSupport / EditorClientGtk.cpp
index cd04782..6e31b03 100644 (file)
 #include "Page.h"
 #include "PasteboardHelperGtk.h"
 #include "PlatformKeyboardEvent.h"
+#include "WebKitDOMBinding.h"
+#include "WebKitDOMCSSStyleDeclarationPrivate.h"
+#include "WebKitDOMHTMLElementPrivate.h"
+#include "WebKitDOMNodePrivate.h"
+#include "WebKitDOMRangePrivate.h"
 #include "WindowsKeyboardCodes.h"
 #include "webkitmarshal.h"
 #include "webkitprivate.h"
+#include "webkitwebviewprivate.h"
 #include <wtf/text/CString.h>
 
 // Arbitrary depth limit for the undo stack, to keep it from using
@@ -67,6 +73,11 @@ static void imContextCommitted(GtkIMContext* context, const gchar* compositionSt
         return;
     }
 
+    // If this signal fires during a mousepress event when we are in the middle
+    // of a composition, skip this 'commit' because the composition is already confirmed. 
+    if (client->preventNextCompositionCommit()) 
+        return;
     frame->editor()->confirmComposition(String::fromUTF8(compositionString));
     client->clearPendingComposition();
 }
@@ -117,6 +128,25 @@ static void pasteClipboardCallback(GtkWidget* widget, EditorClient* client)
     client->addPendingEditorCommand("Paste");
 }
 
+static void toggleOverwriteCallback(GtkWidget* widget, EditorClient*)
+{
+    // We don't support toggling the overwrite mode, but the default callback expects
+    // the GtkTextView to have a layout, so we handle this signal just to stop it.
+    g_signal_stop_emission_by_name(widget, "toggle-overwrite");
+}
+
+// GTK+ will still send these signals to the web view. So we can safely stop signal
+// emission without breaking accessibility.
+static void popupMenuCallback(GtkWidget* widget, EditorClient*)
+{
+    g_signal_stop_emission_by_name(widget, "popup-menu");
+}
+
+static void showHelpCallback(GtkWidget* widget, EditorClient*)
+{
+    g_signal_stop_emission_by_name(widget, "show-help");
+}
+
 static const char* const gtkDeleteCommands[][2] = {
     { "DeleteBackward",               "DeleteForward"                        }, // Characters
     { "DeleteWordBackward",           "DeleteWordForward"                    }, // Word ends
@@ -201,6 +231,13 @@ static void moveCursorCallback(GtkWidget* widget, GtkMovementStep step, gint cou
     if (!rawCommand)
         return;
 
+    if (isSpatialNavigationEnabled(core(client->webView())->focusController()->focusedOrMainFrame()) && step == 1) {
+        if (direction == 1)
+            rawCommand = "MoveRight";
+        else if (!direction)
+            rawCommand = "MoveLeft";
+    }
+
     for (int i = 0; i < abs(count); i++)
         client->addPendingEditorCommand(rawCommand);
 }
@@ -224,27 +261,32 @@ void EditorClient::setInputMethodState(bool active)
     WebKitWebViewPrivate* priv = m_webView->priv;
 
     if (active)
-        gtk_im_context_focus_in(priv->imContext);
+        gtk_im_context_focus_in(priv->imContext.get());
     else
-        gtk_im_context_focus_out(priv->imContext);
+        gtk_im_context_focus_out(priv->imContext.get());
 
 #ifdef MAEMO_CHANGES
     if (active)
-        hildon_gtk_im_context_show(priv->imContext);
+        hildon_gtk_im_context_show(priv->imContext.get());
     else
-        hildon_gtk_im_context_hide(priv->imContext);
+        hildon_gtk_im_context_hide(priv->imContext.get());
 #endif
 }
 
-bool EditorClient::shouldDeleteRange(Range*)
+bool EditorClient::shouldDeleteRange(Range* range)
 {
-    notImplemented();
-    return true;
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMRange> kitRange(adoptPlatformRef(kit(range)));
+    g_signal_emit_by_name(m_webView, "should-delete-range", kitRange.get(), &accept);
+    return accept;
 }
 
-bool EditorClient::shouldShowDeleteInterface(HTMLElement*)
+bool EditorClient::shouldShowDeleteInterface(HTMLElement* element)
 {
-    return false;
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMHTMLElement> kitElement(adoptPlatformRef(kit(element)));
+    g_signal_emit_by_name(m_webView, "should-show-delete-interface-for-element", kitElement.get(), &accept);
+    return accept;
 }
 
 bool EditorClient::isContinuousSpellCheckingEnabled()
@@ -269,38 +311,77 @@ int EditorClient::spellCheckerDocumentTag()
     return 0;
 }
 
-bool EditorClient::shouldBeginEditing(WebCore::Range*)
+bool EditorClient::shouldBeginEditing(WebCore::Range* range)
 {
     clearPendingComposition();
 
-    notImplemented();
-    return true;
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMRange> kitRange(adoptPlatformRef(kit(range)));
+    g_signal_emit_by_name(m_webView, "should-begin-editing", kitRange.get(), &accept);
+    return accept;
 }
 
-bool EditorClient::shouldEndEditing(WebCore::Range*)
+bool EditorClient::shouldEndEditing(WebCore::Range* range)
 {
     clearPendingComposition();
 
-    notImplemented();
-    return true;
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMRange> kitRange(adoptPlatformRef(kit(range)));
+    g_signal_emit_by_name(m_webView, "should-end-editing", kitRange.get(), &accept);
+    return accept;
 }
 
-bool EditorClient::shouldInsertText(const String&, Range*, EditorInsertAction)
+static WebKitInsertAction kit(EditorInsertAction action)
 {
-    notImplemented();
-    return true;
+    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::shouldChangeSelectedRange(Range*, Range*, EAffinity, bool)
+bool EditorClient::shouldInsertText(const String& string, Range* range, EditorInsertAction action)
 {
-    notImplemented();
-    return true;
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMRange> kitRange(adoptPlatformRef(kit(range)));
+    g_signal_emit_by_name(m_webView, "should-insert-text", string.utf8().data(), kitRange.get(), kit(action), &accept);
+    return accept;
 }
 
-bool EditorClient::shouldApplyStyle(WebCore::CSSStyleDeclaration*, WebCore::Range*)
+static WebKitSelectionAffinity kit(EAffinity affinity)
 {
-    notImplemented();
-    return true;
+    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;
+    PlatformRefPtr<WebKitDOMRange> kitFromRange(fromRange ? adoptPlatformRef(kit(fromRange)) : 0);
+    PlatformRefPtr<WebKitDOMRange> kitToRange(toRange ? adoptPlatformRef(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::CSSStyleDeclaration* declaration, WebCore::Range* range)
+{
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMCSSStyleDeclaration> kitDeclaration(kit(declaration));
+    PlatformRefPtr<WebKitDOMRange> kitRange(adoptPlatformRef(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*)
@@ -311,12 +392,12 @@ bool EditorClient::shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*)
 
 void EditorClient::didBeginEditing()
 {
-    notImplemented();
+    g_signal_emit_by_name(m_webView, "editing-began");
 }
 
 void EditorClient::respondToChangedContents()
 {
-    notImplemented();
+    g_signal_emit_by_name(m_webView, "user-changed-contents");
 }
 
 static WebKitWebView* viewSettingClipboard = 0;
@@ -336,8 +417,35 @@ static void collapseSelection(GtkClipboard* clipboard, WebKitWebView* webView)
     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->clear();
+    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);
+    pasteboardHelperInstance()->writeClipboardContents(clipboard, callback);
+    viewSettingClipboard = 0;
+}
+#endif
+
 void EditorClient::respondToChangedSelection()
 {
+    g_signal_emit_by_name(m_webView, "selection-changed");
+
     WebKitWebViewPrivate* priv = m_webView->priv;
     WebCore::Page* corePage = core(m_webView);
     Frame* targetFrame = corePage->focusController()->focusedOrMainFrame();
@@ -349,19 +457,7 @@ void EditorClient::respondToChangedSelection()
         return;
 
 #if PLATFORM(X11)
-    GtkClipboard* clipboard = gtk_widget_get_clipboard(GTK_WIDGET(m_webView), GDK_SELECTION_PRIMARY);
-    DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
-
-    if (targetFrame->selection()->isRange()) {
-        dataObject->clear();
-        dataObject->setRange(targetFrame->selection()->toNormalizedRange());
-
-        viewSettingClipboard = m_webView;
-        GClosure* callback = g_cclosure_new_object(G_CALLBACK(collapseSelection), G_OBJECT(m_webView));
-        g_closure_set_marshal(callback, g_cclosure_marshal_VOID__VOID);
-        pasteboardHelperInstance()->writeClipboardContents(clipboard, callback);
-        viewSettingClipboard = 0;
-    }
+    setSelectionPrimaryClipboardIfNeeded(m_webView);
 #endif
 
     if (!targetFrame->editor()->hasComposition())
@@ -371,14 +467,14 @@ void EditorClient::respondToChangedSelection()
     unsigned end;
     if (!targetFrame->editor()->getCompositionSelection(start, end)) {
         // gtk_im_context_reset() clears the composition for us.
-        gtk_im_context_reset(priv->imContext);
+        gtk_im_context_reset(priv->imContext.get());
         targetFrame->editor()->confirmCompositionWithoutDisturbingSelection();
     }
 }
 
 void EditorClient::didEndEditing()
 {
-    notImplemented();
+    g_signal_emit_by_name(m_webView, "editing-ended");
 }
 
 void EditorClient::didWriteSelectionToPasteboard()
@@ -450,10 +546,13 @@ void EditorClient::redo()
     }
 }
 
-bool EditorClient::shouldInsertNode(Node*, Range*, EditorInsertAction)
+bool EditorClient::shouldInsertNode(Node* node, Range* range, EditorInsertAction action)
 {
-    notImplemented();
-    return true;
+    gboolean accept = TRUE;
+    PlatformRefPtr<WebKitDOMRange> kitRange(adoptPlatformRef(kit(range)));
+    PlatformRefPtr<WebKitDOMNode> kitNode(adoptPlatformRef(kit(node)));
+    g_signal_emit_by_name(m_webView, "should-insert-node", kitNode.get(), kitRange.get(), kit(action), &accept);
+    return accept;
 }
 
 void EditorClient::pageDestroyed()
@@ -532,7 +631,11 @@ void EditorClient::generateEditorCommands(const KeyboardEvent* event)
     m_pendingEditorCommands.clear();
 
     // First try to interpret the command as a native GTK+ key binding.
+#ifdef GTK_API_VERSION_2
     gtk_bindings_activate_event(GTK_OBJECT(m_nativeWidget.get()), event->keyEvent()->gdkEventKey());
+#else
+    gtk_bindings_activate_event(G_OBJECT(m_nativeWidget.get()), event->keyEvent()->gdkEventKey());
+#endif
     if (m_pendingEditorCommands.size() > 0)
         return;
 
@@ -670,6 +773,7 @@ void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
 
     WebKitWebViewPrivate* priv = m_webView->priv;
 
+    m_preventNextCompositionCommit = false;
 
     // Some IM contexts (e.g. 'simple') will act as if they filter every
     // keystroke and just issue a 'commit' signal during handling. In situations
@@ -704,22 +808,45 @@ void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
     m_treatContextCommitAsKeyEvent = (!targetFrame->editor()->hasComposition())
          && event->keyEvent()->gdkEventKey()->keyval;
     clearPendingComposition();
-    if ((gtk_im_context_filter_keypress(priv->imContext, event->keyEvent()->gdkEventKey()) && !m_pendingComposition)
+    if ((gtk_im_context_filter_keypress(priv->imContext.get(), event->keyEvent()->gdkEventKey()) && !m_pendingComposition)
         || (!m_treatContextCommitAsKeyEvent && !targetFrame->editor()->hasComposition()))
         event->preventDefault();
 
     m_treatContextCommitAsKeyEvent = false;
 }
 
+void EditorClient::handleInputMethodMousePress()
+{
+    Frame* targetFrame = core(m_webView)->focusController()->focusedOrMainFrame();
+
+    if (!targetFrame || !targetFrame->editor()->canEdit())
+        return;
+
+    WebKitWebViewPrivate* priv = m_webView->priv;
+
+    // When a mouse press fires, the commit signal happens during a composition.
+    // In this case, if the focused node is changed, the commit signal happens in a diffrent node.
+    // Therefore, we need to confirm the current compositon and ignore the next commit signal. 
+    GOwnPtr<gchar> newPreedit(0);
+    gtk_im_context_get_preedit_string(priv->imContext.get(), &newPreedit.outPtr(), 0, 0);
+    
+    if (g_utf8_strlen(newPreedit.get(), -1)) {
+        targetFrame->editor()->confirmComposition();
+        m_preventNextCompositionCommit = true;
+        gtk_im_context_reset(priv->imContext.get());
+    } 
+}
+
 EditorClient::EditorClient(WebKitWebView* webView)
     : m_isInRedo(false)
     , m_webView(webView)
+    , m_preventNextCompositionCommit(false)
     , m_treatContextCommitAsKeyEvent(false)
     , m_nativeWidget(gtk_text_view_new())
 {
     WebKitWebViewPrivate* priv = m_webView->priv;
-    g_signal_connect(priv->imContext, "commit", G_CALLBACK(imContextCommitted), this);
-    g_signal_connect(priv->imContext, "preedit-changed", G_CALLBACK(imContextPreeditChanged), this);
+    g_signal_connect(priv->imContext.get(), "commit", G_CALLBACK(imContextCommitted), this);
+    g_signal_connect(priv->imContext.get(), "preedit-changed", G_CALLBACK(imContextPreeditChanged), this);
 
     g_signal_connect(m_nativeWidget.get(), "backspace", G_CALLBACK(backspaceCallback), this);
     g_signal_connect(m_nativeWidget.get(), "cut-clipboard", G_CALLBACK(cutClipboardCallback), this);
@@ -728,13 +855,16 @@ EditorClient::EditorClient(WebKitWebView* webView)
     g_signal_connect(m_nativeWidget.get(), "select-all", G_CALLBACK(selectAllCallback), this);
     g_signal_connect(m_nativeWidget.get(), "move-cursor", G_CALLBACK(moveCursorCallback), this);
     g_signal_connect(m_nativeWidget.get(), "delete-from-cursor", G_CALLBACK(deleteFromCursorCallback), this);
+    g_signal_connect(m_nativeWidget.get(), "toggle-overwrite", G_CALLBACK(toggleOverwriteCallback), this);
+    g_signal_connect(m_nativeWidget.get(), "popup-menu", G_CALLBACK(popupMenuCallback), this);
+    g_signal_connect(m_nativeWidget.get(), "show-help", G_CALLBACK(showHelpCallback), this);
 }
 
 EditorClient::~EditorClient()
 {
     WebKitWebViewPrivate* priv = m_webView->priv;
-    g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextCommitted, this);
-    g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextPreeditChanged, this);
+    g_signal_handlers_disconnect_by_func(priv->imContext.get(), (gpointer)imContextCommitted, this);
+    g_signal_handlers_disconnect_by_func(priv->imContext.get(), (gpointer)imContextPreeditChanged, this);
 }
 
 void EditorClient::textFieldDidBeginEditing(Element*)
@@ -879,7 +1009,7 @@ bool EditorClient::spellingUIIsShowing()
     return false;
 }
 
-void EditorClient::getGuessesForWord(const String& word, WTF::Vector<String>& guesses)
+void EditorClient::getGuessesForWord(const String& word, const String& context, WTF::Vector<String>& guesses)
 {
     GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
     guesses.clear();