summaryrefslogtreecommitdiffstats
path: root/views
diff options
context:
space:
mode:
authorhashimoto@chromium.org <hashimoto@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-22 00:48:14 +0000
committerhashimoto@chromium.org <hashimoto@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-22 00:48:14 +0000
commitbc3a32109fd31f4bee7d9d55f1d68a454a26c57c (patch)
tree442fe94240075c86ee04b4c1ba9fa9820c681b00 /views
parent16a7011e429b0e447b57b38e5d5339d6e1ce9af6 (diff)
downloadchromium_src-bc3a32109fd31f4bee7d9d55f1d68a454a26c57c.zip
chromium_src-bc3a32109fd31f4bee7d9d55f1d68a454a26c57c.tar.gz
chromium_src-bc3a32109fd31f4bee7d9d55f1d68a454a26c57c.tar.bz2
Add character composition
Implemented character composition from key strokes starting with dead keys or compose key BUG=chromium-os:13882, chromium-os:15925 TEST= Review URL: http://codereview.chromium.org/6979023 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@89948 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views')
-rw-r--r--views/ime/DEPS4
-rw-r--r--views/ime/character_composer.cc293
-rw-r--r--views/ime/character_composer.h44
-rw-r--r--views/ime/character_composer_unittest.cc149
-rw-r--r--views/ime/input_method_ibus.cc136
-rw-r--r--views/ime/input_method_ibus.h19
-rw-r--r--views/views.gyp14
7 files changed, 589 insertions, 70 deletions
diff --git a/views/ime/DEPS b/views/ime/DEPS
new file mode 100644
index 0000000..fceb080
--- /dev/null
+++ b/views/ime/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+third_party/gtk+/gdk/gdkkeysyms.h",
+ "+third_party/gtk+/gtk/gtkimcontextsimpleseqs.h",
+]
diff --git a/views/ime/character_composer.cc b/views/ime/character_composer.cc
new file mode 100644
index 0000000..88d83cb
--- /dev/null
+++ b/views/ime/character_composer.cc
@@ -0,0 +1,293 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "character_composer.h"
+
+#include <cstdlib>
+
+#include "third_party/gtk+/gdk/gdkkeysyms.h"
+
+namespace {
+
+inline int CompareSequenceValue(unsigned int key, uint16 value) {
+ return (key > value) ? 1 : ((key < value) ? -1 : 0);
+}
+
+class ComposeChecker {
+ public:
+ ComposeChecker(const uint16* data, int max_sequence_length, int n_sequences);
+ bool CheckSequence(const std::vector<unsigned int>& sequence,
+ uint32* composed_character) const;
+
+ private:
+ static int CompareSequence(const void* key_void, const void* value_void);
+
+ const uint16* data_;
+ int max_sequence_length_;
+ int n_sequences_;
+ int row_stride_;
+
+ DISALLOW_COPY_AND_ASSIGN(ComposeChecker);
+};
+
+ComposeChecker::ComposeChecker(const uint16* data,
+ int max_sequence_length,
+ int n_sequences)
+ : data_(data),
+ max_sequence_length_(max_sequence_length),
+ n_sequences_(n_sequences),
+ row_stride_(max_sequence_length + 2) {
+}
+
+bool ComposeChecker::CheckSequence(
+ const std::vector<unsigned int>& sequence,
+ uint32* composed_character) const {
+ const int sequence_length = sequence.size();
+ if (sequence_length > max_sequence_length_)
+ return false;
+ // Find sequence in the table
+ const uint16* found = static_cast<const uint16*>(
+ bsearch(&sequence, data_, n_sequences_,
+ sizeof(uint16)*row_stride_, CompareSequence));
+ if (!found)
+ return false;
+ // Ensure |found| is pointing the first matching element
+ while (found > data_ &&
+ CompareSequence(&sequence, found - row_stride_) == 0)
+ found -= row_stride_;
+
+ if (sequence_length == max_sequence_length_ || found[sequence_length] == 0) {
+ // |found| is not partially matching. It's fully matching
+ const uint16* data_end = data_ + row_stride_*n_sequences_;
+ if (found + row_stride_ >= data_end ||
+ CompareSequence(&sequence, found + row_stride_) != 0) {
+ // There is no composition longer than |found| which matches to |sequence|
+ const uint32 value = (found[max_sequence_length_] << 16) |
+ found[max_sequence_length_ + 1];
+ *composed_character = value;
+ }
+ }
+ return true;
+}
+
+// static
+int ComposeChecker::CompareSequence(const void* key_void,
+ const void* value_void) {
+ typedef std::vector<unsigned int> KeyType;
+ const KeyType& key = *static_cast<const KeyType*>(key_void);
+ const uint16* value = static_cast<const uint16*>(value_void);
+
+ for(size_t i = 0; i < key.size(); ++i) {
+ const int compare_result = CompareSequenceValue(key[i], value[i]);
+ if(compare_result)
+ return compare_result;
+ }
+ return 0;
+}
+
+
+class ComposeCheckerWithCompactTable {
+ public:
+ ComposeCheckerWithCompactTable(const uint16* data,
+ int max_sequence_length,
+ int index_size,
+ int index_stride);
+ bool CheckSequence(const std::vector<unsigned int>& sequence,
+ uint32* composed_character) const;
+
+ private:
+ static int CompareSequenceFront(const void* key_void, const void* value_void);
+ static int CompareSequenceSkipFront(const void* key_void,
+ const void* value_void);
+
+ const uint16* data_;
+ int max_sequence_length_;
+ int index_size_;
+ int index_stride_;
+};
+
+ComposeCheckerWithCompactTable::ComposeCheckerWithCompactTable(
+ const uint16* data,
+ int max_sequence_length,
+ int index_size,
+ int index_stride)
+ : data_(data),
+ max_sequence_length_(max_sequence_length),
+ index_size_(index_size),
+ index_stride_(index_stride) {
+}
+
+bool ComposeCheckerWithCompactTable::CheckSequence(
+ const std::vector<unsigned int>& sequence,
+ uint32* composed_character) const {
+ const int compose_length = sequence.size();
+ if (compose_length > max_sequence_length_)
+ return false;
+ // Find corresponding index for the first keypress
+ const uint16* index = static_cast<const uint16*>(
+ bsearch(&sequence, data_, index_size_,
+ sizeof(uint16)*index_stride_, CompareSequenceFront));
+ if (!index)
+ return false;
+ if (compose_length == 1)
+ return true;
+ // Check for composition sequences
+ for (int length = compose_length - 1; length < max_sequence_length_;
+ ++length) {
+ const uint16* table = data_ + index[length];
+ const uint16* table_next = data_ + index[length + 1];
+ if (table_next > table) {
+ // There are composition sequences for this |length|
+ const int row_stride = length + 1;
+ const int n_sequences = (table_next - table)/row_stride;
+ const uint16* seq = static_cast<const uint16*>(
+ bsearch(&sequence, table, n_sequences,
+ sizeof(uint16)*row_stride, CompareSequenceSkipFront));
+ if (seq) {
+ if (length == compose_length - 1) // exact match
+ *composed_character = seq[length];
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// static
+int ComposeCheckerWithCompactTable::CompareSequenceFront(
+ const void* key_void,
+ const void* value_void) {
+ typedef std::vector<unsigned int> KeyType;
+ const KeyType& key = *static_cast<const KeyType*>(key_void);
+ const uint16* value = static_cast<const uint16*>(value_void);
+
+ return CompareSequenceValue(key[0], value[0]);
+}
+
+// static
+int ComposeCheckerWithCompactTable::CompareSequenceSkipFront(
+ const void* key_void,
+ const void* value_void) {
+ typedef std::vector<unsigned int> KeyType;
+ const KeyType& key = *static_cast<const KeyType*>(key_void);
+ const uint16* value = static_cast<const uint16*>(value_void);
+
+ for(size_t i = 1; i < key.size(); ++i) {
+ const int compare_result = CompareSequenceValue(key[i], value[i - 1]);
+ if(compare_result)
+ return compare_result;
+ }
+ return 0;
+}
+
+// Main table
+typedef uint16 guint16;
+#include "third_party/gtk+/gtk/gtkimcontextsimpleseqs.h"
+
+// Additional table
+
+// The difference between this and the default input method is the handling
+// of C+acute - this method produces C WITH CEDILLA rather than C WITH ACUTE.
+// For languages that use CCedilla and not acute, this is the preferred mapping,
+// and is particularly important for pt_BR, where the us-intl keyboard is
+// used extensively.
+
+const uint16 cedilla_compose_seqs[] = {
+ // LATIN_CAPITAL_LETTER_C_WITH_CEDILLA
+ GDK_KEY_dead_acute, GDK_KEY_C, 0, 0, 0, 0x00C7,
+ // LATIN_SMALL_LETTER_C_WITH_CEDILLA
+ GDK_KEY_dead_acute, GDK_KEY_c, 0, 0, 0, 0x00E7,
+ // LATIN_CAPITAL_LETTER_C_WITH_CEDILLA
+ GDK_KEY_Multi_key, GDK_KEY_apostrophe, GDK_KEY_C, 0, 0, 0x00C7,
+ // LATIN_SMALL_LETTER_C_WITH_CEDILLA
+ GDK_KEY_Multi_key, GDK_KEY_apostrophe, GDK_KEY_c, 0, 0, 0x00E7,
+ // LATIN_CAPITAL_LETTER_C_WITH_CEDILLA
+ GDK_KEY_Multi_key, GDK_KEY_C, GDK_KEY_apostrophe, 0, 0, 0x00C7,
+ // LATIN_SMALL_LETTER_C_WITH_CEDILLA
+ GDK_KEY_Multi_key, GDK_KEY_c, GDK_KEY_apostrophe, 0, 0, 0x00E7,
+};
+
+bool KeypressShouldBeIgnored(unsigned int keycode) {
+ switch(keycode) {
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Caps_Lock:
+ case GDK_KEY_Shift_Lock:
+ case GDK_KEY_Meta_L:
+ case GDK_KEY_Meta_R:
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Super_L:
+ case GDK_KEY_Super_R:
+ case GDK_KEY_Hyper_L:
+ case GDK_KEY_Hyper_R:
+ case GDK_KEY_Mode_switch:
+ case GDK_KEY_ISO_Level3_Shift:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool CheckCharacterComposeTable(const std::vector<unsigned int>& sequence,
+ uint32* composed_character) {
+ // Check cedilla compose table
+ const ComposeChecker kCedillaComposeChecker(
+ cedilla_compose_seqs, 4, arraysize(cedilla_compose_seqs)/(4 + 2));
+ if (kCedillaComposeChecker.CheckSequence(sequence, composed_character))
+ return true;
+
+ // Check main compose table
+ const ComposeCheckerWithCompactTable kMainComposeChecker(
+ gtk_compose_seqs_compact, 5, 24, 6);
+ if (kMainComposeChecker.CheckSequence(sequence, composed_character))
+ return true;
+
+ return false;
+}
+
+} // anonymous namespace
+
+namespace views {
+
+CharacterComposer::CharacterComposer() {
+}
+
+void CharacterComposer::Reset() {
+ compose_buffer_.clear();
+ composed_character_.clear();
+}
+
+bool CharacterComposer::FilterKeyPress(unsigned int keycode) {
+ if(KeypressShouldBeIgnored(keycode))
+ return false;
+
+ compose_buffer_.push_back(keycode);
+
+ // Check compose table
+ composed_character_.clear();
+ uint32 composed_character_utf32 = 0;
+ if (CheckCharacterComposeTable(compose_buffer_, &composed_character_utf32)) {
+ // Key press is recognized as a part of composition
+ if (composed_character_utf32 !=0) {
+ // We get a composed character
+ compose_buffer_.clear();
+ // We assume that composed character is in BMP
+ if (composed_character_utf32 <= 0xffff)
+ composed_character_ += static_cast<char16>(composed_character_utf32);
+ }
+ return true;
+ }
+ // Key press is not a part of composition
+ compose_buffer_.pop_back(); // remove the keypress added this time
+ if (!compose_buffer_.empty()) {
+ compose_buffer_.clear();
+ return true;
+ }
+ return false;
+}
+
+} // namespace views
diff --git a/views/ime/character_composer.h b/views/ime/character_composer.h
new file mode 100644
index 0000000..f56ebc3
--- /dev/null
+++ b/views/ime/character_composer.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIEWS_IME_CHARACTER_COMPOSER_H_
+#define VIEWS_IME_CHARACTER_COMPOSER_H_
+#pragma once
+
+#include <vector>
+#include "base/basictypes.h"
+#include "base/string_util.h"
+
+namespace views {
+
+// A class to recognize compose and dead key sequence.
+// Outputs composed character.
+//
+// TODO(hashimoto): support unicode character composition starting with
+// Ctrl-Shift-U. http://crosbug.com/15925
+class CharacterComposer {
+ public:
+ CharacterComposer();
+
+ void Reset();
+
+ // Filters keypress. Returns true if the keypress is handled.
+ bool FilterKeyPress(unsigned int keycode);
+
+ // Returns a string consisting of composed character.
+ // Empty is returned when there is no composition result.
+ const string16& GetComposedCharacter() const {
+ return composed_character_;
+ }
+
+ private:
+ std::vector<unsigned int> compose_buffer_;
+ string16 composed_character_;
+
+ DISALLOW_COPY_AND_ASSIGN(CharacterComposer);
+};
+
+} // namespace views
+
+#endif // VIEWS_IME_CHARACTER_COMPOSER_H_
diff --git a/views/ime/character_composer_unittest.cc b/views/ime/character_composer_unittest.cc
new file mode 100644
index 0000000..05dc3c1
--- /dev/null
+++ b/views/ime/character_composer_unittest.cc
@@ -0,0 +1,149 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/ime/character_composer.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/gtk+/gdk/gdkkeysyms.h"
+
+namespace views {
+
+namespace {
+
+// Expects key is not filtered and no character is composed
+void ExpectKeyNotFiltered(CharacterComposer* character_composer, uint key) {
+ EXPECT_FALSE(character_composer->FilterKeyPress(key));
+ EXPECT_TRUE(character_composer->GetComposedCharacter().empty());
+}
+
+// Expects key is filtered and no character is composed
+void ExpectKeyFiltered(CharacterComposer* character_composer, uint key) {
+ EXPECT_TRUE(character_composer->FilterKeyPress(key));
+ EXPECT_TRUE(character_composer->GetComposedCharacter().empty());
+}
+
+// Expects |expected_character| is composed after sequence [key1, key2]
+void ExpectCharacterComposed(CharacterComposer* character_composer,
+ uint key1,
+ uint key2,
+ const string16& expected_character) {
+ ExpectKeyFiltered(character_composer, key1);
+ EXPECT_TRUE(character_composer->FilterKeyPress(key2));
+ EXPECT_EQ(character_composer->GetComposedCharacter(), expected_character);
+}
+
+// Expects |expected_character| is composed after sequence [key1, key2, key3]
+void ExpectCharacterComposed(CharacterComposer* character_composer,
+ uint key1,
+ uint key2,
+ uint key3,
+ const string16& expected_character) {
+ ExpectKeyFiltered(character_composer, key1);
+ ExpectCharacterComposed(character_composer, key2, key3, expected_character);
+}
+
+// Expects |expected_character| is composed after sequence [key1, key2, key3,
+// key 4]
+void ExpectCharacterComposed(CharacterComposer* character_composer,
+ uint key1,
+ uint key2,
+ uint key3,
+ uint key4,
+ const string16& expected_character) {
+ ExpectKeyFiltered(character_composer, key1);
+ ExpectCharacterComposed(character_composer, key2, key3, key4,
+ expected_character);
+}
+
+} // anonymous namespace
+
+TEST(CharacterComposerTest, InitialState) {
+ CharacterComposer character_composer;
+ EXPECT_TRUE(character_composer.GetComposedCharacter().empty());
+}
+
+TEST(CharacterComposerTest, NormalKeyIsNotFiltered) {
+ CharacterComposer character_composer;
+ ExpectKeyNotFiltered(&character_composer, GDK_KEY_B);
+ ExpectKeyNotFiltered(&character_composer, GDK_KEY_Z);
+ ExpectKeyNotFiltered(&character_composer, GDK_KEY_c);
+ ExpectKeyNotFiltered(&character_composer, GDK_KEY_m);
+ ExpectKeyNotFiltered(&character_composer, GDK_KEY_0);
+ ExpectKeyNotFiltered(&character_composer, GDK_KEY_1);
+ ExpectKeyNotFiltered(&character_composer, GDK_KEY_8);
+}
+
+TEST(CharacterComposerTest, PartiallyMatchingSequence) {
+ CharacterComposer character_composer;
+
+ // Composition with sequence ['dead acute', '1'] will fail
+ ExpectKeyFiltered(&character_composer, GDK_KEY_dead_acute);
+ EXPECT_TRUE(character_composer.FilterKeyPress(GDK_KEY_1));
+ EXPECT_TRUE(character_composer.GetComposedCharacter().empty());
+
+ // Composition with sequence ['dead acute', 'dead circumflex', '1'] will fail
+ ExpectKeyFiltered(&character_composer, GDK_KEY_dead_acute);
+ ExpectKeyFiltered(&character_composer, GDK_KEY_dead_circumflex);
+ EXPECT_TRUE(character_composer.FilterKeyPress(GDK_KEY_1));
+ EXPECT_TRUE(character_composer.GetComposedCharacter().empty());
+}
+
+TEST(CharacterComposerTest, FullyMatchingSequences) {
+ CharacterComposer character_composer;
+ // LATIN SMALL LETTER A WITH ACUTE
+ ExpectCharacterComposed(&character_composer, GDK_KEY_dead_acute, GDK_KEY_a,
+ string16(1, 0x00E1));
+ // LATIN CAPITAL LETTER A WITH ACUTE
+ ExpectCharacterComposed(&character_composer, GDK_KEY_dead_acute, GDK_KEY_A,
+ string16(1, 0x00C1));
+ // GRAVE ACCENT
+ ExpectCharacterComposed(&character_composer, GDK_KEY_dead_grave,
+ GDK_KEY_dead_grave, string16(1, 0x0060));
+ // LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE
+ ExpectCharacterComposed(&character_composer, GDK_KEY_dead_acute,
+ GDK_KEY_dead_circumflex, GDK_KEY_a,
+ string16(1, 0x1EA5));
+ // LATIN CAPITAL LETTER U WITH HORN AND GRAVE
+ ExpectCharacterComposed(&character_composer, GDK_KEY_dead_grave,
+ GDK_KEY_dead_horn, GDK_KEY_U, string16(1, 0x1EEA));
+ // LATIN CAPITAL LETTER C WITH CEDILLA
+ ExpectCharacterComposed(&character_composer, GDK_KEY_dead_acute, GDK_KEY_C,
+ string16(1, 0x00C7));
+ // LATIN SMALL LETTER C WITH CEDILLA
+ ExpectCharacterComposed(&character_composer, GDK_KEY_dead_acute, GDK_KEY_c,
+ string16(1, 0x00E7));
+}
+
+TEST(CharacterComposerTest, FullyMatchingSequencesAfterMatchingFailure) {
+ CharacterComposer character_composer;
+ // Composition with sequence ['dead acute', 'dead circumflex', '1'] will fail
+ ExpectKeyFiltered(&character_composer, GDK_KEY_dead_acute);
+ ExpectKeyFiltered(&character_composer, GDK_KEY_dead_circumflex);
+ EXPECT_TRUE(character_composer.FilterKeyPress(GDK_KEY_1));
+ EXPECT_TRUE(character_composer.GetComposedCharacter().empty());
+ // LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE
+ ExpectCharacterComposed(&character_composer, GDK_KEY_dead_acute,
+ GDK_KEY_dead_circumflex, GDK_KEY_a,
+ string16(1, 0x1EA5));
+}
+
+TEST(CharacterComposerTest, ComposedCharacterIsClearedAfterReset) {
+ CharacterComposer character_composer;
+ ExpectCharacterComposed(&character_composer, GDK_KEY_dead_acute, GDK_KEY_a,
+ string16(1, 0x00E1));
+ character_composer.Reset();
+ EXPECT_TRUE(character_composer.GetComposedCharacter().empty());
+}
+
+TEST(CharacterComposerTest, CompositionStateIsClearedAfterReset) {
+ CharacterComposer character_composer;
+ // Even though sequence ['dead acute', 'a'] will compose 'a with acute',
+ // no character is composed here because of reset.
+ ExpectKeyFiltered(&character_composer, GDK_KEY_dead_acute);
+ character_composer.Reset();
+ EXPECT_FALSE(character_composer.FilterKeyPress(GDK_KEY_a));
+ EXPECT_TRUE(character_composer.GetComposedCharacter().empty());
+}
+
+} // namespace views
diff --git a/views/ime/input_method_ibus.cc b/views/ime/input_method_ibus.cc
index 958e3df..4543cef 100644
--- a/views/ime/input_method_ibus.cc
+++ b/views/ime/input_method_ibus.cc
@@ -57,6 +57,41 @@ guint32 IBusStateFromViewsFlags(int flags) {
(flags & ui::EF_RIGHT_BUTTON_DOWN ? IBUS_BUTTON3_MASK : 0);
}
+void IBusKeyEventFromViewsKeyEvent(const views::KeyEvent& key,
+ guint32* ibus_keyval,
+ guint32* ibus_keycode,
+ guint32* ibus_state) {
+#if defined(TOUCH_UI)
+ if (key.native_event_2()) {
+ XKeyEvent* x_key = reinterpret_cast<XKeyEvent*>(key.native_event_2());
+ // Yes, ibus uses X11 keysym. We cannot use XLookupKeysym(), which doesn't
+ // translate Shift and CapsLock states.
+ KeySym keysym = NoSymbol;
+ ::XLookupString(x_key, NULL, 0, &keysym, NULL);
+ *ibus_keyval = keysym;
+ *ibus_keycode = x_key->keycode;
+ }
+#elif defined(TOOLKIT_USES_GTK)
+ if (key.native_event()) {
+ GdkEventKey* gdk_key = reinterpret_cast<GdkEventKey*>(key.native_event());
+ *ibus_keyval = gdk_key->keyval;
+ *ibus_keycode = gdk_key->hardware_keycode;
+ }
+#endif
+ else {
+ // GdkKeyCodeForWindowsKeyCode() is actually nothing to do with Gtk, we
+ // probably want to rename it to something like XKeySymForWindowsKeyCode(),
+ // because Gtk keyval is actually same as X KeySym.
+ *ibus_keyval = ui::GdkKeyCodeForWindowsKeyCode(
+ key.key_code(), key.IsShiftDown() ^ key.IsCapsLockDown());
+ *ibus_keycode = 0;
+ }
+
+ *ibus_state = IBusStateFromViewsFlags(key.flags());
+ if (key.type() == ui::ET_KEY_RELEASED)
+ *ibus_state |= IBUS_RELEASE_MASK;
+}
+
void ExtractCompositionTextFromIBusPreedit(IBusText* text,
guint cursor_position,
ui::CompositionText* composition) {
@@ -140,7 +175,8 @@ namespace views {
// InputMethodIBus::PendingKeyEvent implementation ----------------------------
class InputMethodIBus::PendingKeyEvent {
public:
- PendingKeyEvent(InputMethodIBus* input_method, const KeyEvent& key);
+ PendingKeyEvent(InputMethodIBus* input_method, const KeyEvent& key,
+ guint32 ibus_keyval);
~PendingKeyEvent();
// Abandon this pending key event. Its result will just be discarded.
@@ -161,6 +197,8 @@ class InputMethodIBus::PendingKeyEvent {
int flags_;
ui::KeyboardCode key_code_;
+ guint32 ibus_keyval_;
+
#if defined(TOUCH_UI)
// corresponding XEvent data of a views::KeyEvent. It's a plain struct so we
// can do bitwise copy.
@@ -171,12 +209,15 @@ class InputMethodIBus::PendingKeyEvent {
};
InputMethodIBus::PendingKeyEvent::PendingKeyEvent(InputMethodIBus* input_method,
- const KeyEvent& key)
+ const KeyEvent& key,
+ guint32 ibus_keyval)
: input_method_(input_method),
type_(key.type()),
flags_(key.flags()),
- key_code_(key.key_code()) {
+ key_code_(key.key_code()),
+ ibus_keyval_(ibus_keyval) {
DCHECK(input_method_);
+
#if defined(TOUCH_UI)
if (key.native_event_2())
x_event_ = *reinterpret_cast<XKeyEvent*>(key.native_event_2());
@@ -198,12 +239,12 @@ void InputMethodIBus::PendingKeyEvent::ProcessPostIME(bool handled) {
if (x_event_.type == KeyPress || x_event_.type == KeyRelease) {
Event::FromNativeEvent2 from_native;
KeyEvent key(reinterpret_cast<XEvent*>(&x_event_), from_native);
- input_method_->ProcessKeyEventPostIME(key, handled);
+ input_method_->ProcessKeyEventPostIME(key, ibus_keyval_, handled);
return;
}
#endif
KeyEvent key(type_, key_code_, flags_);
- input_method_->ProcessKeyEventPostIME(key, handled);
+ input_method_->ProcessKeyEventPostIME(key, ibus_keyval_, handled);
}
// InputMethodIBus::PendingCreateICRequest implementation ---------------------
@@ -319,6 +360,11 @@ void InputMethodIBus::DispatchKeyEvent(const KeyEvent& key) {
DCHECK(key.type() == ui::ET_KEY_PRESSED || key.type() == ui::ET_KEY_RELEASED);
DCHECK(widget_focused());
+ guint32 ibus_keyval = 0;
+ guint32 ibus_keycode = 0;
+ guint32 ibus_state = 0;
+ IBusKeyEventFromViewsKeyEvent(key, &ibus_keyval, &ibus_keycode, &ibus_state);
+
// If |context_| is not usable and |fake_context_| is not created yet, then we
// can only dispatch the key event as is. We also dispatch the key event
// directly if the current text input type is ui::TEXT_INPUT_TYPE_PASSWORD,
@@ -328,17 +374,14 @@ void InputMethodIBus::DispatchKeyEvent(const KeyEvent& key) {
if (!(context_focused_ || fake_context_) ||
GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD) {
if (key.type() == ui::ET_KEY_PRESSED)
- ProcessUnfilteredKeyPressEvent(key);
+ ProcessUnfilteredKeyPressEvent(key, ibus_keyval);
else
DispatchKeyEventPostIME(key);
return;
}
- guint32 ibus_keyval = 0;
- guint32 ibus_keycode = 0;
- guint32 ibus_state = 0;
- PendingKeyEvent* pending_key =
- NewPendingKeyEvent(key, &ibus_keyval, &ibus_keycode, &ibus_state);
+ PendingKeyEvent* pending_key = new PendingKeyEvent(this, key, ibus_keyval);
+ pending_key_events_.insert(pending_key);
// Note:
// 1. We currently set timeout to -1, because ibus doesn't have a mechanism to
@@ -579,6 +622,8 @@ void InputMethodIBus::ResetContext() {
ibus_input_context_reset(context_);
// We don't need to reset |fake_context_|.
+
+ character_composer_.Reset();
}
void InputMethodIBus::UpdateContextFocusState() {
@@ -625,6 +670,7 @@ void InputMethodIBus::UpdateFakeContextFocusState() {
}
void InputMethodIBus::ProcessKeyEventPostIME(const KeyEvent& key,
+ guint32 ibus_keyval,
bool handled) {
// If we get here without a focused text input client, then it means the key
// event is sent to the global ibus input context.
@@ -655,7 +701,7 @@ void InputMethodIBus::ProcessKeyEventPostIME(const KeyEvent& key,
return;
if (key.type() == ui::ET_KEY_PRESSED && !handled)
- ProcessUnfilteredKeyPressEvent(key);
+ ProcessUnfilteredKeyPressEvent(key, ibus_keyval);
else if (key.type() == ui::ET_KEY_RELEASED)
DispatchKeyEventPostIME(key);
}
@@ -669,7 +715,8 @@ void InputMethodIBus::ProcessFilteredKeyPressEvent(const KeyEvent& key) {
}
}
-void InputMethodIBus::ProcessUnfilteredKeyPressEvent(const KeyEvent& key) {
+void InputMethodIBus::ProcessUnfilteredKeyPressEvent(const KeyEvent& key,
+ guint32 ibus_keyval) {
const View* old_focused_view = focused_view();
DispatchKeyEventPostIME(key);
@@ -678,13 +725,22 @@ void InputMethodIBus::ProcessUnfilteredKeyPressEvent(const KeyEvent& key) {
if (old_focused_view != focused_view())
return;
- // If a key event was not filtered by |context_|, then it means the key
- // event didn't generate any result text. So we need to send corresponding
- // character to the focused text input client.
- // TODO(suzhe): support compose and dead keys. Gtk supports it in
- // GtkIMContextSimple class. We need similar thing after getting rid of Gtk.
- char16 ch = key.GetCharacter();
+ // Process compose and dead keys
+ if (character_composer_.FilterKeyPress(ibus_keyval)) {
+ string16 composed = character_composer_.GetComposedCharacter();
+ if (!composed.empty()) {
+ TextInputClient* client = GetTextInputClient();
+ if (client)
+ client->InsertText(composed);
+ }
+ return;
+ }
+ // If a key event was not filtered by |context_| and |character_composer_|,
+ // then it means the key event didn't generate any result text. So we need
+ // to send corresponding character to the focused text input client.
+
TextInputClient* client = GetTextInputClient();
+ char16 ch = key.GetCharacter();
if (ch && client)
client->InsertChar(ch, key.flags());
}
@@ -737,46 +793,6 @@ void InputMethodIBus::SendFakeProcessKeyEvent(bool pressed) const {
DispatchKeyEventPostIME(key);
}
-InputMethodIBus::PendingKeyEvent* InputMethodIBus::NewPendingKeyEvent(
- const KeyEvent& key,
- guint32* ibus_keyval,
- guint32* ibus_keycode,
- guint32* ibus_state) {
-#if defined(TOUCH_UI)
- if (key.native_event_2()) {
- XKeyEvent* x_key = reinterpret_cast<XKeyEvent*>(key.native_event_2());
- // Yes, ibus uses X11 keysym. We cannot use XLookupKeysym(), which doesn't
- // translate Shift and CapsLock states.
- KeySym keysym = NoSymbol;
- ::XLookupString(x_key, NULL, 0, &keysym, NULL);
- *ibus_keyval = keysym;
- *ibus_keycode = x_key->keycode;
- }
-#elif defined(TOOLKIT_USES_GTK)
- if (key.native_event()) {
- GdkEventKey* gdk_key = reinterpret_cast<GdkEventKey*>(key.native_event());
- *ibus_keyval = gdk_key->keyval;
- *ibus_keycode = gdk_key->hardware_keycode;
- }
-#endif
- else {
- // GdkKeyCodeForWindowsKeyCode() is actually nothing to do with Gtk, we
- // probably want to rename it to something like XKeySymForWindowsKeyCode(),
- // because Gtk keyval is actually same as X KeySym.
- *ibus_keyval = ui::GdkKeyCodeForWindowsKeyCode(
- key.key_code(), key.IsShiftDown() ^ key.IsCapsLockDown());
- *ibus_keycode = 0;
- }
-
- *ibus_state = IBusStateFromViewsFlags(key.flags());
- if (key.type() == ui::ET_KEY_RELEASED)
- *ibus_state |= IBUS_RELEASE_MASK;
-
- PendingKeyEvent* pending_key = new PendingKeyEvent(this, key);
- pending_key_events_.insert(pending_key);
- return pending_key;
-}
-
void InputMethodIBus::FinishPendingKeyEvent(PendingKeyEvent* pending_key) {
DCHECK(pending_key_events_.count(pending_key));
@@ -846,7 +862,7 @@ void InputMethodIBus::OnForwardKeyEvent(IBusInputContext* context,
// calling ProcessKeyEventPostIME(), which will clear pending input method
// results.
if (key.type() == ui::ET_KEY_PRESSED)
- ProcessUnfilteredKeyPressEvent(key);
+ ProcessUnfilteredKeyPressEvent(key, keyval);
else
DispatchKeyEventPostIME(key);
}
diff --git a/views/ime/input_method_ibus.h b/views/ime/input_method_ibus.h
index bda8ab5..0d181d1 100644
--- a/views/ime/input_method_ibus.h
+++ b/views/ime/input_method_ibus.h
@@ -13,6 +13,7 @@
#include "base/scoped_ptr.h"
#include "ui/base/gtk/gtk_signal.h"
#include "views/events/event.h"
+#include "views/ime/character_composer.h"
#include "views/ime/input_method_base.h"
#include "views/view.h"
@@ -85,14 +86,16 @@ class InputMethodIBus : public InputMethodBase {
void UpdateFakeContextFocusState();
// Process a key returned from the input method.
- void ProcessKeyEventPostIME(const KeyEvent& key, bool handled);
+ void ProcessKeyEventPostIME(const KeyEvent& key, guint32 ibus_keycode,
+ bool handled);
// Processes a key event that was already filtered by the input method.
// A VKEY_PROCESSKEY may be dispatched to the focused View.
void ProcessFilteredKeyPressEvent(const KeyEvent& key);
// Processes a key event that was not filtered by the input method.
- void ProcessUnfilteredKeyPressEvent(const KeyEvent& key);
+ void ProcessUnfilteredKeyPressEvent(const KeyEvent& key,
+ guint32 ibus_keycode);
// Sends input method result caused by the given key event to the focused text
// input client.
@@ -109,14 +112,6 @@ class InputMethodIBus : public InputMethodBase {
// the focused View.
void SendFakeProcessKeyEvent(bool pressed) const;
- // Creates a new PendingKeyEvent object and add it to |pending_key_events_|.
- // Corresponding ibus key event information will be stored in |*ibus_keyval|,
- // |*ibus_keycode| and |*ibus_state|.
- PendingKeyEvent* NewPendingKeyEvent(const KeyEvent& key,
- guint32* ibus_keyval,
- guint32* ibus_keycode,
- guint32* ibus_state);
-
// Called when a pending key event has finished. The event will be removed
// from |pending_key_events_|.
void FinishPendingKeyEvent(PendingKeyEvent* pending_key);
@@ -199,6 +194,10 @@ class InputMethodIBus : public InputMethodBase {
// event will be discarded.
bool suppress_next_result_;
+ // An object to compose a character from a sequence of key presses
+ // including dead key etc.
+ CharacterComposer character_composer_;
+
DISALLOW_COPY_AND_ASSIGN(InputMethodIBus);
};
diff --git a/views/views.gyp b/views/views.gyp
index 1f84df7..ef59248 100644
--- a/views/views.gyp
+++ b/views/views.gyp
@@ -287,6 +287,8 @@
'focus/focus_util_win.h',
'focus/view_storage.cc',
'focus/view_storage.h',
+ 'ime/character_composer.cc',
+ 'ime/character_composer.h',
'ime/input_method.h',
'ime/input_method_delegate.h',
'ime/input_method_base.cc',
@@ -437,6 +439,12 @@
}],
],
}],
+ ['chromeos!=1', {
+ 'sources/': [
+ ['exclude', 'ime/character_composer.cc'],
+ ['exclude', 'ime/character_composer.h'],
+ ],
+ }],
['use_ibus==1', {
'dependencies': [
'../build/linux/system.gyp:ibus',
@@ -488,6 +496,7 @@
'focus/focus_manager_unittest.cc',
'ime/mock_input_method.cc',
'ime/mock_input_method.h',
+ 'ime/character_composer_unittest.cc',
'layout/grid_layout_unittest.cc',
'layout/box_layout_unittest.cc',
'test/views_test_base.cc',
@@ -536,6 +545,11 @@
'<(DEPTH)/third_party/wtl/include',
],
}],
+ ['chromeos!=1', {
+ 'sources/': [
+ ['exclude', 'ime/character_composer_unittest.cc'],
+ ],
+ }],
],
},
{