summaryrefslogtreecommitdiffstats
path: root/WebKit/gtk/WebCoreSupport/EditorClientGtk.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'WebKit/gtk/WebCoreSupport/EditorClientGtk.cpp')
-rw-r--r--WebKit/gtk/WebCoreSupport/EditorClientGtk.cpp209
1 files changed, 136 insertions, 73 deletions
diff --git a/WebKit/gtk/WebCoreSupport/EditorClientGtk.cpp b/WebKit/gtk/WebCoreSupport/EditorClientGtk.cpp
index 02d1a53..51172b4 100644
--- a/WebKit/gtk/WebCoreSupport/EditorClientGtk.cpp
+++ b/WebKit/gtk/WebCoreSupport/EditorClientGtk.cpp
@@ -4,6 +4,7 @@
* Copyright (C) 2009 Diego Escalante Urrelo <diegoe@gnome.org>
* Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
* Copyright (C) 2009, 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
@@ -23,7 +24,6 @@
#include "config.h"
#include "EditorClientGtk.h"
-#include "CString.h"
#include "DataObjectGtk.h"
#include "EditCommand.h"
#include "Editor.h"
@@ -32,14 +32,16 @@
#include "FocusController.h"
#include "Frame.h"
#include <glib.h>
-#include "KeyboardCodes.h"
#include "KeyboardEvent.h"
+#include "markup.h"
#include "NotImplemented.h"
#include "Page.h"
#include "PasteboardHelperGtk.h"
#include "PlatformKeyboardEvent.h"
-#include "markup.h"
+#include "WindowsKeyboardCodes.h"
+#include "webkitmarshal.h"
#include "webkitprivate.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
@@ -51,40 +53,48 @@ using namespace WebCore;
namespace WebKit {
-static gchar* pendingComposition = 0;
-static gchar* pendingPreedit = 0;
-
-static void setPendingComposition(gchar* newComposition)
+static void imContextCommitted(GtkIMContext* context, const gchar* compositionString, EditorClient* client)
{
- g_free(pendingComposition);
- pendingComposition = newComposition;
-}
+ Frame* frame = core(client->webView())->focusController()->focusedOrMainFrame();
+ if (!frame || !frame->editor()->canEdit())
+ return;
-static void setPendingPreedit(gchar* newPreedit)
-{
- g_free(pendingPreedit);
- pendingPreedit = newPreedit;
-}
+ // If this signal fires during a keydown event when we are not in the middle
+ // of a composition, then treat this 'commit' as a normal key event and just
+ // change the editable area right before the keypress event.
+ if (client->treatContextCommitAsKeyEvent()) {
+ client->updatePendingComposition(compositionString);
+ return;
+ }
-static void clearPendingIMData()
-{
- setPendingComposition(0);
- setPendingPreedit(0);
-}
-static void imContextCommitted(GtkIMContext* context, const gchar* str, EditorClient* client)
-{
- // This signal will fire during a keydown event. We want the contents of the
- // field to change right before the keyup event, so we wait until then to actually
- // commit this composition.
- setPendingComposition(g_strdup(str));
+ frame->editor()->confirmComposition(String::fromUTF8(compositionString));
+ client->clearPendingComposition();
}
static void imContextPreeditChanged(GtkIMContext* context, EditorClient* client)
{
+ Frame* frame = core(client->webView())->focusController()->focusedOrMainFrame();
+ if (!frame || !frame->editor()->canEdit())
+ return;
+
// We ignore the provided PangoAttrList for now.
- gchar* newPreedit = 0;
- gtk_im_context_get_preedit_string(context, &newPreedit, NULL, NULL);
- setPendingPreedit(newPreedit);
+ GOwnPtr<gchar> newPreedit(0);
+ gtk_im_context_get_preedit_string(context, &newPreedit.outPtr(), 0, 0);
+
+ String preeditString = String::fromUTF8(newPreedit.get());
+ Vector<CompositionUnderline> underlines;
+ underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
+ frame->editor()->setComposition(preeditString, underlines, 0, 0);
+}
+
+void EditorClient::updatePendingComposition(const gchar* newComposition)
+{
+ // The IMContext may signal more than one completed composition in a row,
+ // in which case we want to append them, rather than overwrite the old one.
+ if (!m_pendingComposition)
+ m_pendingComposition.set(g_strdup(newComposition));
+ else
+ m_pendingComposition.set(g_strconcat(m_pendingComposition.get(), newComposition, NULL));
}
void EditorClient::setInputMethodState(bool active)
@@ -139,7 +149,7 @@ int EditorClient::spellCheckerDocumentTag()
bool EditorClient::shouldBeginEditing(WebCore::Range*)
{
- clearPendingIMData();
+ clearPendingComposition();
notImplemented();
return true;
@@ -147,7 +157,7 @@ bool EditorClient::shouldBeginEditing(WebCore::Range*)
bool EditorClient::shouldEndEditing(WebCore::Range*)
{
- clearPendingIMData();
+ clearPendingComposition();
notImplemented();
return true;
@@ -187,6 +197,23 @@ void EditorClient::respondToChangedContents()
notImplemented();
}
+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());
+}
+
void EditorClient::respondToChangedSelection()
{
WebKitWebViewPrivate* priv = m_webView->priv;
@@ -206,7 +233,12 @@ void EditorClient::respondToChangedSelection()
if (targetFrame->selection()->isRange()) {
dataObject->clear();
dataObject->setRange(targetFrame->selection()->toNormalizedRange());
- pasteboardHelperInstance()->writeClipboardContents(clipboard, m_webView);
+
+ 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;
}
#endif
@@ -466,8 +498,12 @@ void EditorClient::handleKeyboardEvent(KeyboardEvent* event)
if (!command.isTextInsertion() && command.execute(event))
event->setDefaultHandled();
+ clearPendingComposition();
return;
- } else if (command.execute(event)) {
+ }
+
+ if (command.execute(event)) {
+ clearPendingComposition();
event->setDefaultHandled();
return;
}
@@ -478,25 +514,14 @@ void EditorClient::handleKeyboardEvent(KeyboardEvent* event)
// be reflected in the contents of the field until the keyup DOM event.
if (event->type() == eventNames().keypressEvent) {
- if (pendingComposition) {
- String compositionString = String::fromUTF8(pendingComposition);
- frame->editor()->confirmComposition(compositionString);
-
- clearPendingIMData();
- event->setDefaultHandled();
-
- } else if (pendingPreedit) {
- String preeditString = String::fromUTF8(pendingPreedit);
-
- // Don't use an empty preedit as it will destroy the current
- // selection, even if the composition is cancelled or fails later on.
- if (!preeditString.isEmpty()) {
- Vector<CompositionUnderline> underlines;
- underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
- frame->editor()->setComposition(preeditString, underlines, 0, 0);
- }
-
- clearPendingIMData();
+ // If we have a pending composition at this point, it happened while
+ // filtering a keypress, so we treat it as a normal text insertion.
+ // This will also ensure that if the keypress event handler changed the
+ // currently focused node, the text is still inserted into the original
+ // node (insertText() has this logic, but confirmComposition() does not).
+ if (m_pendingComposition) {
+ frame->editor()->insertText(String::fromUTF8(m_pendingComposition.get()), event);
+ clearPendingComposition();
event->setDefaultHandled();
} else {
@@ -520,15 +545,53 @@ void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
if (!targetFrame || !targetFrame->editor()->canEdit())
return;
- // TODO: We need to decide which filtered keystrokes should be treated as IM
- // events and which should not.
WebKitWebViewPrivate* priv = m_webView->priv;
- gtk_im_context_filter_keypress(priv->imContext, event->keyEvent()->gdkEventKey());
+
+
+ // Some IM contexts (e.g. 'simple') will act as if they filter every
+ // keystroke and just issue a 'commit' signal during handling. In situations
+ // where the 'commit' signal happens during filtering and there is no active
+ // composition, act as if the keystroke was not filtered. The one exception to
+ // this is when the keyval parameter of the GdkKeyEvent is 0, which is often
+ // a key event sent by the IM context for committing the current composition.
+
+ // Here is a typical sequence of events for the 'simple' context:
+ // 1. GDK key press event -> webkit_web_view_key_press_event
+ // 2. Keydown event -> EditorClient::handleInputMethodKeydown
+ // gtk_im_context_filter_keypress returns true, but there is a pending
+ // composition so event->preventDefault is not called (below).
+ // 3. Keydown event bubbles through the DOM
+ // 4. Keydown event -> EditorClient::handleKeyboardEvent
+ // No action taken.
+ // 4. GDK key release event -> webkit_web_view_key_release_event
+ // 5. gtk_im_context_filter_keypress is called on the release event.
+ // Simple does not filter most key releases, so the event continues.
+ // 6. Keypress event bubbles through the DOM.
+ // 7. Keypress event -> EditorClient::handleKeyboardEvent
+ // pending composition is inserted.
+ // 8. Keyup event bubbles through the DOM.
+ // 9. Keyup event -> EditorClient::handleKeyboardEvent
+ // No action taken.
+
+ // There are two situations where we do filter the keystroke:
+ // 1. The IMContext instructed us to filter and we have no pending composition.
+ // 2. The IMContext did not instruct us to filter, but the keystroke caused a
+ // composition in progress to finish. It seems that sometimes SCIM will finish
+ // a composition and not mark the keystroke as filtered.
+ m_treatContextCommitAsKeyEvent = (!targetFrame->editor()->hasComposition())
+ && event->keyEvent()->gdkEventKey()->keyval;
+ clearPendingComposition();
+ if ((gtk_im_context_filter_keypress(priv->imContext, event->keyEvent()->gdkEventKey()) && !m_pendingComposition)
+ || (!m_treatContextCommitAsKeyEvent && !targetFrame->editor()->hasComposition()))
+ event->preventDefault();
+
+ m_treatContextCommitAsKeyEvent = false;
}
EditorClient::EditorClient(WebKitWebView* webView)
: m_isInRedo(false)
, m_webView(webView)
+ , m_treatContextCommitAsKeyEvent(false)
{
WebKitWebViewPrivate* priv = m_webView->priv;
g_signal_connect(priv->imContext, "commit", G_CALLBACK(imContextCommitted), this);
@@ -571,30 +634,30 @@ void EditorClient::textDidChangeInTextArea(Element*)
void EditorClient::ignoreWordInSpellDocument(const String& text)
{
- GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
+ GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
- for (; langs; langs = langs->next) {
- SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
+ for (; dicts; dicts = dicts->next) {
+ EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
- enchant_dict_add_to_session(lang->speller, text.utf8().data(), -1);
+ enchant_dict_add_to_session(dict, text.utf8().data(), -1);
}
}
void EditorClient::learnWord(const String& text)
{
- GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
+ GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
- for (; langs; langs = langs->next) {
- SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
+ for (; dicts; dicts = dicts->next) {
+ EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
- enchant_dict_add_to_personal(lang->speller, text.utf8().data(), -1);
+ enchant_dict_add_to_personal(dict, text.utf8().data(), -1);
}
}
void EditorClient::checkSpellingOfString(const UChar* text, int length, int* misspellingLocation, int* misspellingLength)
{
- GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
- if (!langs)
+ GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
+ if (!dicts)
return;
gchar* ctext = g_utf16_to_utf8(const_cast<gunichar2*>(text), length, 0, 0, 0);
@@ -623,8 +686,8 @@ void EditorClient::checkSpellingOfString(const UChar* text, int length, int* mis
// check characters twice.
i = end;
- for (; langs; langs = langs->next) {
- SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
+ for (; dicts; dicts = dicts->next) {
+ EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
gchar* cstart = g_utf8_offset_to_pointer(ctext, start);
gint bytes = static_cast<gint>(g_utf8_offset_to_pointer(ctext, end) - cstart);
gchar* word = g_new0(gchar, bytes+1);
@@ -632,7 +695,7 @@ void EditorClient::checkSpellingOfString(const UChar* text, int length, int* mis
g_utf8_strncpy(word, cstart, end - start);
- result = enchant_dict_check(lang->speller, word, -1);
+ result = enchant_dict_check(dict, word, -1);
g_free(word);
if (result) {
*misspellingLocation = start;
@@ -686,21 +749,21 @@ bool EditorClient::spellingUIIsShowing()
void EditorClient::getGuessesForWord(const String& word, WTF::Vector<String>& guesses)
{
- GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
+ GSList* dicts = webkit_web_settings_get_enchant_dicts(m_webView);
guesses.clear();
- for (; langs; langs = langs->next) {
+ for (; dicts; dicts = dicts->next) {
size_t numberOfSuggestions;
size_t i;
- SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
- gchar** suggestions = enchant_dict_suggest(lang->speller, word.utf8().data(), -1, &numberOfSuggestions);
+ EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
+ gchar** suggestions = enchant_dict_suggest(dict, word.utf8().data(), -1, &numberOfSuggestions);
for (i = 0; i < numberOfSuggestions && i < 10; i++)
guesses.append(String::fromUTF8(suggestions[i]));
if (numberOfSuggestions > 0)
- enchant_dict_free_suggestions(lang->speller, suggestions);
+ enchant_dict_free_suggestions(dict, suggestions);
}
}