1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/base/ime/chromeos/character_composer.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/third_party/icu/icu_utf.h"
13 #include "ui/events/event.h"
14 #include "ui/events/keycodes/dom/dom_key.h"
15 #include "ui/events/keycodes/dom/keycode_converter.h"
16 #include "ui/events/keycodes/keyboard_codes.h"
20 #include "ui/base/ime/chromeos/character_composer_data.h"
22 bool CheckCharacterComposeTable(
23 const ui::CharacterComposer::ComposeBuffer
& compose_sequence
,
24 uint32
* composed_character
) {
25 const ui::TreeComposeChecker
kTreeComposeChecker(kCompositions
);
26 return kTreeComposeChecker
.CheckSequence(compose_sequence
,
27 composed_character
) !=
28 ui::ComposeChecker::CheckSequenceResult::NO_MATCH
;
31 // Converts |character| to UTF16 string.
32 // Returns false when |character| is not a valid character.
33 bool UTF32CharacterToUTF16(uint32 character
, base::string16
* output
) {
35 // Reject invalid character. (e.g. codepoint greater than 0x10ffff)
36 if (!CBU_IS_UNICODE_CHAR(character
))
39 output
->resize(CBU16_LENGTH(character
));
41 CBU16_APPEND_UNSAFE(&(*output
)[0], i
, character
);
46 // Returns an hexadecimal digit integer (0 to 15) corresponding to |keycode|.
47 // -1 is returned when |keycode| cannot be a hexadecimal digit.
48 int KeycodeToHexDigit(unsigned int keycode
) {
49 if (ui::VKEY_0
<= keycode
&& keycode
<= ui::VKEY_9
)
50 return keycode
- ui::VKEY_0
;
51 if (ui::VKEY_A
<= keycode
&& keycode
<= ui::VKEY_F
)
52 return keycode
- ui::VKEY_A
+ 10;
53 return -1; // |keycode| cannot be a hexadecimal digit.
60 CharacterComposer::CharacterComposer() : composition_mode_(KEY_SEQUENCE_MODE
) {
63 CharacterComposer::~CharacterComposer() {
66 void CharacterComposer::Reset() {
67 compose_buffer_
.clear();
69 composed_character_
.clear();
70 preedit_string_
.clear();
71 composition_mode_
= KEY_SEQUENCE_MODE
;
74 bool CharacterComposer::FilterKeyPress(const ui::KeyEvent
& event
) {
75 if (event
.type() != ET_KEY_PRESSED
&& event
.type() != ET_KEY_RELEASED
)
78 // We don't care about modifier key presses.
79 if (KeycodeConverter::IsDomKeyForModifier(event
.GetDomKey()))
82 composed_character_
.clear();
83 preedit_string_
.clear();
85 // When the user presses Ctrl+Shift+U, maybe switch to HEX_MODE.
86 // We don't care about other modifiers like Alt. When CapsLock is down, we
87 // do nothing because what we receive is Ctrl+Shift+u (not U).
88 if (event
.key_code() == VKEY_U
&&
89 (event
.flags() & (EF_SHIFT_DOWN
| EF_CONTROL_DOWN
| EF_CAPS_LOCK_DOWN
)) ==
90 (EF_SHIFT_DOWN
| EF_CONTROL_DOWN
)) {
91 if (composition_mode_
== KEY_SEQUENCE_MODE
&& compose_buffer_
.empty()) {
92 // There is no ongoing composition. Let's switch to HEX_MODE.
93 composition_mode_
= HEX_MODE
;
94 UpdatePreeditStringHexMode();
99 // Filter key press in an appropriate manner.
100 switch (composition_mode_
) {
101 case KEY_SEQUENCE_MODE
:
102 return FilterKeyPressSequenceMode(event
);
104 return FilterKeyPressHexMode(event
);
111 bool CharacterComposer::FilterKeyPressSequenceMode(const KeyEvent
& event
) {
112 DCHECK(composition_mode_
== KEY_SEQUENCE_MODE
);
113 compose_buffer_
.push_back(event
.GetDomKey());
115 // Check compose table.
116 uint32 composed_character_utf32
= 0;
117 if (CheckCharacterComposeTable(compose_buffer_
, &composed_character_utf32
)) {
118 // Key press is recognized as a part of composition.
119 if (composed_character_utf32
!= 0) {
120 // We get a composed character.
121 compose_buffer_
.clear();
122 UTF32CharacterToUTF16(composed_character_utf32
, &composed_character_
);
126 // Key press is not a part of composition.
127 compose_buffer_
.pop_back(); // Remove the keypress added this time.
128 if (!compose_buffer_
.empty()) {
129 compose_buffer_
.clear();
135 bool CharacterComposer::FilterKeyPressHexMode(const KeyEvent
& event
) {
136 DCHECK(composition_mode_
== HEX_MODE
);
137 const size_t kMaxHexSequenceLength
= 8;
138 base::char16 c
= event
.GetCharacter();
140 if (base::IsHexDigit(c
)) {
141 hex_digit
= base::HexDigitToInt(c
);
143 // With 101 keyboard, control + shift + 3 produces '#', but a user may
144 // have intended to type '3'. So, if a hexadecimal character was not found,
145 // suppose a user is holding shift key (and possibly control key, too) and
146 // try a character with modifier keys removed.
147 hex_digit
= KeycodeToHexDigit(event
.key_code());
149 if (hex_digit
>= 0) {
150 if (hex_buffer_
.size() < kMaxHexSequenceLength
) {
151 // Add the key to the buffer if it is a hex digit.
152 hex_buffer_
.push_back(hex_digit
);
155 DomKey key
= event
.GetDomKey();
156 if (key
== DomKey::ESCAPE
) {
157 // Cancel composition when ESC is pressed.
159 } else if (key
== DomKey::ENTER
|| c
== ' ') {
160 // Commit the composed character when Enter or space is pressed.
162 } else if (key
== DomKey::BACKSPACE
) {
163 // Pop back the buffer when Backspace is pressed.
164 if (!hex_buffer_
.empty()) {
165 hex_buffer_
.pop_back();
167 // If there is no character in |hex_buffer_|, cancel composition.
171 // Other keystrokes are ignored in hex composition mode.
173 UpdatePreeditStringHexMode();
177 void CharacterComposer::CommitHex() {
178 DCHECK(composition_mode_
== HEX_MODE
);
179 uint32 composed_character_utf32
= 0;
180 for (size_t i
= 0; i
!= hex_buffer_
.size(); ++i
) {
181 const uint32 digit
= hex_buffer_
[i
];
182 DCHECK(0 <= digit
&& digit
< 16);
183 composed_character_utf32
<<= 4;
184 composed_character_utf32
|= digit
;
187 UTF32CharacterToUTF16(composed_character_utf32
, &composed_character_
);
190 void CharacterComposer::UpdatePreeditStringHexMode() {
191 if (composition_mode_
!= HEX_MODE
) {
192 preedit_string_
.clear();
195 std::string
preedit_string_ascii("u");
196 for (size_t i
= 0; i
!= hex_buffer_
.size(); ++i
) {
197 const int digit
= hex_buffer_
[i
];
198 DCHECK(0 <= digit
&& digit
< 16);
199 preedit_string_ascii
+= digit
<= 9 ? ('0' + digit
) : ('a' + (digit
- 10));
201 preedit_string_
= base::ASCIIToUTF16(preedit_string_ascii
);
204 ComposeChecker::CheckSequenceResult
TreeComposeChecker::CheckSequence(
205 const ui::CharacterComposer::ComposeBuffer
& sequence
,
206 uint32_t* composed_character
) const {
207 *composed_character
= 0;
208 if (sequence
.size() > data_
.maximum_sequence_length
)
209 return CheckSequenceResult::NO_MATCH
;
211 uint16_t tree_index
= 0;
212 for (const auto& keystroke
: sequence
) {
213 DCHECK(tree_index
< data_
.tree_entries
);
215 // If we are looking up a dead key, skip over the character tables.
216 int32_t character
= -1;
217 if (keystroke
.IsDeadKey()) {
218 tree_index
+= 2 * data_
.tree
[tree_index
] + 1; // internal unicode table
219 tree_index
+= 2 * data_
.tree
[tree_index
] + 1; // leaf unicode table
220 character
= keystroke
.ToDeadKeyCombiningCharacter();
221 } else if (keystroke
.IsCharacter()) {
222 character
= keystroke
.ToCharacter();
224 if (character
< 0 || character
> 0xFFFF)
225 return CheckSequenceResult::NO_MATCH
;
227 // Check the internal subtree table.
229 uint16_t entries
= data_
.tree
[tree_index
++];
231 Find(tree_index
, entries
, static_cast<uint16_t>(character
), &result
)) {
236 // Skip over the internal subtree table and check the leaf table.
237 tree_index
+= 2 * entries
;
238 entries
= data_
.tree
[tree_index
++];
240 Find(tree_index
, entries
, static_cast<uint16_t>(character
), &result
)) {
241 *composed_character
= result
;
242 return CheckSequenceResult::FULL_MATCH
;
244 return CheckSequenceResult::NO_MATCH
;
246 return CheckSequenceResult::PREFIX_MATCH
;
249 bool TreeComposeChecker::Find(uint16_t index
,
252 uint16_t* value
) const {
256 bool operator<(const TableEntry
& other
) const {
257 return this->key
< other
.key
;
260 const TableEntry
* a
= reinterpret_cast<const TableEntry
*>(&data_
.tree
[index
]);
261 const TableEntry
* z
= a
+ size
;
262 const TableEntry target
= {key
, 0};
263 const TableEntry
* it
= std::lower_bound(a
, z
, target
);
264 if ((it
!= z
) && (it
->key
== key
)) {