diff options
author | hashimoto@chromium.org <hashimoto@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-22 00:48:14 +0000 |
---|---|---|
committer | hashimoto@chromium.org <hashimoto@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-22 00:48:14 +0000 |
commit | bc3a32109fd31f4bee7d9d55f1d68a454a26c57c (patch) | |
tree | 442fe94240075c86ee04b4c1ba9fa9820c681b00 /views | |
parent | 16a7011e429b0e447b57b38e5d5339d6e1ce9af6 (diff) | |
download | chromium_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/DEPS | 4 | ||||
-rw-r--r-- | views/ime/character_composer.cc | 293 | ||||
-rw-r--r-- | views/ime/character_composer.h | 44 | ||||
-rw-r--r-- | views/ime/character_composer_unittest.cc | 149 | ||||
-rw-r--r-- | views/ime/input_method_ibus.cc | 136 | ||||
-rw-r--r-- | views/ime/input_method_ibus.h | 19 | ||||
-rw-r--r-- | views/views.gyp | 14 |
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'], + ], + }], ], }, { |