Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / ui / base / ime / input_method_chromeos.cc
1 // Copyright 2014 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.
4
5 #include "ui/base/ime/input_method_chromeos.h"
6
7 #include <algorithm>
8 #include <cstring>
9 #include <set>
10 #include <vector>
11
12 #include "base/basictypes.h"
13 #include "base/bind.h"
14 #include "base/i18n/char_iterator.h"
15 #include "base/logging.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/third_party/icu/icu_utf.h"
19 #include "chromeos/ime/composition_text.h"
20 #include "ui/base/ime/text_input_client.h"
21 #include "ui/events/event.h"
22 #include "ui/events/event_constants.h"
23 #include "ui/events/event_utils.h"
24 #include "ui/events/keycodes/keyboard_code_conversion.h"
25 #include "ui/events/keycodes/keyboard_code_conversion_x.h"
26 #include "ui/events/keycodes/keyboard_codes.h"
27 #include "ui/gfx/rect.h"
28
29 namespace {
30 chromeos::IMEEngineHandlerInterface* GetEngine() {
31   return chromeos::IMEBridge::Get()->GetCurrentEngineHandler();
32 }
33 }  // namespace
34
35 namespace ui {
36
37 // InputMethodChromeOS implementation -----------------------------------------
38 InputMethodChromeOS::InputMethodChromeOS(
39     internal::InputMethodDelegate* delegate)
40     : composing_text_(false),
41       composition_changed_(false),
42       current_keyevent_id_(0),
43       weak_ptr_factory_(this) {
44   SetDelegate(delegate);
45   chromeos::IMEBridge::Get()->SetInputContextHandler(this);
46
47   UpdateContextFocusState();
48 }
49
50 InputMethodChromeOS::~InputMethodChromeOS() {
51   AbandonAllPendingKeyEvents();
52   ConfirmCompositionText();
53   // We are dead, so we need to ask the client to stop relying on us.
54   OnInputMethodChanged();
55
56   chromeos::IMEBridge::Get()->SetInputContextHandler(NULL);
57 }
58
59 void InputMethodChromeOS::OnFocus() {
60   InputMethodBase::OnFocus();
61   OnTextInputTypeChanged(GetTextInputClient());
62 }
63
64 void InputMethodChromeOS::OnBlur() {
65   ConfirmCompositionText();
66   InputMethodBase::OnBlur();
67   OnTextInputTypeChanged(GetTextInputClient());
68 }
69
70 bool InputMethodChromeOS::OnUntranslatedIMEMessage(
71     const base::NativeEvent& event,
72     NativeEventResult* result) {
73   return false;
74 }
75
76 void InputMethodChromeOS::ProcessKeyEventDone(uint32 id,
77                                               ui::KeyEvent* event,
78                                               bool is_handled) {
79   if (pending_key_events_.find(id) == pending_key_events_.end())
80    return;  // Abandoned key event.
81
82   DCHECK(event);
83   if (event->type() == ET_KEY_PRESSED) {
84     if (is_handled) {
85       // IME event has a priority to be handled, so that character composer
86       // should be reset.
87       character_composer_.Reset();
88     } else {
89       // If IME does not handle key event, passes keyevent to character composer
90       // to be able to compose complex characters.
91       is_handled = ExecuteCharacterComposer(*event);
92     }
93   }
94
95   if (event->type() == ET_KEY_PRESSED || event->type() == ET_KEY_RELEASED)
96     ProcessKeyEventPostIME(*event, is_handled);
97
98   // ProcessKeyEventPostIME may change the |pending_key_events_|.
99   pending_key_events_.erase(id);
100 }
101
102 bool InputMethodChromeOS::DispatchKeyEvent(const ui::KeyEvent& event) {
103   DCHECK(event.type() == ET_KEY_PRESSED || event.type() == ET_KEY_RELEASED);
104   DCHECK(system_toplevel_window_focused());
105
106   // If |context_| is not usable, then we can only dispatch the key event as is.
107   // We only dispatch the key event to input method when the |context_| is an
108   // normal input field (not a password field).
109   // Note: We need to send the key event to ibus even if the |context_| is not
110   // enabled, so that ibus can have a chance to enable the |context_|.
111   if (!IsInputFieldFocused() || !GetEngine()) {
112     if (event.type() == ET_KEY_PRESSED) {
113       if (ExecuteCharacterComposer(event)) {
114         // Treating as PostIME event if character composer handles key event and
115         // generates some IME event,
116         ProcessKeyEventPostIME(event, true);
117         return true;
118       }
119       ProcessUnfilteredKeyPressEvent(event);
120     } else {
121       DispatchKeyEventPostIME(event);
122     }
123     return true;
124   }
125
126   pending_key_events_.insert(current_keyevent_id_);
127
128   ui::KeyEvent* copied_event = new ui::KeyEvent(event);
129   GetEngine()->ProcessKeyEvent(
130       event,
131       base::Bind(&InputMethodChromeOS::ProcessKeyEventDone,
132                  weak_ptr_factory_.GetWeakPtr(),
133                  current_keyevent_id_,
134                  // Pass the ownership of |copied_event|.
135                  base::Owned(copied_event)));
136
137   ++current_keyevent_id_;
138   return true;
139 }
140
141 void InputMethodChromeOS::OnTextInputTypeChanged(
142     const TextInputClient* client) {
143   if (!IsTextInputClientFocused(client))
144     return;
145
146   UpdateContextFocusState();
147
148   chromeos::IMEEngineHandlerInterface* engine = GetEngine();
149   if (engine) {
150     // When focused input client is not changed, a text input type change should
151     // cause blur/focus events to engine.
152     // The focus in to or out from password field should also notify engine.
153     engine->FocusOut();
154     chromeos::IMEEngineHandlerInterface::InputContext context(
155         GetTextInputType(), GetTextInputMode());
156     engine->FocusIn(context);
157   }
158
159   InputMethodBase::OnTextInputTypeChanged(client);
160 }
161
162 void InputMethodChromeOS::OnCaretBoundsChanged(const TextInputClient* client) {
163   if (!IsInputFieldFocused() || !IsTextInputClientFocused(client))
164     return;
165
166   // The current text input type should not be NONE if |context_| is focused.
167   DCHECK(!IsTextInputTypeNone());
168   const gfx::Rect rect = GetTextInputClient()->GetCaretBounds();
169
170   gfx::Rect composition_head;
171   if (!GetTextInputClient()->GetCompositionCharacterBounds(0,
172                                                            &composition_head)) {
173     composition_head = rect;
174   }
175
176   chromeos::IMECandidateWindowHandlerInterface* candidate_window =
177       chromeos::IMEBridge::Get()->GetCandidateWindowHandler();
178   if (!candidate_window)
179     return;
180   candidate_window->SetCursorBounds(rect, composition_head);
181
182   gfx::Range text_range;
183   gfx::Range selection_range;
184   base::string16 surrounding_text;
185   if (!GetTextInputClient()->GetTextRange(&text_range) ||
186       !GetTextInputClient()->GetTextFromRange(text_range, &surrounding_text) ||
187       !GetTextInputClient()->GetSelectionRange(&selection_range)) {
188     previous_surrounding_text_.clear();
189     previous_selection_range_ = gfx::Range::InvalidRange();
190     return;
191   }
192
193   if (previous_selection_range_ == selection_range &&
194       previous_surrounding_text_ == surrounding_text)
195     return;
196
197   previous_selection_range_ = selection_range;
198   previous_surrounding_text_ = surrounding_text;
199
200   if (!selection_range.IsValid()) {
201     // TODO(nona): Ideally selection_range should not be invalid.
202     // TODO(nona): If javascript changes the focus on page loading, even (0,0)
203     //             can not be obtained. Need investigation.
204     return;
205   }
206
207   // Here SetSurroundingText accepts relative position of |surrounding_text|, so
208   // we have to convert |selection_range| from node coordinates to
209   // |surrounding_text| coordinates.
210   if (!GetEngine())
211     return;
212   GetEngine()->SetSurroundingText(base::UTF16ToUTF8(surrounding_text),
213                                   selection_range.start() - text_range.start(),
214                                   selection_range.end() - text_range.start());
215 }
216
217 void InputMethodChromeOS::CancelComposition(const TextInputClient* client) {
218   if (IsInputFieldFocused() && IsTextInputClientFocused(client))
219     ResetContext();
220 }
221
222 void InputMethodChromeOS::OnInputLocaleChanged() {
223   // Not supported.
224 }
225
226 std::string InputMethodChromeOS::GetInputLocale() {
227   // Not supported.
228   return "";
229 }
230
231 bool InputMethodChromeOS::IsActive() {
232   return true;
233 }
234
235 bool InputMethodChromeOS::IsCandidatePopupOpen() const {
236   // TODO(yukishiino): Implement this method.
237   return false;
238 }
239
240 void InputMethodChromeOS::OnWillChangeFocusedClient(
241     TextInputClient* focused_before,
242     TextInputClient* focused) {
243   ConfirmCompositionText();
244
245   if (GetEngine())
246     GetEngine()->FocusOut();
247 }
248
249 void InputMethodChromeOS::OnDidChangeFocusedClient(
250     TextInputClient* focused_before,
251     TextInputClient* focused) {
252   // Force to update the input type since client's TextInputStateChanged()
253   // function might not be called if text input types before the client loses
254   // focus and after it acquires focus again are the same.
255   UpdateContextFocusState();
256
257   if (GetEngine()) {
258     chromeos::IMEEngineHandlerInterface::InputContext context(
259         GetTextInputType(), GetTextInputMode());
260     GetEngine()->FocusIn(context);
261   }
262 }
263
264 void InputMethodChromeOS::ConfirmCompositionText() {
265   TextInputClient* client = GetTextInputClient();
266   if (client && client->HasCompositionText())
267     client->ConfirmCompositionText();
268
269   ResetContext();
270 }
271
272 void InputMethodChromeOS::ResetContext() {
273   if (!IsInputFieldFocused() || !GetTextInputClient())
274     return;
275
276   DCHECK(system_toplevel_window_focused());
277
278   composition_.Clear();
279   result_text_.clear();
280   composing_text_ = false;
281   composition_changed_ = false;
282
283   // We need to abandon all pending key events, but as above comment says, there
284   // is no reliable way to abandon all results generated by these abandoned key
285   // events.
286   AbandonAllPendingKeyEvents();
287
288   // This function runs asynchronously.
289   // Note: some input method engines may not support reset method, such as
290   // ibus-anthy. But as we control all input method engines by ourselves, we can
291   // make sure that all of the engines we are using support it correctly.
292   if (GetEngine())
293     GetEngine()->Reset();
294
295   character_composer_.Reset();
296 }
297
298 void InputMethodChromeOS::UpdateContextFocusState() {
299   ResetContext();
300   OnInputMethodChanged();
301
302   // Propagate the focus event to the candidate window handler which also
303   // manages the input method mode indicator.
304   chromeos::IMECandidateWindowHandlerInterface* candidate_window =
305       chromeos::IMEBridge::Get()->GetCandidateWindowHandler();
306   if (candidate_window)
307     candidate_window->FocusStateChanged(IsInputFieldFocused());
308
309   chromeos::IMEBridge::Get()->SetCurrentTextInputType(GetTextInputType());
310
311   if (!IsTextInputTypeNone())
312     OnCaretBoundsChanged(GetTextInputClient());
313 }
314
315 void InputMethodChromeOS::ProcessKeyEventPostIME(
316     const ui::KeyEvent& event,
317     bool handled) {
318   TextInputClient* client = GetTextInputClient();
319   if (!client) {
320     // As ibus works asynchronously, there is a chance that the focused client
321     // loses focus before this method gets called.
322     DispatchKeyEventPostIME(event);
323     return;
324   }
325
326   if (event.type() == ET_KEY_PRESSED && handled)
327     ProcessFilteredKeyPressEvent(event);
328
329   // In case the focus was changed by the key event. The |context_| should have
330   // been reset when the focused window changed.
331   if (client != GetTextInputClient())
332     return;
333
334   if (HasInputMethodResult())
335     ProcessInputMethodResult(event, handled);
336
337   // In case the focus was changed when sending input method results to the
338   // focused window.
339   if (client != GetTextInputClient())
340     return;
341
342   if (event.type() == ET_KEY_PRESSED && !handled)
343     ProcessUnfilteredKeyPressEvent(event);
344   else if (event.type() == ET_KEY_RELEASED)
345     DispatchKeyEventPostIME(event);
346 }
347
348 void InputMethodChromeOS::ProcessFilteredKeyPressEvent(
349     const ui::KeyEvent& event) {
350   if (NeedInsertChar()) {
351     DispatchKeyEventPostIME(event);
352   } else {
353     const ui::KeyEvent fabricated_event(ET_KEY_PRESSED,
354                                         VKEY_PROCESSKEY,
355                                         event.flags(),
356                                         false);  // is_char
357     DispatchKeyEventPostIME(fabricated_event);
358   }
359 }
360
361 void InputMethodChromeOS::ProcessUnfilteredKeyPressEvent(
362     const ui::KeyEvent& event) {
363   const TextInputClient* prev_client = GetTextInputClient();
364   DispatchKeyEventPostIME(event);
365
366   // We shouldn't dispatch the character anymore if the key event dispatch
367   // caused focus change. For example, in the following scenario,
368   // 1. visit a web page which has a <textarea>.
369   // 2. click Omnibox.
370   // 3. enable Korean IME, press A, then press Tab to move the focus to the web
371   //    page.
372   // We should return here not to send the Tab key event to RWHV.
373   TextInputClient* client = GetTextInputClient();
374   if (!client || client != prev_client)
375     return;
376
377   // If a key event was not filtered by |context_| and |character_composer_|,
378   // then it means the key event didn't generate any result text. So we need
379   // to send corresponding character to the focused text input client.
380   const uint32 event_flags = event.flags();
381   uint16 ch = 0;
382   if (event.HasNativeEvent()) {
383     const base::NativeEvent& native_event = event.native_event();
384
385     if (!(event_flags & ui::EF_CONTROL_DOWN))
386       ch = ui::GetCharacterFromXEvent(native_event);
387     if (!ch) {
388       ch = ui::GetCharacterFromKeyCode(
389           ui::KeyboardCodeFromNative(native_event), event_flags);
390     }
391   } else {
392     ch = ui::GetCharacterFromKeyCode(event.key_code(), event_flags);
393   }
394
395   if (ch)
396     client->InsertChar(ch, event_flags);
397 }
398
399 void InputMethodChromeOS::ProcessInputMethodResult(const ui::KeyEvent& event,
400                                                bool handled) {
401   TextInputClient* client = GetTextInputClient();
402   DCHECK(client);
403
404   if (result_text_.length()) {
405     if (handled && NeedInsertChar()) {
406       for (base::string16::const_iterator i = result_text_.begin();
407            i != result_text_.end(); ++i) {
408         client->InsertChar(*i, event.flags());
409       }
410     } else {
411       client->InsertText(result_text_);
412       composing_text_ = false;
413     }
414   }
415
416   if (composition_changed_ && !IsTextInputTypeNone()) {
417     if (composition_.text.length()) {
418       composing_text_ = true;
419       client->SetCompositionText(composition_);
420     } else if (result_text_.empty()) {
421       client->ClearCompositionText();
422     }
423   }
424
425   // We should not clear composition text here, as it may belong to the next
426   // composition session.
427   result_text_.clear();
428   composition_changed_ = false;
429 }
430
431 bool InputMethodChromeOS::NeedInsertChar() const {
432   return GetTextInputClient() &&
433       (IsTextInputTypeNone() ||
434        (!composing_text_ && result_text_.length() == 1));
435 }
436
437 bool InputMethodChromeOS::HasInputMethodResult() const {
438   return result_text_.length() || composition_changed_;
439 }
440
441 void InputMethodChromeOS::AbandonAllPendingKeyEvents() {
442   pending_key_events_.clear();
443 }
444
445 void InputMethodChromeOS::CommitText(const std::string& text) {
446   if (text.empty())
447     return;
448
449   // We need to receive input method result even if the text input type is
450   // TEXT_INPUT_TYPE_NONE, to make sure we can always send correct
451   // character for each key event to the focused text input client.
452   if (!GetTextInputClient())
453     return;
454
455   const base::string16 utf16_text = base::UTF8ToUTF16(text);
456   if (utf16_text.empty())
457     return;
458
459   // Append the text to the buffer, because commit signal might be fired
460   // multiple times when processing a key event.
461   result_text_.append(utf16_text);
462
463   // If we are not handling key event, do not bother sending text result if the
464   // focused text input client does not support text input.
465   if (pending_key_events_.empty() && !IsTextInputTypeNone()) {
466     GetTextInputClient()->InsertText(utf16_text);
467     result_text_.clear();
468   }
469 }
470
471 void InputMethodChromeOS::UpdateCompositionText(
472     const chromeos::CompositionText& text,
473     uint32 cursor_pos,
474     bool visible) {
475   if (IsTextInputTypeNone())
476     return;
477
478   if (!CanComposeInline()) {
479     chromeos::IMECandidateWindowHandlerInterface* candidate_window =
480         chromeos::IMEBridge::Get()->GetCandidateWindowHandler();
481     if (candidate_window)
482       candidate_window->UpdatePreeditText(text.text(), cursor_pos, visible);
483   }
484
485   // |visible| argument is very confusing. For example, what's the correct
486   // behavior when:
487   // 1. OnUpdatePreeditText() is called with a text and visible == false, then
488   // 2. OnShowPreeditText() is called afterwards.
489   //
490   // If it's only for clearing the current preedit text, then why not just use
491   // OnHidePreeditText()?
492   if (!visible) {
493     HidePreeditText();
494     return;
495   }
496
497   ExtractCompositionText(text, cursor_pos, &composition_);
498
499   composition_changed_ = true;
500
501   // In case OnShowPreeditText() is not called.
502   if (composition_.text.length())
503     composing_text_ = true;
504
505   // If we receive a composition text without pending key event, then we need to
506   // send it to the focused text input client directly.
507   if (pending_key_events_.empty()) {
508     GetTextInputClient()->SetCompositionText(composition_);
509     composition_changed_ = false;
510     composition_.Clear();
511   }
512 }
513
514 void InputMethodChromeOS::HidePreeditText() {
515   if (composition_.text.empty() || IsTextInputTypeNone())
516     return;
517
518   // Intentionally leaves |composing_text_| unchanged.
519   composition_changed_ = true;
520   composition_.Clear();
521
522   if (pending_key_events_.empty()) {
523     TextInputClient* client = GetTextInputClient();
524     if (client && client->HasCompositionText())
525       client->ClearCompositionText();
526     composition_changed_ = false;
527   }
528 }
529
530 void InputMethodChromeOS::DeleteSurroundingText(int32 offset, uint32 length) {
531   if (!composition_.text.empty())
532     return;  // do nothing if there is ongoing composition.
533   if (offset < 0 && static_cast<uint32>(-1 * offset) != length)
534     return;  // only preceding text can be deletable.
535   if (GetTextInputClient())
536     GetTextInputClient()->ExtendSelectionAndDelete(length, 0U);
537 }
538
539 bool InputMethodChromeOS::ExecuteCharacterComposer(const ui::KeyEvent& event) {
540   if (!character_composer_.FilterKeyPress(event))
541     return false;
542
543   // CharacterComposer consumed the key event.  Update the composition text.
544   chromeos::CompositionText preedit;
545   preedit.set_text(character_composer_.preedit_string());
546   UpdateCompositionText(preedit, preedit.text().size(),
547                         !preedit.text().empty());
548   std::string commit_text =
549       base::UTF16ToUTF8(character_composer_.composed_character());
550   if (!commit_text.empty()) {
551     CommitText(commit_text);
552   }
553   return true;
554 }
555
556 void InputMethodChromeOS::ExtractCompositionText(
557     const chromeos::CompositionText& text,
558     uint32 cursor_position,
559     CompositionText* out_composition) const {
560   out_composition->Clear();
561   out_composition->text = text.text();
562
563   if (out_composition->text.empty())
564     return;
565
566   // ibus uses character index for cursor position and attribute range, but we
567   // use char16 offset for them. So we need to do conversion here.
568   std::vector<size_t> char16_offsets;
569   size_t length = out_composition->text.length();
570   base::i18n::UTF16CharIterator char_iterator(&out_composition->text);
571   do {
572     char16_offsets.push_back(char_iterator.array_pos());
573   } while (char_iterator.Advance());
574
575   // The text length in Unicode characters.
576   uint32 char_length = static_cast<uint32>(char16_offsets.size());
577   // Make sure we can convert the value of |char_length| as well.
578   char16_offsets.push_back(length);
579
580   size_t cursor_offset =
581       char16_offsets[std::min(char_length, cursor_position)];
582
583   out_composition->selection = gfx::Range(cursor_offset);
584
585   const std::vector<chromeos::CompositionText::UnderlineAttribute>&
586       underline_attributes = text.underline_attributes();
587   if (!underline_attributes.empty()) {
588     for (size_t i = 0; i < underline_attributes.size(); ++i) {
589       const uint32 start = underline_attributes[i].start_index;
590       const uint32 end = underline_attributes[i].end_index;
591       if (start >= end)
592         continue;
593       CompositionUnderline underline(
594           char16_offsets[start], char16_offsets[end],
595           SK_ColorBLACK, false /* thick */);
596       if (underline_attributes[i].type ==
597           chromeos::CompositionText::COMPOSITION_TEXT_UNDERLINE_DOUBLE)
598         underline.thick = true;
599       else if (underline_attributes[i].type ==
600                chromeos::CompositionText::COMPOSITION_TEXT_UNDERLINE_ERROR)
601         underline.color = SK_ColorRED;
602       out_composition->underlines.push_back(underline);
603     }
604   }
605
606   DCHECK(text.selection_start() <= text.selection_end());
607   if (text.selection_start() < text.selection_end()) {
608     const uint32 start = text.selection_start();
609     const uint32 end = text.selection_end();
610     CompositionUnderline underline(
611         char16_offsets[start], char16_offsets[end],
612         SK_ColorBLACK, true /* thick */);
613     out_composition->underlines.push_back(underline);
614
615     // If the cursor is at start or end of this underline, then we treat
616     // it as the selection range as well, but make sure to set the cursor
617     // position to the selection end.
618     if (underline.start_offset == cursor_offset) {
619       out_composition->selection.set_start(underline.end_offset);
620       out_composition->selection.set_end(cursor_offset);
621     } else if (underline.end_offset == cursor_offset) {
622       out_composition->selection.set_start(underline.start_offset);
623       out_composition->selection.set_end(cursor_offset);
624     }
625   }
626
627   // Use a black thin underline by default.
628   if (out_composition->underlines.empty()) {
629     out_composition->underlines.push_back(CompositionUnderline(
630         0, length, SK_ColorBLACK, false /* thick */));
631   }
632 }
633
634 bool InputMethodChromeOS::IsInputFieldFocused() {
635   TextInputType type = GetTextInputType();
636   return (type != TEXT_INPUT_TYPE_NONE) && (type != TEXT_INPUT_TYPE_PASSWORD);
637 }
638
639 }  // namespace ui