diff options
Diffstat (limited to 'chromium/ui/base/ime/input_method_ibus.cc')
-rw-r--r-- | chromium/ui/base/ime/input_method_ibus.cc | 665 |
1 files changed, 0 insertions, 665 deletions
diff --git a/chromium/ui/base/ime/input_method_ibus.cc b/chromium/ui/base/ime/input_method_ibus.cc deleted file mode 100644 index 6f1213b7c8f..00000000000 --- a/chromium/ui/base/ime/input_method_ibus.cc +++ /dev/null @@ -1,665 +0,0 @@ -// Copyright (c) 2012 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 "ui/base/ime/input_method_ibus.h" - -#include <algorithm> -#include <cstring> -#include <set> -#include <vector> - -#include "base/basictypes.h" -#include "base/bind.h" -#include "base/i18n/char_iterator.h" -#include "base/logging.h" -#include "base/strings/string_util.h" -#include "base/strings/utf_string_conversions.h" -#include "base/third_party/icu/icu_utf.h" -#include "chromeos/ime/ibus_text.h" -#include "chromeos/ime/input_method_descriptor.h" -#include "chromeos/ime/input_method_manager.h" -#include "ui/base/ime/text_input_client.h" -#include "ui/events/event.h" -#include "ui/events/event_constants.h" -#include "ui/events/event_utils.h" -#include "ui/events/keycodes/keyboard_code_conversion.h" -#include "ui/events/keycodes/keyboard_code_conversion_x.h" -#include "ui/events/keycodes/keyboard_codes.h" -#include "ui/gfx/rect.h" - -namespace { -chromeos::IBusEngineHandlerInterface* GetEngine() { - return chromeos::IBusBridge::Get()->GetCurrentEngineHandler(); -} -} // namespace - -namespace ui { - -// InputMethodIBus implementation ----------------------------------------- -InputMethodIBus::InputMethodIBus( - internal::InputMethodDelegate* delegate) - : context_focused_(false), - composing_text_(false), - composition_changed_(false), - suppress_next_result_(false), - current_keyevent_id_(0), - previous_textinput_type_(TEXT_INPUT_TYPE_NONE), - weak_ptr_factory_(this) { - SetDelegate(delegate); - chromeos::IBusBridge::Get()->SetInputContextHandler(this); - - UpdateContextFocusState(); - OnInputMethodChanged(); -} - -InputMethodIBus::~InputMethodIBus() { - AbandonAllPendingKeyEvents(); - context_focused_ = false; - ConfirmCompositionText(); - // We are dead, so we need to ask the client to stop relying on us. - OnInputMethodChanged(); - - chromeos::IBusBridge::Get()->SetInputContextHandler(NULL); -} - -void InputMethodIBus::OnFocus() { - InputMethodBase::OnFocus(); - UpdateContextFocusState(); -} - -void InputMethodIBus::OnBlur() { - ConfirmCompositionText(); - InputMethodBase::OnBlur(); - UpdateContextFocusState(); -} - -bool InputMethodIBus::OnUntranslatedIMEMessage(const base::NativeEvent& event, - NativeEventResult* result) { - return false; -} - -void InputMethodIBus::ProcessKeyEventDone(uint32 id, - ui::KeyEvent* event, - bool is_handled) { - if (pending_key_events_.find(id) == pending_key_events_.end()) - return; // Abandoned key event. - - DCHECK(event); - if (event->type() == ET_KEY_PRESSED) { - if (is_handled) { - // IME event has a priority to be handled, so that character composer - // should be reset. - character_composer_.Reset(); - } else { - // If IME does not handle key event, passes keyevent to character composer - // to be able to compose complex characters. - is_handled = ExecuteCharacterComposer(*event); - } - } - - if (event->type() == ET_KEY_PRESSED || event->type() == ET_KEY_RELEASED) - ProcessKeyEventPostIME(*event, is_handled); - - // ProcessKeyEventPostIME may change the |pending_key_events_|. - pending_key_events_.erase(id); -} - -bool InputMethodIBus::DispatchKeyEvent(const ui::KeyEvent& event) { - DCHECK(event.type() == ET_KEY_PRESSED || event.type() == ET_KEY_RELEASED); - DCHECK(system_toplevel_window_focused()); - - // If |context_| is not usable, then we can only dispatch the key event as is. - // We also dispatch the key event directly if the current text input type is - // TEXT_INPUT_TYPE_PASSWORD, to bypass the input method. - // Note: We need to send the key event to ibus even if the |context_| is not - // enabled, so that ibus can have a chance to enable the |context_|. - if (!context_focused_ || !GetEngine() || - GetTextInputType() == TEXT_INPUT_TYPE_PASSWORD ) { - if (event.type() == ET_KEY_PRESSED) { - if (ExecuteCharacterComposer(event)) { - // Treating as PostIME event if character composer handles key event and - // generates some IME event, - ProcessKeyEventPostIME(event, true); - return true; - } - ProcessUnfilteredKeyPressEvent(event); - } else { - DispatchKeyEventPostIME(event); - } - return true; - } - - pending_key_events_.insert(current_keyevent_id_); - - ui::KeyEvent* copied_event = new ui::KeyEvent(event); - GetEngine()->ProcessKeyEvent( - event, - base::Bind(&InputMethodIBus::ProcessKeyEventDone, - weak_ptr_factory_.GetWeakPtr(), - current_keyevent_id_, - // Pass the ownership of |copied_event|. - base::Owned(copied_event))); - - ++current_keyevent_id_; - - // We don't want to suppress the result generated by this key event, but it - // may cause problem. See comment in ResetContext() method. - suppress_next_result_ = false; - return true; -} - -void InputMethodIBus::OnTextInputTypeChanged(const TextInputClient* client) { - if (IsTextInputClientFocused(client)) { - ResetContext(); - UpdateContextFocusState(); - if (previous_textinput_type_ != client->GetTextInputType()) - OnInputMethodChanged(); - previous_textinput_type_ = client->GetTextInputType(); - } - InputMethodBase::OnTextInputTypeChanged(client); -} - -void InputMethodIBus::OnCaretBoundsChanged(const TextInputClient* client) { - if (!context_focused_ || !IsTextInputClientFocused(client)) - return; - - // The current text input type should not be NONE if |context_| is focused. - DCHECK(!IsTextInputTypeNone()); - const gfx::Rect rect = GetTextInputClient()->GetCaretBounds(); - - gfx::Rect composition_head; - if (!GetTextInputClient()->GetCompositionCharacterBounds(0, - &composition_head)) { - composition_head = rect; - } - - chromeos::IBusPanelCandidateWindowHandlerInterface* candidate_window = - chromeos::IBusBridge::Get()->GetCandidateWindowHandler(); - if (!candidate_window) - return; - candidate_window->SetCursorBounds(rect, composition_head); - - gfx::Range text_range; - gfx::Range selection_range; - string16 surrounding_text; - if (!GetTextInputClient()->GetTextRange(&text_range) || - !GetTextInputClient()->GetTextFromRange(text_range, &surrounding_text) || - !GetTextInputClient()->GetSelectionRange(&selection_range)) { - previous_surrounding_text_.clear(); - previous_selection_range_ = gfx::Range::InvalidRange(); - return; - } - - if (previous_selection_range_ == selection_range && - previous_surrounding_text_ == surrounding_text) - return; - - previous_selection_range_ = selection_range; - previous_surrounding_text_ = surrounding_text; - - if (!selection_range.IsValid()) { - // TODO(nona): Ideally selection_range should not be invalid. - // TODO(nona): If javascript changes the focus on page loading, even (0,0) - // can not be obtained. Need investigation. - return; - } - - // Here SetSurroundingText accepts relative position of |surrounding_text|, so - // we have to convert |selection_range| from node coordinates to - // |surrounding_text| coordinates. - if (!GetEngine()) - return; - GetEngine()->SetSurroundingText(UTF16ToUTF8(surrounding_text), - selection_range.start() - text_range.start(), - selection_range.end() - text_range.start()); -} - -void InputMethodIBus::CancelComposition(const TextInputClient* client) { - if (context_focused_ && IsTextInputClientFocused(client)) - ResetContext(); -} - -void InputMethodIBus::OnInputLocaleChanged() { - // Not supported. -} - -std::string InputMethodIBus::GetInputLocale() { - // Not supported. - return ""; -} - -base::i18n::TextDirection InputMethodIBus::GetInputTextDirection() { - // Not supported. - return base::i18n::UNKNOWN_DIRECTION; -} - -bool InputMethodIBus::IsActive() { - return true; -} - -bool InputMethodIBus::IsCandidatePopupOpen() const { - // TODO(yukishiino): Implement this method. - return false; -} - -void InputMethodIBus::OnWillChangeFocusedClient(TextInputClient* focused_before, - TextInputClient* focused) { - ConfirmCompositionText(); -} - -void InputMethodIBus::OnDidChangeFocusedClient(TextInputClient* focused_before, - TextInputClient* focused) { - // Force to update the input type since client's TextInputStateChanged() - // function might not be called if text input types before the client loses - // focus and after it acquires focus again are the same. - OnTextInputTypeChanged(focused); - - UpdateContextFocusState(); - // Force to update caret bounds, in case the client thinks that the caret - // bounds has not changed. - OnCaretBoundsChanged(focused); -} - -void InputMethodIBus::ConfirmCompositionText() { - TextInputClient* client = GetTextInputClient(); - if (client && client->HasCompositionText()) - client->ConfirmCompositionText(); - - ResetContext(); -} - -void InputMethodIBus::ResetContext() { - if (!context_focused_ || !GetTextInputClient()) - return; - - DCHECK(system_toplevel_window_focused()); - - // Because ibus runs in asynchronous mode, the input method may still send us - // results after sending out the reset request, so we use a flag to discard - // all results generated by previous key events. But because ibus does not - // have a mechanism to identify each key event and corresponding results, this - // approach will not work for some corner cases. For example if the user types - // very fast, then the next key event may come in before the |context_| is - // really reset. Then we actually cannot know whether or not the next - // result should be discard. - suppress_next_result_ = true; - - composition_.Clear(); - result_text_.clear(); - composing_text_ = false; - composition_changed_ = false; - - // We need to abandon all pending key events, but as above comment says, there - // is no reliable way to abandon all results generated by these abandoned key - // events. - AbandonAllPendingKeyEvents(); - - // This function runs asynchronously. - // Note: some input method engines may not support reset method, such as - // ibus-anthy. But as we control all input method engines by ourselves, we can - // make sure that all of the engines we are using support it correctly. - if (GetEngine()) - GetEngine()->Reset(); - - character_composer_.Reset(); -} - -void InputMethodIBus::UpdateContextFocusState() { - const bool old_context_focused = context_focused_; - const TextInputType current_text_input_type = GetTextInputType(); - // Use switch here in case we are going to add more text input types. - switch (current_text_input_type) { - case TEXT_INPUT_TYPE_NONE: - case TEXT_INPUT_TYPE_PASSWORD: - context_focused_ = false; - break; - default: - context_focused_ = true; - break; - } - - // Propagate the focus event to the candidate window handler which also - // manages the input method mode indicator. - chromeos::IBusPanelCandidateWindowHandlerInterface* candidate_window = - chromeos::IBusBridge::Get()->GetCandidateWindowHandler(); - if (candidate_window) - candidate_window->FocusStateChanged(context_focused_); - - if (!GetEngine()) - return; - - // We only focus in |context_| when the focus is in a normal textfield. - // Even if focus is not changed, a text input type change causes a focus - // blink. - // ibus_input_context_focus_{in|out}() run asynchronously. - bool input_type_change = - (current_text_input_type != previous_textinput_type_); - if (old_context_focused && (!context_focused_ || input_type_change)) - GetEngine()->FocusOut(); - if (context_focused_ && (!old_context_focused || input_type_change)) { - chromeos::IBusEngineHandlerInterface::InputContext context( - current_text_input_type, GetTextInputMode()); - GetEngine()->FocusIn(context); - OnCaretBoundsChanged(GetTextInputClient()); - } -} - -void InputMethodIBus::ProcessKeyEventPostIME( - const ui::KeyEvent& event, - bool handled) { - TextInputClient* client = GetTextInputClient(); - if (!client) { - // As ibus works asynchronously, there is a chance that the focused client - // loses focus before this method gets called. - DispatchKeyEventPostIME(event); - return; - } - - if (event.type() == ET_KEY_PRESSED && handled) - ProcessFilteredKeyPressEvent(event); - - // In case the focus was changed by the key event. The |context_| should have - // been reset when the focused window changed. - if (client != GetTextInputClient()) - return; - - if (HasInputMethodResult()) - ProcessInputMethodResult(event, handled); - - // In case the focus was changed when sending input method results to the - // focused window. - if (client != GetTextInputClient()) - return; - - if (event.type() == ET_KEY_PRESSED && !handled) - ProcessUnfilteredKeyPressEvent(event); - else if (event.type() == ET_KEY_RELEASED) - DispatchKeyEventPostIME(event); -} - -void InputMethodIBus::ProcessFilteredKeyPressEvent(const ui::KeyEvent& event) { - if (NeedInsertChar()) { - DispatchKeyEventPostIME(event); - } else { - const ui::KeyEvent fabricated_event(ET_KEY_PRESSED, - VKEY_PROCESSKEY, - event.flags(), - false); // is_char - DispatchKeyEventPostIME(fabricated_event); - } -} - -void InputMethodIBus::ProcessUnfilteredKeyPressEvent( - const ui::KeyEvent& event) { - const TextInputClient* prev_client = GetTextInputClient(); - DispatchKeyEventPostIME(event); - - // We shouldn't dispatch the character anymore if the key event dispatch - // caused focus change. For example, in the following scenario, - // 1. visit a web page which has a <textarea>. - // 2. click Omnibox. - // 3. enable Korean IME, press A, then press Tab to move the focus to the web - // page. - // We should return here not to send the Tab key event to RWHV. - TextInputClient* client = GetTextInputClient(); - if (!client || client != prev_client) - 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. - const uint32 event_flags = event.flags(); - uint16 ch = 0; - if (event.HasNativeEvent()) { - const base::NativeEvent& native_event = event.native_event(); - - if (!(event_flags & ui::EF_CONTROL_DOWN)) - ch = ui::GetCharacterFromXEvent(native_event); - if (!ch) { - ch = ui::GetCharacterFromKeyCode( - ui::KeyboardCodeFromNative(native_event), event_flags); - } - } else { - ch = ui::GetCharacterFromKeyCode(event.key_code(), event_flags); - } - - if (ch) - client->InsertChar(ch, event_flags); -} - -void InputMethodIBus::ProcessInputMethodResult(const ui::KeyEvent& event, - bool handled) { - TextInputClient* client = GetTextInputClient(); - DCHECK(client); - - if (result_text_.length()) { - if (handled && NeedInsertChar()) { - for (string16::const_iterator i = result_text_.begin(); - i != result_text_.end(); ++i) { - client->InsertChar(*i, event.flags()); - } - } else { - client->InsertText(result_text_); - composing_text_ = false; - } - } - - if (composition_changed_ && !IsTextInputTypeNone()) { - if (composition_.text.length()) { - composing_text_ = true; - client->SetCompositionText(composition_); - } else if (result_text_.empty()) { - client->ClearCompositionText(); - } - } - - // We should not clear composition text here, as it may belong to the next - // composition session. - result_text_.clear(); - composition_changed_ = false; -} - -bool InputMethodIBus::NeedInsertChar() const { - return GetTextInputClient() && - (IsTextInputTypeNone() || - (!composing_text_ && result_text_.length() == 1)); -} - -bool InputMethodIBus::HasInputMethodResult() const { - return result_text_.length() || composition_changed_; -} - -void InputMethodIBus::AbandonAllPendingKeyEvents() { - pending_key_events_.clear(); -} - -void InputMethodIBus::CommitText(const std::string& text) { - if (suppress_next_result_ || text.empty()) - return; - - // We need to receive input method result even if the text input type is - // TEXT_INPUT_TYPE_NONE, to make sure we can always send correct - // character for each key event to the focused text input client. - if (!GetTextInputClient()) - return; - - const string16 utf16_text = UTF8ToUTF16(text); - if (utf16_text.empty()) - return; - - // Append the text to the buffer, because commit signal might be fired - // multiple times when processing a key event. - result_text_.append(utf16_text); - - // If we are not handling key event, do not bother sending text result if the - // focused text input client does not support text input. - if (pending_key_events_.empty() && !IsTextInputTypeNone()) { - GetTextInputClient()->InsertText(utf16_text); - result_text_.clear(); - } -} - -void InputMethodIBus::UpdatePreeditText(const chromeos::IBusText& text, - uint32 cursor_pos, - bool visible) { - if (suppress_next_result_ || IsTextInputTypeNone()) - return; - - if (!CanComposeInline()) { - chromeos::IBusPanelCandidateWindowHandlerInterface* candidate_window = - chromeos::IBusBridge::Get()->GetCandidateWindowHandler(); - if (candidate_window) - candidate_window->UpdatePreeditText(text.text(), cursor_pos, visible); - } - - // |visible| argument is very confusing. For example, what's the correct - // behavior when: - // 1. OnUpdatePreeditText() is called with a text and visible == false, then - // 2. OnShowPreeditText() is called afterwards. - // - // If it's only for clearing the current preedit text, then why not just use - // OnHidePreeditText()? - if (!visible) { - HidePreeditText(); - return; - } - - ExtractCompositionText(text, cursor_pos, &composition_); - - composition_changed_ = true; - - // In case OnShowPreeditText() is not called. - if (composition_.text.length()) - composing_text_ = true; - - // If we receive a composition text without pending key event, then we need to - // send it to the focused text input client directly. - if (pending_key_events_.empty()) { - GetTextInputClient()->SetCompositionText(composition_); - composition_changed_ = false; - composition_.Clear(); - } -} - -void InputMethodIBus::HidePreeditText() { - if (composition_.text.empty() || IsTextInputTypeNone()) - return; - - // Intentionally leaves |composing_text_| unchanged. - composition_changed_ = true; - composition_.Clear(); - - if (pending_key_events_.empty()) { - TextInputClient* client = GetTextInputClient(); - if (client && client->HasCompositionText()) - client->ClearCompositionText(); - composition_changed_ = false; - } -} - -void InputMethodIBus::DeleteSurroundingText(int32 offset, uint32 length) { - if (!composition_.text.empty()) - return; // do nothing if there is ongoing composition. - if (offset < 0 && static_cast<uint32>(-1 * offset) != length) - return; // only preceding text can be deletable. - if (GetTextInputClient()) - GetTextInputClient()->ExtendSelectionAndDelete(length, 0U); -} - -bool InputMethodIBus::ExecuteCharacterComposer(const ui::KeyEvent& event) { - bool consumed = character_composer_.FilterKeyPress(event); - - suppress_next_result_ = false; - chromeos::IBusText preedit; - preedit.set_text( - UTF16ToUTF8(character_composer_.preedit_string())); - UpdatePreeditText(preedit, preedit.text().size(), - !preedit.text().empty()); - std::string commit_text = - UTF16ToUTF8(character_composer_.composed_character()); - if (!commit_text.empty()) { - CommitText(commit_text); - } - return consumed; -} - -void InputMethodIBus::ExtractCompositionText( - const chromeos::IBusText& text, - uint32 cursor_position, - CompositionText* out_composition) const { - out_composition->Clear(); - out_composition->text = UTF8ToUTF16(text.text()); - - if (out_composition->text.empty()) - return; - - // ibus uses character index for cursor position and attribute range, but we - // use char16 offset for them. So we need to do conversion here. - std::vector<size_t> char16_offsets; - size_t length = out_composition->text.length(); - base::i18n::UTF16CharIterator char_iterator(&out_composition->text); - do { - char16_offsets.push_back(char_iterator.array_pos()); - } while (char_iterator.Advance()); - - // The text length in Unicode characters. - uint32 char_length = static_cast<uint32>(char16_offsets.size()); - // Make sure we can convert the value of |char_length| as well. - char16_offsets.push_back(length); - - size_t cursor_offset = - char16_offsets[std::min(char_length, cursor_position)]; - - out_composition->selection = gfx::Range(cursor_offset); - - const std::vector<chromeos::IBusText::UnderlineAttribute>& - underline_attributes = text.underline_attributes(); - if (!underline_attributes.empty()) { - for (size_t i = 0; i < underline_attributes.size(); ++i) { - const uint32 start = underline_attributes[i].start_index; - const uint32 end = underline_attributes[i].end_index; - if (start >= end) - continue; - CompositionUnderline underline( - char16_offsets[start], char16_offsets[end], - SK_ColorBLACK, false /* thick */); - if (underline_attributes[i].type == - chromeos::IBusText::IBUS_TEXT_UNDERLINE_DOUBLE) - underline.thick = true; - else if (underline_attributes[i].type == - chromeos::IBusText::IBUS_TEXT_UNDERLINE_ERROR) - underline.color = SK_ColorRED; - out_composition->underlines.push_back(underline); - } - } - - DCHECK(text.selection_start() <= text.selection_end()); - if (text.selection_start() < text.selection_end()) { - const uint32 start = text.selection_start(); - const uint32 end = text.selection_end(); - CompositionUnderline underline( - char16_offsets[start], char16_offsets[end], - SK_ColorBLACK, true /* thick */); - out_composition->underlines.push_back(underline); - - // If the cursor is at start or end of this underline, then we treat - // it as the selection range as well, but make sure to set the cursor - // position to the selection end. - if (underline.start_offset == cursor_offset) { - out_composition->selection.set_start(underline.end_offset); - out_composition->selection.set_end(cursor_offset); - } else if (underline.end_offset == cursor_offset) { - out_composition->selection.set_start(underline.start_offset); - out_composition->selection.set_end(cursor_offset); - } - } - - // Use a black thin underline by default. - if (out_composition->underlines.empty()) { - out_composition->underlines.push_back(CompositionUnderline( - 0, length, SK_ColorBLACK, false /* thick */)); - } -} - -} // namespace ui |