diff options
author | suzhe@google.com <suzhe@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-01 22:59:32 +0000 |
---|---|---|
committer | suzhe@google.com <suzhe@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-01 22:59:32 +0000 |
commit | 4b99402b089ba598ae26c82d99c3c3d2fe0cd574 (patch) | |
tree | f4fe9b7a92bc90b364c40b901919fee163b7061b | |
parent | 4b8b6d844d019a612a02274d298e3dcffc42276b (diff) | |
download | chromium_src-4b99402b089ba598ae26c82d99c3c3d2fe0cd574.zip chromium_src-4b99402b089ba598ae26c82d99c3c3d2fe0cd574.tar.gz chromium_src-4b99402b089ba598ae26c82d99c3c3d2fe0cd574.tar.bz2 |
Refactor RenderWidgetHostViewViews to use the new input method API for Views.
This CL also fixes some issues related to NULL host_ pointer in the
original code. Hope the fixes are correct.
Another side effect of this CL is: it converts views::KeyEvent to NativeWebKeyboardEvent by using the conversion table of Views instead of WebInputEventFactory provided by WebKit. See bug 54315.
BUG=54315
BUG=75003
TEST=none
Review URL: http://codereview.chromium.org/6713083
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@80238 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_edit_view_views.cc | 4 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_widget_host_view_views.cc | 473 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_widget_host_view_views.h | 50 | ||||
-rw-r--r-- | content/common/native_web_keyboard_event.h | 28 | ||||
-rw-r--r-- | content/common/native_web_keyboard_event_views.cc | 86 | ||||
-rw-r--r-- | content/content_common.gypi | 5 | ||||
-rw-r--r-- | views/ime/ibus_ime_context.cc | 398 | ||||
-rw-r--r-- | views/ime/ime_context.cc | 88 | ||||
-rw-r--r-- | views/ime/ime_context.h | 240 | ||||
-rw-r--r-- | views/ime/input_method_gtk.cc | 5 | ||||
-rw-r--r-- | views/ime/input_method_ibus.cc | 998 | ||||
-rw-r--r-- | views/ime/input_method_ibus.h | 204 | ||||
-rw-r--r-- | views/views.gyp | 24 | ||||
-rw-r--r-- | views/widget/widget_gtk.cc | 15 | ||||
-rw-r--r-- | views/widget/widget_gtk.h | 2 |
15 files changed, 1619 insertions, 1001 deletions
diff --git a/chrome/browser/autocomplete/autocomplete_edit_view_views.cc b/chrome/browser/autocomplete/autocomplete_edit_view_views.cc index ce9ef43..55d49387 100644 --- a/chrome/browser/autocomplete/autocomplete_edit_view_views.cc +++ b/chrome/browser/autocomplete/autocomplete_edit_view_views.cc @@ -422,9 +422,9 @@ void AutocompleteEditViewViews::UpdatePopup() { // the text, or in the middle of composition. ui::Range sel; textfield_->GetSelectedRange(&sel); - bool no_inline_autocomplete = sel.GetMax() < GetTextLength(); + bool no_inline_autocomplete = + sel.GetMax() < GetTextLength() || textfield_->IsIMEComposing(); - // TODO(oshima): Support IME. Don't show autocomplete if IME has some text. model_->StartAutocomplete(!sel.is_empty(), no_inline_autocomplete); } diff --git a/chrome/browser/renderer_host/render_widget_host_view_views.cc b/chrome/browser/renderer_host/render_widget_host_view_views.cc index 977f877..47dc659 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_views.cc +++ b/chrome/browser/renderer_host/render_widget_host_view_views.cc @@ -30,7 +30,7 @@ #include "ui/gfx/canvas_skia.h" #include "ui/gfx/gtk_native_view_id_manager.h" #include "views/events/event.h" -#include "views/ime/ime_context.h" +#include "views/ime/input_method.h" #include "views/widget/widget.h" #include "views/widget/widget_gtk.h" @@ -145,149 +145,6 @@ void InitializeWebMouseEventFromViewsEvent(const views::LocatedEvent& event, } // namespace -class IMEContextHandler : public views::CommitTextListener, - public views::CompositionListener, - public views::ForwardKeyEventListener { - public: - explicit IMEContextHandler( - RenderWidgetHostViewViews* host_view) - : host_view_(host_view), - is_enabled_(false), - is_focused_(false), - ime_context_(views::IMEContext::Create(host_view_)) { - ime_context_->set_commit_text_listener(this); - ime_context_->set_composition_listener(this); - ime_context_->set_forward_key_event_listener(this); - } - - // IMEContext Listeners implementation - virtual void OnCommitText(views::IMEContext* sender, - const string16& text) { - DCHECK(ime_context_ == sender); - - RenderWidgetHost* host = host_view_->GetRenderWidgetHost(); - if (host) { - SendFakeCompositionWebKeyboardEvent(WebKit::WebInputEvent::RawKeyDown); - host->ImeConfirmComposition(text); - SendFakeCompositionWebKeyboardEvent(WebKit::WebInputEvent::KeyUp); - } - } - - virtual void OnStartComposition(views::IMEContext* sender) { - DCHECK(ime_context_ == sender); - } - - virtual void OnEndComposition(views::IMEContext* sender) { - DCHECK(ime_context_ == sender); - - RenderWidgetHost* host = host_view_->GetRenderWidgetHost(); - if (host) - host->ImeCancelComposition(); - } - - virtual void OnSetComposition(views::IMEContext* sender, - const string16& text, - const views::CompositionAttributeList& attributes, - uint32 cursor_pos) { - DCHECK(ime_context_ == sender); - - RenderWidgetHost* host = host_view_->GetRenderWidgetHost(); - if (host) { - SendFakeCompositionWebKeyboardEvent(WebKit::WebInputEvent::RawKeyDown); - - // Cast CompositonAttribute to WebKit::WebCompositionUnderline directly, - // becasue CompositionAttribute is duplicated from - // WebKit::WebCompositionUnderline. - const std::vector<WebKit::WebCompositionUnderline>& underlines = - reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>( - attributes); - host->ImeSetComposition(text, underlines, cursor_pos, cursor_pos); - SendFakeCompositionWebKeyboardEvent(WebKit::WebInputEvent::KeyUp); - } - } - - virtual void OnForwardKeyEvent(views::IMEContext* sender, - const views::KeyEvent& event) { - DCHECK(ime_context_ == sender); - - host_view_->ForwardKeyEvent(event); - } - - bool FilterKeyEvent(const views::KeyEvent& event) { - return is_enabled_ && ime_context_->FilterKeyEvent(event); - } - - void Focus() { - if (!is_focused_) { - ime_context_->Focus(); - is_focused_ = true; - } - - // Enables RenderWidget's IME related events, so that we can be notified - // when WebKit wants to enable or disable IME. - RenderWidgetHost* host = host_view_->GetRenderWidgetHost(); - if (host) - host->SetInputMethodActive(true); - } - - void Blur() { - if (is_focused_) { - ime_context_->Blur(); - is_focused_ = false; - } - - // Disable RenderWidget's IME related events to save bandwidth. - RenderWidgetHost* host = host_view_->GetRenderWidgetHost(); - if (host) - host->SetInputMethodActive(false); - } - - void ImeUpdateTextInputState(WebKit::WebTextInputType type, - const gfx::Rect& caret_rect) { - bool enable = - (type != WebKit::WebTextInputTypeNone) && - (type != WebKit::WebTextInputTypePassword); - - if (is_enabled_ != enable) { - is_enabled_ = enable; - if (is_focused_) { - if (is_enabled_) - ime_context_->Focus(); - else - ime_context_->Blur(); - } - } - - if (is_enabled_) { - gfx::Point p(caret_rect.origin()); - views::View::ConvertPointToScreen(host_view_, &p); - - ime_context_->SetCursorLocation(gfx::Rect(p, caret_rect.size())); - } - } - - void Reset() { - ime_context_->Reset(); - } - - private: - void SendFakeCompositionWebKeyboardEvent(WebKit::WebInputEvent::Type type) { - NativeWebKeyboardEvent fake_event; - fake_event.windowsKeyCode = kCompositionEventKeyCode; - fake_event.skip_in_browser = true; - fake_event.type = type; - host_view_->ForwardWebKeyboardEvent(fake_event); - } - - private: - RenderWidgetHostViewViews* host_view_; - bool is_enabled_; - bool is_focused_; - scoped_ptr<views::IMEContext> ime_context_; - - DISALLOW_COPY_AND_ASSIGN(IMEContextHandler); -}; - // static RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget( RenderWidgetHost* widget) { @@ -302,7 +159,9 @@ RenderWidgetHostViewViews::RenderWidgetHostViewViews(RenderWidgetHost* host) native_cursor_(NULL), is_showing_context_menu_(false), visually_deemphasized_(false), - touch_event_() { + touch_event_(), + text_input_type_(ui::TEXT_INPUT_TYPE_NONE), + has_composition_text_(false) { SetFocusable(true); host_->set_view(this); } @@ -312,7 +171,6 @@ RenderWidgetHostViewViews::~RenderWidgetHostViewViews() { void RenderWidgetHostViewViews::InitAsChild() { Show(); - ime_context_.reset(new IMEContextHandler(this)); } void RenderWidgetHostViewViews::InitAsPopup( @@ -337,7 +195,8 @@ void RenderWidgetHostViewViews::DidBecomeSelected() { if (tab_switch_paint_time_.is_null()) tab_switch_paint_time_ = base::TimeTicks::Now(); is_hidden_ = false; - host_->WasRestored(); + if (host_) + host_->WasRestored(); } void RenderWidgetHostViewViews::WasHidden() { @@ -351,7 +210,8 @@ void RenderWidgetHostViewViews::WasHidden() { // If we have a renderer, then inform it that we are being hidden so it can // reduce its resource utilization. - GetRenderWidgetHost()->WasHidden(); + if (host_) + host_->WasHidden(); } void RenderWidgetHostViewViews::SetSize(const gfx::Size& size) { @@ -362,7 +222,8 @@ void RenderWidgetHostViewViews::SetSize(const gfx::Size& size) { requested_size_.height() != height) { requested_size_ = gfx::Size(width, height); views::View::SetBounds(x(), y(), width, height); - host_->WasResized(); + if (host_) + host_->WasResized(); } } @@ -425,11 +286,22 @@ void RenderWidgetHostViewViews::SetIsLoading(bool is_loading) { void RenderWidgetHostViewViews::ImeUpdateTextInputState( WebKit::WebTextInputType type, const gfx::Rect& caret_rect) { - ime_context_->ImeUpdateTextInputState(type, caret_rect); + DCHECK(GetInputMethod()); + ui::TextInputType new_type = static_cast<ui::TextInputType>(type); + if (text_input_type_ != new_type) { + text_input_type_ = new_type; + GetInputMethod()->OnTextInputTypeChanged(this); + } + if (caret_bounds_ != caret_rect) { + caret_bounds_ = caret_rect; + GetInputMethod()->OnCaretBoundsChanged(this); + } } void RenderWidgetHostViewViews::ImeCancelComposition() { - ime_context_->Reset(); + DCHECK(GetInputMethod()); + GetInputMethod()->CancelComposition(this); + has_composition_text_ = false; } void RenderWidgetHostViewViews::DidUpdateBackingStore( @@ -463,7 +335,8 @@ void RenderWidgetHostViewViews::DidUpdateBackingStore( void RenderWidgetHostViewViews::RenderViewGone(base::TerminationStatus status, int error_code) { - GetRenderWidgetHost()->ViewDestroyed(); + DCHECK(host_); + host_->ViewDestroyed(); Destroy(); } @@ -508,7 +381,8 @@ BackingStore* RenderWidgetHostViewViews::AllocBackingStore( void RenderWidgetHostViewViews::SetBackground(const SkBitmap& background) { RenderWidgetHostView::SetBackground(background); - host_->Send(new ViewMsg_SetBackground(host_->routing_id(), background)); + if (host_) + host_->Send(new ViewMsg_SetBackground(host_->routing_id(), background)); } void RenderWidgetHostViewViews::CreatePluginContainer( @@ -564,76 +438,6 @@ gfx::NativeView RenderWidgetHostViewViews::GetInnerNativeView() const { return widget ? widget->window_contents() : NULL; } -void RenderWidgetHostViewViews::ForwardKeyEvent( - const views::KeyEvent& event) { - // This is how it works: - // (1) If a RawKeyDown event is an accelerator for a reserved command (see - // Browser::IsReservedCommand), then the command is executed. Otherwise, - // the event is first sent off to the renderer. The renderer is also - // notified whether the event would trigger an accelerator in the browser. - // (2) A Char event is then sent to the renderer. - // (3) If the renderer does not process the event in step (1), and the event - // triggers an accelerator, then it will ignore the event in step (2). The - // renderer also sends back notification to the browser for both steps (1) - // and (2) about whether the events were processed or not. If the event - // for (1) is not processed by the renderer, then it is processed by the - // browser, and (2) is ignored. - if (event.type() == ui::ET_KEY_PRESSED) { - NativeWebKeyboardEvent wke; - - wke.type = WebKit::WebInputEvent::RawKeyDown; - wke.windowsKeyCode = event.key_code(); - wke.setKeyIdentifierFromWindowsKeyCode(); - - int keyval = ui::GdkKeyCodeForWindowsKeyCode(event.key_code(), - event.IsShiftDown() ^ event.IsCapsLockDown()); - - wke.text[0] = wke.unmodifiedText[0] = - static_cast<unsigned short>(gdk_keyval_to_unicode(keyval)); - - // Due to a bug in GDK, gdk_keyval_to_unicode(keyval) returns 0 if keyval - // is GDK_Return. It should instead return '\r'. This is causing - // http://code.google.com/p/chromium/issues/detail?id=75779 - // Hence, the ugly hack below. - // TODO(varunjain): remove the hack when the GDK bug - // https://bugzilla.gnome.org/show_bug.cgi?id=644836 gets sorted out. - if (event.key_code() == ui::VKEY_RETURN) { - wke.text[0] = wke.unmodifiedText[0] = '\r'; - } - - wke.modifiers = WebInputEventFlagsFromViewsEvent(event); - - ForwardWebKeyboardEvent(wke); - - wke.type = WebKit::WebInputEvent::Char; - ForwardWebKeyboardEvent(wke); - } else { - NativeWebKeyboardEvent wke; - - wke.type = WebKit::WebInputEvent::KeyUp; - wke.windowsKeyCode = event.key_code(); - wke.setKeyIdentifierFromWindowsKeyCode(); - ForwardWebKeyboardEvent(wke); - } -} - -void RenderWidgetHostViewViews::ForwardWebKeyboardEvent( - const NativeWebKeyboardEvent& event) { - if (!host_) - return; - - EditCommands edit_commands; -#if 0 - // TODO(bryeung): key bindings - if (!event.skip_in_browser && - key_bindings_handler_->Match(event, &edit_commands)) { - host_->ForwardEditCommandsForNextKeyEvent(edit_commands); - } -#endif - - host_->ForwardKeyboardEvent(event); -} - std::string RenderWidgetHostViewViews::GetClassName() const { return kViewClassName; } @@ -644,8 +448,15 @@ gfx::NativeCursor RenderWidgetHostViewViews::GetCursorForPoint( } bool RenderWidgetHostViewViews::OnMousePressed(const views::MouseEvent& event) { + if (!host_) + return false; + RequestFocus(); + // Confirm existing composition text on mouse click events, to make sure + // the input caret won't be moved with an ongoing composition text. + FinishImeCompositionSession(); + // TODO(anicolao): validate event generation. WebKit::WebMouseEvent e = WebMouseEventFromViewsEvent(event); @@ -653,7 +464,7 @@ bool RenderWidgetHostViewViews::OnMousePressed(const views::MouseEvent& event) { e.type = WebKit::WebInputEvent::MouseDown; e.clickCount = 1; - GetRenderWidgetHost()->ForwardMouseEvent(e); + host_->ForwardMouseEvent(e); return true; } @@ -664,20 +475,22 @@ bool RenderWidgetHostViewViews::OnMouseDragged(const views::MouseEvent& event) { void RenderWidgetHostViewViews::OnMouseReleased( const views::MouseEvent& event) { - WebKit::WebMouseEvent e = WebMouseEventFromViewsEvent(event); + if (!host_) + return; + WebKit::WebMouseEvent e = WebMouseEventFromViewsEvent(event); e.type = WebKit::WebInputEvent::MouseUp; e.clickCount = 1; - - GetRenderWidgetHost()->ForwardMouseEvent(e); + host_->ForwardMouseEvent(e); } void RenderWidgetHostViewViews::OnMouseMoved(const views::MouseEvent& event) { - WebKit::WebMouseEvent e = WebMouseEventFromViewsEvent(event); + if (!host_) + return; + WebKit::WebMouseEvent e = WebMouseEventFromViewsEvent(event); e.type = WebKit::WebInputEvent::MouseMove; - - GetRenderWidgetHost()->ForwardMouseEvent(e); + host_->ForwardMouseEvent(e); } void RenderWidgetHostViewViews::OnMouseEntered(const views::MouseEvent& event) { @@ -690,6 +503,9 @@ void RenderWidgetHostViewViews::OnMouseExited(const views::MouseEvent& event) { views::View::TouchStatus RenderWidgetHostViewViews::OnTouchEvent( const views::TouchEvent& event) { + if (!host_) + return TOUCH_STATUS_UNKNOWN; + // Update the list of touch points first. WebKit::WebTouchPoint* point = NULL; TouchStatus status = TOUCH_STATUS_UNKNOWN; @@ -708,6 +524,11 @@ views::View::TouchStatus RenderWidgetHostViewViews::OnTouchEvent( // We also want the focus. RequestFocus(); + + // Confirm existing composition text on touch press events, to make + // sure the input caret won't be moved with an ongoing composition + // text. + FinishImeCompositionSession(); } } break; @@ -783,19 +604,25 @@ views::View::TouchStatus RenderWidgetHostViewViews::OnTouchEvent( } bool RenderWidgetHostViewViews::OnKeyPressed(const views::KeyEvent& event) { - if (!ime_context_->FilterKeyEvent(event)) - ForwardKeyEvent(event); - return TRUE; + // TODO(suzhe): Support editor key bindings. + if (!host_) + return false; + host_->ForwardKeyboardEvent(NativeWebKeyboardEvent(event)); + return true; } bool RenderWidgetHostViewViews::OnKeyReleased(const views::KeyEvent& event) { - if (!ime_context_->FilterKeyEvent(event)) - ForwardKeyEvent(event); - return TRUE; + if (!host_) + return false; + host_->ForwardKeyboardEvent(NativeWebKeyboardEvent(event)); + return true; } bool RenderWidgetHostViewViews::OnMouseWheel( const views::MouseWheelEvent& event) { + if (!host_) + return false; + WebMouseWheelEvent wmwe; InitializeWebMouseEventFromViewsEvent(event, GetMirroredPosition(), &wmwe); @@ -806,14 +633,149 @@ bool RenderWidgetHostViewViews::OnMouseWheel( wmwe.deltaY = event.offset(); wmwe.wheelTicksY = wmwe.deltaY > 0 ? 1 : -1; - GetRenderWidgetHost()->ForwardWheelEvent(wmwe); + host_->ForwardWheelEvent(wmwe); return true; } -void RenderWidgetHostViewViews::OnPaint(gfx::Canvas* canvas) { - if (is_hidden_) { +views::TextInputClient* RenderWidgetHostViewViews::GetTextInputClient() { + return this; +} + +// TextInputClient implementation --------------------------------------------- +void RenderWidgetHostViewViews::SetCompositionText( + const ui::CompositionText& composition) { + if (!host_) return; + + // ui::CompositionUnderline should be identical to + // WebKit::WebCompositionUnderline, so that we can do reinterpret_cast safely. + COMPILE_ASSERT(sizeof(ui::CompositionUnderline) == + sizeof(WebKit::WebCompositionUnderline), + ui_CompositionUnderline__WebKit_WebCompositionUnderline_diff); + + // TODO(suzhe): convert both renderer_host and renderer to use + // ui::CompositionText. + const std::vector<WebKit::WebCompositionUnderline>& underlines = + reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>( + composition.underlines); + + // TODO(suzhe): due to a bug of webkit, we can't use selection range with + // composition string. See: https://bugs.webkit.org/show_bug.cgi?id=37788 + host_->ImeSetComposition(composition.text, underlines, + composition.selection.end(), + composition.selection.end()); + + has_composition_text_ = !composition.text.empty(); +} + +void RenderWidgetHostViewViews::ConfirmCompositionText() { + if (host_ && has_composition_text_) + host_->ImeConfirmComposition(); + has_composition_text_ = false; +} + +void RenderWidgetHostViewViews::ClearCompositionText() { + if (host_ && has_composition_text_) + host_->ImeCancelComposition(); + has_composition_text_ = false; +} + +void RenderWidgetHostViewViews::InsertText(const string16& text) { + DCHECK(text_input_type_ != ui::TEXT_INPUT_TYPE_NONE); + if (host_) + host_->ImeConfirmComposition(text); + has_composition_text_ = false; +} + +void RenderWidgetHostViewViews::InsertChar(char16 ch, int flags) { + if (host_) { + NativeWebKeyboardEvent::FromViewsEvent from_views_event; + NativeWebKeyboardEvent wke(ch, flags, base::Time::Now().ToDoubleT(), + from_views_event); + host_->ForwardKeyboardEvent(wke); } +} + +ui::TextInputType RenderWidgetHostViewViews::GetTextInputType() { + return text_input_type_; +} + +gfx::Rect RenderWidgetHostViewViews::GetCaretBounds() { + return caret_bounds_; +} + +bool RenderWidgetHostViewViews::HasCompositionText() { + return has_composition_text_; +} + +bool RenderWidgetHostViewViews::GetTextRange(ui::Range* range) { + // TODO(suzhe): implement this method when fixing http://crbug.com/55130. + NOTIMPLEMENTED(); + return false; +} + +bool RenderWidgetHostViewViews::GetCompositionTextRange(ui::Range* range) { + // TODO(suzhe): implement this method when fixing http://crbug.com/55130. + NOTIMPLEMENTED(); + return false; +} + +bool RenderWidgetHostViewViews::GetSelectionRange(ui::Range* range) { + // TODO(suzhe): implement this method when fixing http://crbug.com/55130. + NOTIMPLEMENTED(); + return false; +} + +bool RenderWidgetHostViewViews::SetSelectionRange(const ui::Range& range) { + // TODO(suzhe): implement this method when fixing http://crbug.com/55130. + NOTIMPLEMENTED(); + return false; +} + +bool RenderWidgetHostViewViews::DeleteRange(const ui::Range& range) { + // TODO(suzhe): implement this method when fixing http://crbug.com/55130. + NOTIMPLEMENTED(); + return false; +} + +bool RenderWidgetHostViewViews::GetTextFromRange( + const ui::Range& range, + const base::Callback<void(const string16&)>& callback) { + // TODO(suzhe): implement this method when fixing http://crbug.com/55130. + NOTIMPLEMENTED(); + return false; +} + +void RenderWidgetHostViewViews::OnInputMethodChanged() { + if (!host_) + return; + + DCHECK(GetInputMethod()); + host_->SetInputMethodActive(GetInputMethod()->IsActive()); + + // TODO(suzhe): implement the newly added “locale” property of HTML DOM + // TextEvent. +} + +bool RenderWidgetHostViewViews::ChangeTextDirectionAndLayoutAlignment( + base::i18n::TextDirection direction) { + if (!host_) + return false; + host_->UpdateTextDirection( + direction == base::i18n::RIGHT_TO_LEFT ? + WebKit::WebTextDirectionRightToLeft : + WebKit::WebTextDirectionLeftToRight); + host_->NotifyTextDirection(); + return true; +} + +views::View* RenderWidgetHostViewViews::GetOwnerViewOfTextInputClient() { + return this; +} + +void RenderWidgetHostViewViews::OnPaint(gfx::Canvas* canvas) { + if (is_hidden_ || !host_) + return; // Paint a "hole" in the canvas so that the render of the web page is on // top of whatever else has already been painted in the views hierarchy. @@ -824,10 +786,8 @@ void RenderWidgetHostViewViews::OnPaint(gfx::Canvas* canvas) { // Don't do any painting if the GPU process is rendering directly // into the View. - RenderWidgetHost* render_widget_host = GetRenderWidgetHost(); - if (render_widget_host->is_accelerated_compositing_active()) { + if (host_->is_accelerated_compositing_active()) return; - } GdkWindow* window = GetInnerNativeView()->window; DCHECK(!about_to_validate_and_paint_); @@ -916,21 +876,30 @@ void RenderWidgetHostViewViews::Focus() { void RenderWidgetHostViewViews::Blur() { // TODO(estade): We should be clearing native focus as well, but I know of no // way to do that without focusing another widget. - host_->Blur(); + if (host_) + host_->Blur(); } void RenderWidgetHostViewViews::OnFocus() { - ime_context_->Focus(); + if (!host_) + return; + + DCHECK(GetInputMethod()); + View::OnFocus(); ShowCurrentCursor(); - GetRenderWidgetHost()->GotFocus(); + host_->GotFocus(); + host_->SetInputMethodActive(GetInputMethod()->IsActive()); } void RenderWidgetHostViewViews::OnBlur() { + if (!host_) + return; + View::OnBlur(); // If we are showing a context menu, maintain the illusion that webkit has // focus. - if (!is_showing_context_menu_ && !is_hidden_ && host_) + if (!is_showing_context_menu_ && !is_hidden_) host_->Blur(); - ime_context_->Blur(); + host_->SetInputMethodActive(false); } bool RenderWidgetHostViewViews::NeedsInputGrab() { @@ -973,6 +942,16 @@ WebKit::WebMouseEvent RenderWidgetHostViewViews::WebMouseEventFromViewsEvent( return wmevent; } +void RenderWidgetHostViewViews::FinishImeCompositionSession() { + if (!has_composition_text_) + return; + if (host_) + host_->ImeConfirmComposition(); + DCHECK(GetInputMethod()); + GetInputMethod()->CancelComposition(this); + has_composition_text_ = false; +} + // static RenderWidgetHostView* RenderWidgetHostView::GetRenderWidgetHostViewFromNativeView( diff --git a/chrome/browser/renderer_host/render_widget_host_view_views.h b/chrome/browser/renderer_host/render_widget_host_view_views.h index 203e639..014844c 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_views.h +++ b/chrome/browser/renderer_host/render_widget_host_view_views.h @@ -17,10 +17,10 @@ #include "ui/gfx/native_widget_types.h" #include "views/controls/native/native_view_host.h" #include "views/events/event.h" +#include "views/ime/text_input_client.h" #include "views/view.h" #include "webkit/glue/webcursor.h" -class IMEContextHandler; class RenderWidgetHost; struct NativeWebKeyboardEvent; @@ -28,7 +28,8 @@ struct NativeWebKeyboardEvent; // See comments in render_widget_host_view.h about this class and its members. // ----------------------------------------------------------------------------- class RenderWidgetHostViewViews : public RenderWidgetHostView, - public views::View { + public views::View, + public views::TextInputClient { public: // Internal class name. static const char kViewClassName[]; @@ -88,12 +89,6 @@ class RenderWidgetHostViewViews : public RenderWidgetHostView, // inner view. This can return NULL when it's not attached to a view. gfx::NativeView GetInnerNativeView() const; - // Forwards a keyboard event to renderer. - void ForwardKeyEvent(const views::KeyEvent& event); - - // Forwards a web keyboard event to renderer. - void ForwardWebKeyboardEvent(const NativeWebKeyboardEvent& event); - // Overridden from views::View. virtual std::string GetClassName() const OVERRIDE; virtual gfx::NativeCursor GetCursorForPoint(ui::EventType type, @@ -109,6 +104,30 @@ class RenderWidgetHostViewViews : public RenderWidgetHostView, virtual bool OnKeyPressed(const views::KeyEvent& event) OVERRIDE; virtual bool OnKeyReleased(const views::KeyEvent& event) OVERRIDE; virtual bool OnMouseWheel(const views::MouseWheelEvent& event) OVERRIDE; + virtual views::TextInputClient* GetTextInputClient() OVERRIDE; + + // Overridden from TextInputClient: + virtual void SetCompositionText( + const ui::CompositionText& composition) OVERRIDE; + virtual void ConfirmCompositionText() OVERRIDE; + virtual void ClearCompositionText() OVERRIDE; + virtual void InsertText(const string16& text) OVERRIDE; + virtual void InsertChar(char16 ch, int flags) OVERRIDE; + virtual ui::TextInputType GetTextInputType() OVERRIDE; + virtual gfx::Rect GetCaretBounds() OVERRIDE; + virtual bool HasCompositionText() OVERRIDE; + virtual bool GetTextRange(ui::Range* range) OVERRIDE; + virtual bool GetCompositionTextRange(ui::Range* range) OVERRIDE; + virtual bool GetSelectionRange(ui::Range* range) OVERRIDE; + virtual bool SetSelectionRange(const ui::Range& range) OVERRIDE; + virtual bool DeleteRange(const ui::Range& range) OVERRIDE; + virtual bool GetTextFromRange( + const ui::Range& range, + const base::Callback<void(const string16&)>& callback) OVERRIDE; + virtual void OnInputMethodChanged() OVERRIDE; + virtual bool ChangeTextDirectionAndLayoutAlignment( + base::i18n::TextDirection direction) OVERRIDE; + virtual views::View* GetOwnerViewOfTextInputClient() OVERRIDE; protected: // Overridden from RenderWidgetHostView / views::View. @@ -136,6 +155,10 @@ class RenderWidgetHostViewViews : public RenderWidgetHostView, WebKit::WebMouseEvent WebMouseEventFromViewsEvent( const views::MouseEvent& event); + // Confirm existing composition text in the webpage and ask the input method + // to cancel its ongoing composition sesstion. + void FinishImeCompositionSession(); + // The model object. RenderWidgetHost* host_; @@ -181,9 +204,14 @@ class RenderWidgetHostViewViews : public RenderWidgetHostView, // removed from the list on an ET_TOUCH_RELEASED event. WebKit::WebTouchEvent touch_event_; - // Input method context used to translating sequence of key events into other - // languages. - scoped_ptr<IMEContextHandler> ime_context_; + // The current text input type. + ui::TextInputType text_input_type_; + + // The current caret bounds. + gfx::Rect caret_bounds_; + + // Indicates if there is onging composition text. + bool has_composition_text_; DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewViews); }; diff --git a/content/common/native_web_keyboard_event.h b/content/common/native_web_keyboard_event.h index 08de652..6e94a34 100644 --- a/content/common/native_web_keyboard_event.h +++ b/content/common/native_web_keyboard_event.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// 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. @@ -7,6 +7,7 @@ #pragma once #include "base/basictypes.h" +#include "build/build_config.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" #if defined(OS_WIN) @@ -21,6 +22,12 @@ class NSEvent; typedef struct _GdkEventKey GdkEventKey; #endif +#if defined(TOOLKIT_VIEWS) +namespace views { +class KeyEvent; +} +#endif + // Owns a platform specific event; used to pass own and pass event through // platform independent code. struct NativeWebKeyboardEvent : public WebKit::WebKeyboardEvent { @@ -34,12 +41,31 @@ struct NativeWebKeyboardEvent : public WebKit::WebKeyboardEvent { int state, double time_stamp_seconds); #elif defined(TOOLKIT_USES_GTK) + // TODO(suzhe): Limit these constructors to Linux native Gtk port. + // For Linux Views port, after using RenderWidgetHostViewViews to replace + // RenderWidgetHostViewGtk, we can use constructors for TOOLKIT_VIEWS defined + // below. explicit NativeWebKeyboardEvent(const GdkEventKey* event); NativeWebKeyboardEvent(wchar_t character, int state, double time_stamp_seconds); #endif +#if defined(TOOLKIT_VIEWS) + // TODO(suzhe): remove once we get rid of Gtk from Views. + struct FromViewsEvent {}; + // These two constructors are shared between Windows and Linux Views ports. + explicit NativeWebKeyboardEvent(const views::KeyEvent& event); + // TODO(suzhe): Sadly, we need to add a meanless FromViewsEvent parameter to + // distinguish between this contructor and above Gtk one, because they use + // different modifier flags. We can remove this extra parameter as soon as we + // disable above Gtk constructor in Linux Views port. + NativeWebKeyboardEvent(uint16 character, + int flags, + double time_stamp_seconds, + FromViewsEvent); +#endif + NativeWebKeyboardEvent(const NativeWebKeyboardEvent& event); ~NativeWebKeyboardEvent(); diff --git a/content/common/native_web_keyboard_event_views.cc b/content/common/native_web_keyboard_event_views.cc new file mode 100644 index 0000000..8c6eb12 --- /dev/null +++ b/content/common/native_web_keyboard_event_views.cc @@ -0,0 +1,86 @@ +// 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 "content/common/native_web_keyboard_event.h" + +#if defined(TOOLKIT_USES_GTK) +#include <gdk/gdk.h> +#endif + +#include "base/logging.h" +#include "views/events/event.h" + +namespace { + +int ViewsFlagsToWebInputEventModifiers(int flags) { + return + (flags & ui::EF_SHIFT_DOWN ? WebKit::WebInputEvent::ShiftKey : 0) | + (flags & ui::EF_CONTROL_DOWN ? WebKit::WebInputEvent::ControlKey : 0) | + (flags & ui::EF_CAPS_LOCK_DOWN ? WebKit::WebInputEvent::CapsLockOn : 0) | + (flags & ui::EF_ALT_DOWN ? WebKit::WebInputEvent::AltKey : 0); +} + +} // namespace + +NativeWebKeyboardEvent::NativeWebKeyboardEvent( + const views::KeyEvent& event) + : skip_in_browser(false) { + DCHECK(event.type() == ui::ET_KEY_PRESSED || + event.type() == ui::ET_KEY_RELEASED); + + if (event.type() == ui::ET_KEY_PRESSED) + type = WebKit::WebInputEvent::RawKeyDown; + else + type = WebKit::WebInputEvent::KeyUp; + + modifiers = ViewsFlagsToWebInputEventModifiers(event.flags()); + timeStampSeconds = event.time_stamp().ToDoubleT(); + windowsKeyCode = event.key_code(); + nativeKeyCode = windowsKeyCode; + text[0] = event.GetCharacter(); + unmodifiedText[0] = event.GetUnmodifiedCharacter(); + setKeyIdentifierFromWindowsKeyCode(); + +#if defined(OS_WIN) + // |os_event| is a MSG struct, so we can copy it directly. + os_event = event.native_event(); +#elif defined(TOOLKIT_USES_GTK) + if (event.native_event()) { + os_event = + reinterpret_cast<GdkEventKey*>(gdk_event_copy(event.native_event())); + nativeKeyCode = os_event->keyval; + } else { + os_event = NULL; + } +#endif + +#if defined(OS_LINUX) + match_edit_command = false; +#endif +} + +NativeWebKeyboardEvent::NativeWebKeyboardEvent(uint16 character, + int flags, + double time_stamp_seconds, + FromViewsEvent) + : skip_in_browser(true) { + type = WebKit::WebInputEvent::Char; + modifiers = ViewsFlagsToWebInputEventModifiers(flags); + timeStampSeconds = time_stamp_seconds; + windowsKeyCode = character; + nativeKeyCode = character; + text[0] = character; + unmodifiedText[0] = character; + isSystemKey = (flags & ui::EF_ALT_DOWN) != 0; + +#if defined(OS_WIN) + memset(&os_event, 0, sizeof(os_event)); +#elif defined(TOOLKIT_USES_GTK) + os_event = NULL; +#endif + +#if defined(OS_LINUX) + match_edit_command = false; +#endif +} diff --git a/content/content_common.gypi b/content/content_common.gypi index 6f1c8e5..6225b90 100644 --- a/content/content_common.gypi +++ b/content/content_common.gypi @@ -220,6 +220,11 @@ '../build/linux/system.gyp:gtk', ], }], + ['toolkit_views==1', { + 'sources': [ + 'common/native_web_keyboard_event_views.cc', + ], + }], ], }, ], diff --git a/views/ime/ibus_ime_context.cc b/views/ime/ibus_ime_context.cc deleted file mode 100644 index 9c49560..0000000 --- a/views/ime/ibus_ime_context.cc +++ /dev/null @@ -1,398 +0,0 @@ -// 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 <ibus.h> - -#include "base/logging.h" -#include "base/utf_string_conversions.h" -#include "third_party/skia/include/core/SkColor.h" -#include "ui/base/gtk/gtk_signal.h" -#include "ui/base/keycodes/keyboard_code_conversion_gtk.h" -#include "ui/gfx/rect.h" -#include "views/events/event.h" -#include "views/ime/ime_context.h" - -namespace { - -class IBusIMEContext; - -struct ProcessKeyEventData { - ProcessKeyEventData(IBusIMEContext* context, - const views::KeyEvent& event) - : context(context), - event(event.type(), event.key_code(), event.flags()) { - } - - IBusIMEContext* context; - views::KeyEvent event; -}; - -// Implements IMEContext to integrate ibus input method framework -class IBusIMEContext : public views::IMEContext { - public: - explicit IBusIMEContext(views::View* view); - virtual ~IBusIMEContext(); - - // views::IMEContext implementations: - virtual void Focus(); - virtual void Blur(); - virtual void Reset(); - virtual bool FilterKeyEvent(const views::KeyEvent& event); - virtual void SetCursorLocation(const gfx::Rect& caret_rect); - virtual void SetSurrounding(const string16& text, int cursor_pos); - - private: - void CreateContext(); - void DestroyContext(); - - // Event handlers for IBusBus: - CHROMEG_CALLBACK_0(IBusIMEContext, void, OnConnected, IBusBus*); - CHROMEG_CALLBACK_0(IBusIMEContext, void, OnDisconnected, IBusBus*); - - // Event handlers for IBusIMEContext: - CHROMEG_CALLBACK_1(IBusIMEContext, void, OnCommitText, - IBusInputContext*, IBusText*); - CHROMEG_CALLBACK_3(IBusIMEContext, void, OnForwardKeyEvent, - IBusInputContext*, guint, guint, guint); - CHROMEG_CALLBACK_3(IBusIMEContext, void, OnUpdatePreeditText, - IBusInputContext*, IBusText*, guint, gboolean); - CHROMEG_CALLBACK_0(IBusIMEContext, void, OnShowPreeditText, - IBusInputContext*); - CHROMEG_CALLBACK_0(IBusIMEContext, void, OnHidePreeditText, - IBusInputContext*); - CHROMEG_CALLBACK_0(IBusIMEContext, void, OnEnable, IBusInputContext*); - CHROMEG_CALLBACK_0(IBusIMEContext, void, OnDisable, IBusInputContext*); - CHROMEG_CALLBACK_0(IBusIMEContext, void, OnDestroy, IBusInputContext*); - - static void ProcessKeyEventDone(IBusInputContext* context, - GAsyncResult* res, - ProcessKeyEventData *data); - - IBusInputContext* context_; - bool is_focused_; - gfx::Rect caret_rect_; - - static IBusBus* bus_; - - DISALLOW_COPY_AND_ASSIGN(IBusIMEContext); -}; - -IBusBus* IBusIMEContext::bus_ = NULL; - -IBusIMEContext::IBusIMEContext(views::View* view) - : views::IMEContext(view), - context_(NULL), - is_focused_(false) { - // init ibus - if (!bus_) { - ibus_init(); - bus_ = ibus_bus_new(); - } - - // connect bus signals - g_signal_connect(bus_, - "connected", - G_CALLBACK(OnConnectedThunk), - this); - g_signal_connect(bus_, - "disconnected", - G_CALLBACK(OnDisconnectedThunk), - this); - - if (ibus_bus_is_connected(bus_)) - CreateContext(); -} - -IBusIMEContext::~IBusIMEContext() { - // disconnect bus signals - g_signal_handlers_disconnect_by_func(bus_, - reinterpret_cast<gpointer>(OnConnectedThunk), this); - g_signal_handlers_disconnect_by_func(bus_, - reinterpret_cast<gpointer>(OnDisconnectedThunk), this); - - DestroyContext(); -} - -void IBusIMEContext::Focus() { - if (is_focused_) - return; - is_focused_ = true; - - if (context_) - ibus_input_context_focus_in(context_); -} - -void IBusIMEContext::Blur() { - if (!is_focused_) - return; - - is_focused_ = false; - - if (context_) - ibus_input_context_focus_out(context_); -} - -void IBusIMEContext::Reset() { - if (context_) - ibus_input_context_reset(context_); -} - -bool IBusIMEContext::FilterKeyEvent(const views::KeyEvent& event) { - guint keyval = ui::GdkKeyCodeForWindowsKeyCode(event.key_code(), - event.IsShiftDown() ^ event.IsCapsLockDown()); - - if (context_) { - guint modifiers = 0; - - if (event.type() == ui::ET_KEY_RELEASED) - modifiers |= IBUS_RELEASE_MASK; - - if (event.IsShiftDown()) - modifiers |= IBUS_SHIFT_MASK; - if (event.IsControlDown()) - modifiers |= IBUS_CONTROL_MASK; - if (event.IsAltDown()) - modifiers |= IBUS_MOD1_MASK; - if (event.IsCapsLockDown()) - modifiers |= IBUS_LOCK_MASK; - - ibus_input_context_process_key_event_async(context_, - keyval, 0, modifiers, - -1, - NULL, - reinterpret_cast<GAsyncReadyCallback>(ProcessKeyEventDone), - new ProcessKeyEventData(this, event)); - return true; - } - - return false; -} - -void IBusIMEContext::SetCursorLocation(const gfx::Rect& caret_rect) { - if (context_ && caret_rect_ != caret_rect) { - caret_rect_ = caret_rect; - ibus_input_context_set_cursor_location(context_, - caret_rect_.x(), - caret_rect_.y(), - caret_rect_.width(), - caret_rect_.height()); - } -} - -void IBusIMEContext::SetSurrounding(const string16& text, - int cursor_pos) { - // TODO(penghuang) support surrounding -} - -void IBusIMEContext::CreateContext() { - DCHECK(bus_ != NULL); - DCHECK(ibus_bus_is_connected(bus_)); - - context_ = ibus_bus_create_input_context(bus_, "chrome"); - - // connect input context signals - g_signal_connect(context_, - "commit-text", - G_CALLBACK(OnCommitTextThunk), - this); - g_signal_connect(context_, - "forward-key-event", - G_CALLBACK(OnForwardKeyEventThunk), - this); - g_signal_connect(context_, - "update-preedit-text", - G_CALLBACK(OnUpdatePreeditTextThunk), - this); - g_signal_connect(context_, - "show-preedit-text", - G_CALLBACK(OnShowPreeditTextThunk), - this); - g_signal_connect(context_, - "hide-preedit-text", - G_CALLBACK(OnHidePreeditTextThunk), - this); - g_signal_connect(context_, - "enabled", - G_CALLBACK(OnEnableThunk), - this); - g_signal_connect(context_, - "disabled", - G_CALLBACK(OnDisableThunk), - this); - g_signal_connect(context_, - "destroy", - G_CALLBACK(OnDestroyThunk), - this); - - guint32 caps = IBUS_CAP_PREEDIT_TEXT | - IBUS_CAP_FOCUS | - IBUS_CAP_SURROUNDING_TEXT; - ibus_input_context_set_capabilities(context_, caps); - - if (is_focused_) - ibus_input_context_focus_in(context_); - - ibus_input_context_set_cursor_location(context_, - caret_rect_.x(), - caret_rect_.y(), - caret_rect_.width(), - caret_rect_.height()); -} - -void IBusIMEContext::DestroyContext() { - if (!context_) - return; - - ibus_proxy_destroy(reinterpret_cast<IBusProxy *>(context_)); - - DCHECK(!context_); -} - -void IBusIMEContext::OnConnected(IBusBus *bus) { - DCHECK(bus_ == bus); - DCHECK(ibus_bus_is_connected(bus_)); - - if (context_) - DestroyContext(); - CreateContext(); -} - -void IBusIMEContext::OnDisconnected(IBusBus *bus) { -} - -void IBusIMEContext::OnCommitText(IBusInputContext *context, - IBusText* text) { - DCHECK(context_ == context); - views::IMEContext::CommitText(UTF8ToUTF16(text->text)); -} - -void IBusIMEContext::OnForwardKeyEvent(IBusInputContext *context, - guint keyval, - guint keycode, - guint state) { - DCHECK(context_ == context); - - int flags = 0; - - if (state & IBUS_LOCK_MASK) - flags |= ui::EF_CAPS_LOCK_DOWN; - if (state & IBUS_CONTROL_MASK) - flags |= ui::EF_CONTROL_DOWN; - if (state & IBUS_SHIFT_MASK) - flags |= ui::EF_SHIFT_DOWN; - if (state & IBUS_MOD1_MASK) - flags |= ui::EF_ALT_DOWN; - if (state & IBUS_BUTTON1_MASK) - flags |= ui::EF_LEFT_BUTTON_DOWN; - if (state & IBUS_BUTTON2_MASK) - flags |= ui::EF_MIDDLE_BUTTON_DOWN; - if (state & IBUS_BUTTON3_MASK) - flags |= ui::EF_RIGHT_BUTTON_DOWN; - - ForwardKeyEvent(views::KeyEvent( - state & IBUS_RELEASE_MASK ? - ui::ET_KEY_RELEASED : ui::ET_KEY_PRESSED, - ui::WindowsKeyCodeForGdkKeyCode(keyval), - flags)); -} - -void IBusIMEContext::OnUpdatePreeditText(IBusInputContext *context, - IBusText* text, - guint cursor_pos, - gboolean visible) { - DCHECK(context_ == context); - DCHECK(IBUS_IS_TEXT(text)); - - if (visible) { - views::CompositionAttributeList attributes; - IBusAttrList *attrs = text->attrs; - if (attrs) { - int i = 0; - while (1) { - IBusAttribute *attr = ibus_attr_list_get(attrs, i++); - if (!attr) - break; - if (attr->type == IBUS_ATTR_TYPE_UNDERLINE || - attr->type == IBUS_ATTR_TYPE_BACKGROUND) { - views::CompositionAttribute attribute(attr->start_index, - attr->end_index, - SK_ColorBLACK, - false); - if (attr->type == IBUS_ATTR_TYPE_BACKGROUND) { - attribute.thick = true; - } else if (attr->type == IBUS_ATTR_TYPE_UNDERLINE) { - if (attr->value == IBUS_ATTR_UNDERLINE_DOUBLE) { - attribute.thick = true; - } else if (attr->value == IBUS_ATTR_UNDERLINE_ERROR) { - attribute.color = SK_ColorRED; - } - } - attributes.push_back(attribute); - } - } - } - - SetComposition(UTF8ToUTF16(text->text), - attributes, - cursor_pos); - } else { - EndComposition(); - } -} - -void IBusIMEContext::OnShowPreeditText(IBusInputContext *context) { - DCHECK(context_ == context); -} - -void IBusIMEContext::OnHidePreeditText(IBusInputContext *context) { - DCHECK(context_ == context); - - EndComposition(); -} - -void IBusIMEContext::OnEnable(IBusInputContext *context) { - DCHECK(context_ == context); -} - -void IBusIMEContext::OnDisable(IBusInputContext *context) { - DCHECK(context_ == context); -} - -void IBusIMEContext::OnDestroy(IBusInputContext *context) { - DCHECK(context_ == context); - - g_object_unref(context_); - context_ = NULL; - - EndComposition(); -} - -void IBusIMEContext::ProcessKeyEventDone(IBusInputContext *context, - GAsyncResult* res, - ProcessKeyEventData *data) { - DCHECK(data->context->context_ == context); - - gboolean processed = FALSE; - - if (!ibus_input_context_process_key_event_async_finish(context, - res, - &processed, - NULL)) - processed = FALSE; - - if (processed == FALSE) - data->context->ForwardKeyEvent(data->event); - - delete data; -} - -} // namespace - -namespace views { - -IMEContext* IMEContext::Create(View* view) { - return new IBusIMEContext(view); -} - -} // namespace views diff --git a/views/ime/ime_context.cc b/views/ime/ime_context.cc deleted file mode 100644 index 033e3be..0000000 --- a/views/ime/ime_context.cc +++ /dev/null @@ -1,88 +0,0 @@ -// 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/ime_context.h" - -namespace { - -#ifdef USE_DUMMY_IME_CONTEXT -class DummyIMEContext : public views::IMEContext { - public: - explicit DummyIMEContext(views::View* view) - : views::IMEContext(view) {} - virtual ~DummyIMEContext() {} - - // views::IMEContext implementations: - virtual void Focus() {} - virtual void Blur() {} - virtual void Reset() {} - virtual bool FilterKeyEvent(const views::KeyEvent& event) { - return false; - } - virtual void SetCursorLocation(const gfx::Rect& caret_rect) {} - virtual void SetSurrounding(const string16& text, int cursor_pos) {} - - private: - DISALLOW_COPY_AND_ASSIGN(DummyIMEContext); -}; -#endif // USE_DUMMY_IME_CONTEXT - -} // namespace - -namespace views { - -IMEContext::IMEContext(View* view) - : view_(view) , - commit_text_listener_(NULL), - composition_listener_(NULL), - forward_key_event_listener_(NULL), - surrounding_listener_(NULL) { -} - -void IMEContext::CommitText(const string16& text) { - if (commit_text_listener_) - commit_text_listener_->OnCommitText(this, text); -} - -void IMEContext::StartComposition() { - if (composition_listener_) - composition_listener_->OnStartComposition(this); -} - -void IMEContext::EndComposition() { - if (composition_listener_) - composition_listener_->OnEndComposition(this); -} - -void IMEContext::SetComposition(const string16& text, - const CompositionAttributeList& attributes, - uint32 cursor_pos) { - if (composition_listener_) - composition_listener_->OnSetComposition(this, text, attributes, cursor_pos); -} - -void IMEContext::ForwardKeyEvent(const KeyEvent& event) { - if (forward_key_event_listener_) - forward_key_event_listener_->OnForwardKeyEvent(this, event); -} - -bool IMEContext::SetSurroundingActive(bool active) { - if (surrounding_listener_) - return surrounding_listener_->OnSetSurroundingActive(this, active); - return false; -} - -bool IMEContext::DeleteSurrounding(int offset, int nchars) { - if (surrounding_listener_) - return surrounding_listener_->OnDeleteSurrounding(this, offset, nchars); - return false; -} - -#ifdef USE_DUMMY_IME_CONTEXT -IMEContext* IMEContext::Create(View* view) { - return new DummyIMEContext(view); -} -#endif // USE_DUMMY_IME_CONTEXT - -} // namespace views diff --git a/views/ime/ime_context.h b/views/ime/ime_context.h deleted file mode 100644 index 7261b4e..0000000 --- a/views/ime/ime_context.h +++ /dev/null @@ -1,240 +0,0 @@ -// 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_IME_CONTEXT_H_ -#define VIEWS_IME_IME_CONTEXT_H_ -#pragma once - -#include <vector> - -#include "base/basictypes.h" -#include "base/string16.h" -#include "third_party/skia/include/core/SkColor.h" - -namespace gfx { -class Rect; -} - -namespace views { - -class IMEContext; -class KeyEvent; -class View; - -// Duplicate WebKit::WebCompositionUnderline -struct CompositionUnderline { - CompositionUnderline() - : start_offset(0), - end_offset(0), - color(0), - thick(false) {} - - CompositionUnderline(uint32 s, uint32 e, SkColor c, bool t) - : start_offset(s), - end_offset(e), - color(c), - thick(t) {} - - uint32 start_offset; - uint32 end_offset; - SkColor color; - bool thick; -}; - -// WebKit only supports underline attribue for composition, so we use -// CompositionUnderline as CompositionAttribute right now. -typedef struct CompositionUnderline CompositionAttribute; -typedef std::vector<CompositionAttribute> CompositionAttributeList; - -// TODO(penghuang) more attributes (background, foreground color and etc) -// class CompositionAttribute { -// public: -// enum CompositionAttributeType{ -// CAT_UNDERLINE = 1, -// CAT_FORGROUND = 2, -// CAT_BACKGRUND = 3, -// }; -// -// CompositionAttributeType GetType() const { return type_; } -// unsigned GetStartOffset() const { return start_offset_; } -// unsigned GetEndOffset() const { return end_offset_; } -// unsigned GetValue() const { return value_; } -// -// private: -// CompositionAttributeType type_; -// unsigned start_offset_; -// unsigned end_offset_; -// unsigned value_; -// }; - -// CommitTextListener is notified when a text is commited from the context. -class CommitTextListener { - public: - virtual void OnCommitText(IMEContext* sender, - const string16& text) = 0; - - protected: - virtual ~CommitTextListener() {} -}; - -// CompositionListener is notified when composition of the context is changed. -class CompositionListener { - public: - virtual void OnStartComposition(IMEContext* sender) = 0; - virtual void OnEndComposition(IMEContext* sender) = 0; - virtual void OnSetComposition(IMEContext* sender, - const string16& text, - const CompositionAttributeList& attributes, - uint32 cursor_pos) = 0; - - protected: - virtual ~CompositionListener() {} -}; - -// CompositionListener is notified when a key event is forwarded from -// the context. -class ForwardKeyEventListener { - public: - virtual void OnForwardKeyEvent(IMEContext* sender, - const KeyEvent& event) = 0; - - protected: - virtual ~ForwardKeyEventListener() {} -}; - -// SurroundingListener is notified when an input method context needs to -// manipulate the text surround the input cursor. If associated view supports -// surrounding, it should set the listener to input method context. Some input -// methods generate different result depends on the chars before or after the -// input cursor. -// -// For example: -// Some Korean input method: -// Key events Unicode char -// 1 'C' +U314a -// 2 'K' +U314f -// 3 'C' 'K' +Ucc28 -// -// In case 2 and 3, when users press 'K', the input method will check the char -// before input cursor. If the char is +U314a, it will remove the char and -// insert a new char +Ucc28. If no char before input cursor, it will insert char -// +U314f. -// -// See also OnSetSurroundingActive() and OnDeleteSurrounding(). -class SurroundingListener { - public: - // Activate or inactivate surrounding support. When surrounding is activated, - // The assoicated view should call SetSurrounding() to notify any changes of - // text surround the input cursor. - // Return true if associated view can support surrounding. - virtual bool OnSetSurroundingActive(IMEContext* sender, - bool activate) = 0; - - // Delete a picse of text surround the input cursor. - // Return true if associated view delete the surrounding text successfully. - virtual bool OnDeleteSurrounding(IMEContext* sender, - int offset, - int chars) = 0; - protected: - virtual ~SurroundingListener() {} -}; - -class IMEContext { - public: - virtual ~IMEContext() {} - - // Set associated view. - void set_view(View* view) { view_ = view; } - - // Get associated view. - const View* view() const { return view_; } - - // Set a listener to receive a callback when im context commits a text. - void set_commit_text_listener(CommitTextListener* listener) { - commit_text_listener_ = listener; - } - - // Set a listener to receive a callback when im context changes composition. - void set_composition_listener(CompositionListener* listener) { - composition_listener_ = listener; - } - - // Set a listener to receive a callback when im context forwards a key event. - void set_forward_key_event_listener(ForwardKeyEventListener* listener) { - forward_key_event_listener_ = listener; - } - - // Set a listener to receive a callback when im context need operater - // surrounding. - void set_surrounding_listener(SurroundingListener* listener) { - surrounding_listener_ = listener; - } - - // Tell the context it got/lost focus. - virtual void Focus() = 0; - virtual void Blur() = 0; - - // Reset context, context will be reset to initial state. - virtual void Reset() = 0; - - // Filter key event, returns true, if the key event is handled, - // associated widget should ignore it. - virtual bool FilterKeyEvent(const views::KeyEvent& event) = 0; - - // Set text input cursor location on screen. - virtual void SetCursorLocation(const gfx::Rect& caret_rect) = 0; - - // Set surrounding context. - virtual void SetSurrounding(const string16& text, - int cursor_pos) = 0; - - // Create an im context. - static IMEContext* Create(View* view); - - protected: - explicit IMEContext(View* view); - - // Commit text. - void CommitText(const string16& text); - - // Start compostion. - void StartComposition(); - - // End composition. - void EndComposition(); - - // Set composition. - void SetComposition(const string16& text, - const CompositionAttributeList& attributes, - uint32 cursor_pos); - - // Forward key event. - void ForwardKeyEvent(const KeyEvent& event); - - // Notify the associated view whether or not the input context needs - // surrounding. When surrounding is activated, associated view should - // call SetSurrounding() to update any changes of text arround the input - // cursor. - // Return true if associated view can support surrounding. - bool SetSurroundingActive(bool activate); - - // Delete surrouding arround cursor. Return true, if it is handled. - bool DeleteSurrounding(int offset, int nchars); - - private: - // Client view. - View* view_; - - // Listeners: - CommitTextListener* commit_text_listener_; - CompositionListener* composition_listener_; - ForwardKeyEventListener* forward_key_event_listener_; - SurroundingListener* surrounding_listener_; - - DISALLOW_COPY_AND_ASSIGN(IMEContext); -}; - -} // namespace views - -#endif // VIEWS_IME_IM_CONTEXT_H_ diff --git a/views/ime/input_method_gtk.cc b/views/ime/input_method_gtk.cc index 93445c9..023bcc6 100644 --- a/views/ime/input_method_gtk.cc +++ b/views/ime/input_method_gtk.cc @@ -213,6 +213,11 @@ void InputMethodGtk::FocusedViewWillChange() { void InputMethodGtk::FocusedViewDidChange() { UpdateContextFocusState(); + + // Force to update caret bounds, in case the View thinks that the caret + // bounds has not changed. + if (context_focused_) + OnCaretBoundsChanged(focused_view()); } void InputMethodGtk::ConfirmCompositionText() { diff --git a/views/ime/input_method_ibus.cc b/views/ime/input_method_ibus.cc new file mode 100644 index 0000000..406554e --- /dev/null +++ b/views/ime/input_method_ibus.cc @@ -0,0 +1,998 @@ +// 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/input_method_ibus.h" + +#include <ibus.h> + +#include <cstring> +#include <set> + +#if defined(TOUCH_UI) +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#endif + +#include "base/basictypes.h" +#include "base/i18n/char_iterator.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/third_party/icu/icu_utf.h" +#include "base/utf_string_conversions.h" +#include "ui/base/keycodes/keyboard_codes.h" +#include "ui/base/keycodes/keyboard_code_conversion_gtk.h" +#include "views/events/event.h" +#include "views/widget/widget.h" + +#if defined(TOUCH_UI) +#include "ui/base/keycodes/keyboard_code_conversion_x.h" +#endif + +namespace { + +// Converts ibus key state flags to Views event flags. +int ViewsFlagsFromIBusState(guint32 state) { + return (state & IBUS_LOCK_MASK ? ui::EF_CAPS_LOCK_DOWN : 0) | + (state & IBUS_CONTROL_MASK ? ui::EF_CONTROL_DOWN : 0) | + (state & IBUS_SHIFT_MASK ? ui::EF_SHIFT_DOWN : 0) | + (state & IBUS_MOD1_MASK ? ui::EF_ALT_DOWN : 0) | + (state & IBUS_BUTTON1_MASK ? ui::EF_LEFT_BUTTON_DOWN : 0) | + (state & IBUS_BUTTON2_MASK ? ui::EF_MIDDLE_BUTTON_DOWN : 0) | + (state & IBUS_BUTTON3_MASK ? ui::EF_RIGHT_BUTTON_DOWN : 0); +} + +// Converts Views event flags to ibus key state flags. +guint32 IBusStateFromViewsFlags(int flags) { + return (flags & ui::EF_CAPS_LOCK_DOWN ? IBUS_LOCK_MASK : 0) | + (flags & ui::EF_CONTROL_DOWN ? IBUS_CONTROL_MASK : 0) | + (flags & ui::EF_SHIFT_DOWN ? IBUS_SHIFT_MASK : 0) | + (flags & ui::EF_ALT_DOWN ? IBUS_MOD1_MASK : 0) | + (flags & ui::EF_LEFT_BUTTON_DOWN ? IBUS_BUTTON1_MASK : 0) | + (flags & ui::EF_MIDDLE_BUTTON_DOWN ? IBUS_BUTTON2_MASK : 0) | + (flags & ui::EF_RIGHT_BUTTON_DOWN ? IBUS_BUTTON3_MASK : 0); +} + +void ExtractCompositionTextFromIBusPreedit(IBusText* text, + guint cursor_position, + ui::CompositionText* composition) { + composition->Clear(); + composition->text = UTF8ToUTF16(text->text); + + if (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 = composition->text.length(); + base::i18n::UTF16CharIterator char_iterator(&composition->text); + do { + char16_offsets.push_back(char_iterator.array_pos()); + } while (char_iterator.Advance()); + + // The text length in Unicode characters. + guint char_length = static_cast<guint>(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)]; + + composition->selection = ui::Range(cursor_offset); + if (text->attrs) { + guint i = 0; + while(true) { + IBusAttribute* attr = ibus_attr_list_get(text->attrs, i++); + if (!attr) + break; + if (attr->type != IBUS_ATTR_TYPE_UNDERLINE && + attr->type != IBUS_ATTR_TYPE_BACKGROUND) { + continue; + } + guint start = std::min(char_length, attr->start_index); + guint end = std::min(char_length, attr->end_index); + if (start >= end) + continue; + ui::CompositionUnderline underline( + char16_offsets[start], char16_offsets[end], + SK_ColorBLACK, false /* thick */); + if (attr->type == IBUS_ATTR_TYPE_BACKGROUND) { + underline.thick = true; + // 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) { + composition->selection.set_start(underline.end_offset); + composition->selection.set_end(cursor_offset); + } else if (underline.end_offset == cursor_offset) { + composition->selection.set_start(underline.start_offset); + composition->selection.set_end(cursor_offset); + } + } else if (attr->type == IBUS_ATTR_TYPE_UNDERLINE) { + if (attr->value == IBUS_ATTR_UNDERLINE_DOUBLE) + underline.thick = true; + else if (attr->value == IBUS_ATTR_UNDERLINE_ERROR) + underline.color = SK_ColorRED; + } + composition->underlines.push_back(underline); + } + } + + // Use a black thin underline by default. + if (composition->underlines.empty()) { + composition->underlines.push_back(ui::CompositionUnderline( + 0, length, SK_ColorBLACK, false /* thick */)); + } +} + +} // namespace + +namespace views { + +// InputMethodIBus::PendingKeyEvent implementation ---------------------------- +class InputMethodIBus::PendingKeyEvent { + public: + PendingKeyEvent(InputMethodIBus* input_method, const KeyEvent& key); + ~PendingKeyEvent(); + + // Abandon this pending key event. Its result will just be discarded. + void abandon() { input_method_ = NULL; } + + InputMethodIBus* input_method() const { return input_method_; } + + // Process this pending key event after we receive its result from the input + // method. It just call through InputMethodIBus::ProcessKeyEventPostIME(). + void ProcessPostIME(bool handled); + + private: + InputMethodIBus* input_method_; + + // Complete information of a views::KeyEvent. Sadly, views::KeyEvent doesn't + // support copy. + ui::EventType type_; + int flags_; + ui::KeyboardCode key_code_; + +#if defined(TOUCH_UI) + // corresponding XEvent data of a views::KeyEvent. It's a plain struct so we + // can do bitwise copy. + XKeyEvent x_event_; +#endif + + DISALLOW_COPY_AND_ASSIGN(PendingKeyEvent); +}; + +InputMethodIBus::PendingKeyEvent::PendingKeyEvent(InputMethodIBus* input_method, + const KeyEvent& key) + : input_method_(input_method), + type_(key.type()), + flags_(key.flags()), + key_code_(key.key_code()) { + DCHECK(input_method_); +#if defined(TOUCH_UI) + if (key.native_event_2()) + x_event_ = *reinterpret_cast<XKeyEvent*>(key.native_event_2()); + else + memset(&x_event_, 0, sizeof(x_event_)); +#endif +} + +InputMethodIBus::PendingKeyEvent::~PendingKeyEvent() { + if (input_method_) + input_method_->FinishPendingKeyEvent(this); +} + +void InputMethodIBus::PendingKeyEvent::ProcessPostIME(bool handled) { + if (!input_method_) + return; + +#if defined(TOUCH_UI) + 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); + return; + } +#endif + KeyEvent key(type_, key_code_, flags_); + input_method_->ProcessKeyEventPostIME(key, handled); +} + +// InputMethodIBus::PendingCreateICRequest implementation --------------------- +class InputMethodIBus::PendingCreateICRequest { + public: + PendingCreateICRequest(InputMethodIBus* input_method, + PendingCreateICRequest** request_ptr, + bool fake); + ~PendingCreateICRequest(); + + // Abandon this pending key event. Its result will just be discarded. + void abandon() { + input_method_ = NULL; + request_ptr_ = NULL; + } + + // Stores the result input context to |input_method_|, or abandon it if + // |input_method_| is NULL. + void StoreOrAbandonInputContext(IBusInputContext* ic); + + private: + InputMethodIBus* input_method_; + PendingCreateICRequest** request_ptr_; + bool fake_; +}; + +InputMethodIBus::PendingCreateICRequest::PendingCreateICRequest( + InputMethodIBus* input_method, + PendingCreateICRequest** request_ptr, + bool fake) + : input_method_(input_method), + request_ptr_(request_ptr), + fake_(fake) { +} + +InputMethodIBus::PendingCreateICRequest::~PendingCreateICRequest() { + if (request_ptr_) { + DCHECK_EQ(*request_ptr_, this); + *request_ptr_ = NULL; + } +} + +void InputMethodIBus::PendingCreateICRequest::StoreOrAbandonInputContext( + IBusInputContext* ic) { + if (input_method_) { + input_method_->SetContext(ic, fake_); + } else { + // ibus_proxy_destroy() will not really release the object, we still need + // to call g_object_unref() explicitly. + ibus_proxy_destroy(reinterpret_cast<IBusProxy *>(ic)); + g_object_unref(ic); + } +} + +// InputMethodIBus implementation --------------------------------------------- +InputMethodIBus::InputMethodIBus(internal::InputMethodDelegate* delegate) + : context_(NULL), + fake_context_(NULL), + pending_create_ic_request_(NULL), + pending_create_fake_ic_request_(NULL), + context_focused_(false), + composing_text_(false), + composition_changed_(false), + suppress_next_result_(false), + enabled_(false) { + set_delegate(delegate); +} + +InputMethodIBus::~InputMethodIBus() { + AbandonAllPendingKeyEvents(); + DestroyContext(); + + // Disconnect bus signals + g_signal_handlers_disconnect_by_func( + GetIBus(), reinterpret_cast<gpointer>(OnIBusConnectedThunk), this); + g_signal_handlers_disconnect_by_func( + GetIBus(), reinterpret_cast<gpointer>(OnIBusDisconnectedThunk), this); +} + +void InputMethodIBus::OnFocus() { + DCHECK(!widget_focused()); + InputMethodBase::OnFocus(); + UpdateContextFocusState(); +} + +void InputMethodIBus::OnBlur() { + DCHECK(widget_focused()); + ConfirmCompositionText(); + InputMethodBase::OnBlur(); + UpdateContextFocusState(); +} + +void InputMethodIBus::Init(Widget* widget) { + InputMethodBase::Init(widget); + + // Initializes the connection to ibus daemon. It may happen asynchronously, + // and as soon as the connection is established, the |context_| will be + // created automatically. + IBusBus* bus = GetIBus(); + + // connect bus signals + g_signal_connect(bus, "connected", + G_CALLBACK(OnIBusConnectedThunk), this); + g_signal_connect(bus, "disconnected", + G_CALLBACK(OnIBusDisconnectedThunk), this); + + // Creates the |context_| if the connection is already established. In such + // case, we will not get "connected" signal. + if (ibus_bus_is_connected(bus)) + CreateContext(); +} + +void InputMethodIBus::DispatchKeyEvent(const KeyEvent& key) { + DCHECK(key.type() == ui::ET_KEY_PRESSED || key.type() == ui::ET_KEY_RELEASED); + DCHECK(widget_focused()); + + // 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, + // 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_ || fake_context_) || + GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD) { + if (key.type() == ui::ET_KEY_PRESSED) + ProcessUnfilteredKeyPressEvent(key); + 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); + + // Note: + // 1. We currently set timeout to -1, because ibus doesn't have a mechanism to + // associate input method results to corresponding key event, thus there is + // actually no way to abandon results generated by a specific key event. So we + // actually cannot abandon a specific key event and its result but accept + // following key events and their results. So a timeout to abandon a key event + // will not work. + // 2. We set GCancellable to NULL, because the operation of cancelling a async + // request also happens asynchronously, thus it's actually useless to us. + // + // The fundemental problem of ibus' async API is: it uses Glib's GIO API to + // realize async communication, but in fact, GIO API is specially designed for + // concurrent tasks, though it supports async communication as well, the API + // is much more complicated than an ordinary message based async communication + // API (such as Chrome's IPC). + // Thus it's very complicated, if not impossible, to implement a client that + // fully utilize asynchronous communication without potential problem. + ibus_input_context_process_key_event_async( + context_focused_ ? context_ : fake_context_, + ibus_keyval, ibus_keycode, ibus_state, -1, NULL, + reinterpret_cast<GAsyncReadyCallback>(ProcessKeyEventDone), + pending_key); + + // 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; +} + +void InputMethodIBus::OnTextInputTypeChanged(View* view) { + if (context_ && IsViewFocused(view)) { + ResetContext(); + UpdateContextFocusState(); + } +} + +void InputMethodIBus::OnCaretBoundsChanged(View* view) { + if (!context_focused_ || !IsViewFocused(view)) + return; + + // The current text input type should not be NONE if |context_| is focused. + DCHECK(!IsTextInputTypeNone()); + + gfx::Rect rect = GetTextInputClient()->GetCaretBounds(); + gfx::Point origin = rect.origin(); + gfx::Point end = gfx::Point(rect.right(), rect.bottom()); + + // We need to convert the origin and end points separately, in case the View + // is scaled. + View::ConvertPointToScreen(view, &origin); + View::ConvertPointToScreen(view, &end); + + // This function runs asynchronously. + ibus_input_context_set_cursor_location( + context_, origin.x(), origin.y(), + end.x() - origin.x(), end.y() - origin.y()); +} + +void InputMethodIBus::CancelComposition(View* view) { + if (context_focused_ && IsViewFocused(view)) + ResetContext(); +} + +std::string InputMethodIBus::GetInputLocale() { + // Not supported. + return std::string(""); +} + +base::i18n::TextDirection InputMethodIBus::GetInputTextDirection() { + // Not supported. + return base::i18n::UNKNOWN_DIRECTION; +} + +bool InputMethodIBus::IsActive() { + return enabled_; +} + +void InputMethodIBus::FocusedViewWillChange() { + ConfirmCompositionText(); +} + +void InputMethodIBus::FocusedViewDidChange() { + UpdateContextFocusState(); + + // Force to update caret bounds, in case the View thinks that the caret + // bounds has not changed. + if (context_focused_) + OnCaretBoundsChanged(focused_view()); +} + +void InputMethodIBus::CreateContext() { + DCHECK(!context_); + DCHECK(!fake_context_); + DCHECK(GetIBus()); + DCHECK(ibus_bus_is_connected(GetIBus())); + DCHECK(!pending_create_ic_request_); + DCHECK(!pending_create_fake_ic_request_); + + // Creates the input context asynchronously. + pending_create_ic_request_ = new PendingCreateICRequest( + this, &pending_create_ic_request_, false /* fake */); + ibus_bus_create_input_context_async( + GetIBus(), "chrome", -1, NULL, + reinterpret_cast<GAsyncReadyCallback>(CreateInputContextDone), + pending_create_ic_request_); + + // Creates the fake input context asynchronously. ibus will match the "fake" + // prefix in the application name to do magic thing. + pending_create_fake_ic_request_ = new PendingCreateICRequest( + this, &pending_create_fake_ic_request_, true /* fake */); + ibus_bus_create_input_context_async( + GetIBus(), "fake-chrome", -1, NULL, + reinterpret_cast<GAsyncReadyCallback>(CreateInputContextDone), + pending_create_fake_ic_request_); +} + +void InputMethodIBus::SetContext(IBusInputContext* ic, bool fake) { + DCHECK(ic); + if (fake) { + DCHECK(!fake_context_); + fake_context_ = ic; + + // We only need to care about "destroy" signal of the fake context, + // as it will not generate any input method result. + g_signal_connect(ic, "destroy", G_CALLBACK(OnFakeDestroyThunk), this); + + ibus_input_context_set_capabilities(ic, IBUS_CAP_FOCUS); + UpdateFakeContextFocusState(); + return; + } + + DCHECK(!context_); + context_ = ic; + + // connect input context signals + g_signal_connect(ic, "commit-text", + G_CALLBACK(OnCommitTextThunk), this); + g_signal_connect(ic, "forward-key-event", + G_CALLBACK(OnForwardKeyEventThunk), this); + g_signal_connect(ic, "update-preedit-text", + G_CALLBACK(OnUpdatePreeditTextThunk), this); + g_signal_connect(ic, "show-preedit-text", + G_CALLBACK(OnShowPreeditTextThunk), this); + g_signal_connect(ic, "hide-preedit-text", + G_CALLBACK(OnHidePreeditTextThunk), this); + g_signal_connect(ic, "enabled", + G_CALLBACK(OnEnableThunk), this); + g_signal_connect(ic, "disabled", + G_CALLBACK(OnDisableThunk), this); + g_signal_connect(ic, "destroy", + G_CALLBACK(OnDestroyThunk), this); + + // TODO(suzhe): support surrounding text. + guint32 caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS; + ibus_input_context_set_capabilities(ic, caps); + + // Sadly, we will not receive "enabled" signal at all. So just assume the + // input context is enabled by default. + enabled_ = true; + UpdateContextFocusState(); + OnInputMethodChanged(); +} + +void InputMethodIBus::DestroyContext() { + if (pending_create_ic_request_) { + DCHECK(!context_); + // |pending_create_ic_request_| will be deleted in CreateInputContextDone(). + pending_create_ic_request_->abandon(); + pending_create_ic_request_ = NULL; + } else if (context_) { + // ibus_proxy_destroy() will not really release the resource of |context_| + // object. We still need to handle "destroy" signal and call + // g_object_unref() there. + ibus_proxy_destroy(reinterpret_cast<IBusProxy *>(context_)); + DCHECK(!context_); + } + + if (pending_create_fake_ic_request_) { + DCHECK(!fake_context_); + // |pending_create_fake_ic_request_| will be deleted in + // CreateInputContextDone(). + pending_create_fake_ic_request_->abandon(); + pending_create_fake_ic_request_ = NULL; + } else if (fake_context_) { + ibus_proxy_destroy(reinterpret_cast<IBusProxy *>(fake_context_)); + DCHECK(!fake_context_); + } +} + +void InputMethodIBus::ConfirmCompositionText() { + TextInputClient* client = GetTextInputClient(); + if (client && client->HasCompositionText()) + client->ConfirmCompositionText(); + + ResetContext(); +} + +void InputMethodIBus::ResetContext() { + if (!context_focused_ || !GetTextInputClient()) + return; + + DCHECK(widget_focused()); + DCHECK(focused_view()); + + // 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. + ibus_input_context_reset(context_); + + // We don't need to reset |fake_context_|. +} + +void InputMethodIBus::UpdateContextFocusState() { + if (!context_) { + context_focused_ = false; + UpdateFakeContextFocusState(); + return; + } + + const bool old_context_focused = context_focused_; + // Use switch here in case we are going to add more text input types. + switch (GetTextInputType()) { + case ui::TEXT_INPUT_TYPE_NONE: + case ui::TEXT_INPUT_TYPE_PASSWORD: + context_focused_ = false; + break; + default: + context_focused_ = true; + break; + } + + // We only focus in |context_| when the focus is in a normal textfield. + // ibus_input_context_focus_{in|out}() run asynchronously. + if (old_context_focused && !context_focused_) + ibus_input_context_focus_out(context_); + else if (!old_context_focused && context_focused_) + ibus_input_context_focus_in(context_); + + UpdateFakeContextFocusState(); +} + +void InputMethodIBus::UpdateFakeContextFocusState() { + if (!fake_context_) + return; + + if (widget_focused() && !context_focused_ && + GetTextInputType() != ui::TEXT_INPUT_TYPE_PASSWORD) { + // We disable input method in password fields, so it makes no sense to allow + // switching input method. + ibus_input_context_focus_in(fake_context_); + } else { + ibus_input_context_focus_out(fake_context_); + } +} + +void InputMethodIBus::ProcessKeyEventPostIME(const KeyEvent& key, + 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. + if (!GetTextInputClient()) { + DispatchKeyEventPostIME(key); + return; + } + + const View* old_focused_view = focused_view(); + + // Same reason as above DCHECK. + DCHECK(old_focused_view); + + if (key.type() == ui::ET_KEY_PRESSED && handled) + ProcessFilteredKeyPressEvent(key); + + // In case the focus was changed by the key event. The |context_| should have + // been reset when the focused view changed. + if (old_focused_view != focused_view()) + return; + + if (HasInputMethodResult()) + ProcessInputMethodResult(key, handled); + + // In case the focus was changed when sending input method results to the + // focused View. + if (old_focused_view != focused_view()) + return; + + if (key.type() == ui::ET_KEY_PRESSED && !handled) + ProcessUnfilteredKeyPressEvent(key); + else if (key.type() == ui::ET_KEY_RELEASED) + DispatchKeyEventPostIME(key); +} + +void InputMethodIBus::ProcessFilteredKeyPressEvent(const KeyEvent& key) { + if (NeedInsertChar()) { + DispatchKeyEventPostIME(key); + } else { + KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, key.flags()); + DispatchKeyEventPostIME(key); + } +} + +void InputMethodIBus::ProcessUnfilteredKeyPressEvent(const KeyEvent& key) { + const View* old_focused_view = focused_view(); + DispatchKeyEventPostIME(key); + + // We shouldn't dispatch the character anymore if the key event caused focus + // change. + 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(); + TextInputClient* client = GetTextInputClient(); + if (ch && client) + client->InsertChar(ch, key.flags()); +} + +void InputMethodIBus::ProcessInputMethodResult(const KeyEvent& key, + bool filtered) { + TextInputClient* client = GetTextInputClient(); + DCHECK(client); + + if (result_text_.length()) { + if (filtered && NeedInsertChar()) { + for (string16::const_iterator i = result_text_.begin(); + i != result_text_.end(); ++i) { + client->InsertChar(*i, key.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::SendFakeProcessKeyEvent(bool pressed) const { + KeyEvent key(pressed ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED, + ui::VKEY_PROCESSKEY, 0); + 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)); + + // |pending_key| will be deleted in ProcessKeyEventDone(). + pending_key_events_.erase(pending_key); +} + +void InputMethodIBus::AbandonAllPendingKeyEvents() { + for (std::set<PendingKeyEvent*>::iterator i = pending_key_events_.begin(); + i != pending_key_events_.end(); ++i) { + // The object will be deleted in ProcessKeyEventDone(). + (*i)->abandon(); + } + pending_key_events_.clear(); +} + +void InputMethodIBus::OnCommitText(IBusInputContext* context, IBusText* text) { + DCHECK_EQ(context_, context); + if (suppress_next_result_ || !text || !text->text) + return; + + // We need to receive input method result even if the text input type is + // ui::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; + + string16 utf16_text(UTF8ToUTF16(text->text)); + + // 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()) { + SendFakeProcessKeyEvent(true); + GetTextInputClient()->InsertText(utf16_text); + SendFakeProcessKeyEvent(false); + result_text_.clear(); + } +} + +void InputMethodIBus::OnForwardKeyEvent(IBusInputContext* context, + guint keyval, + guint keycode, + guint state) { + DCHECK_EQ(context_, context); + + ui::KeyboardCode key_code = ui::VKEY_UNKNOWN; +#if defined(TOUCH_UI) + key_code = ui::KeyboardCodeFromXKeysym(keyval); +#elif defined(TOOLKIT_USES_GTK) + key_code = ui::WindowsKeyCodeForGdkKeyCode(keyval); +#endif + + if (!key_code) + return; + + KeyEvent key(state & IBUS_RELEASE_MASK ? + ui::ET_KEY_RELEASED : ui::ET_KEY_PRESSED, + key_code, ViewsFlagsFromIBusState(state)); + + // It is not clear when the input method will forward us a fake key event. + // If there is a pending key event, then we may already received some input + // method results, so we dispatch this fake key event directly rather than + // calling ProcessKeyEventPostIME(), which will clear pending input method + // results. + if (key.type() == ui::ET_KEY_PRESSED) + ProcessUnfilteredKeyPressEvent(key); + else + DispatchKeyEventPostIME(key); +} + +void InputMethodIBus::OnShowPreeditText(IBusInputContext* context) { + DCHECK_EQ(context_, context); + if (suppress_next_result_ || IsTextInputTypeNone()) + return; + + composing_text_ = true; +} + +void InputMethodIBus::OnUpdatePreeditText(IBusInputContext* context, + IBusText* text, + guint cursor_pos, + gboolean visible) { + DCHECK_EQ(context_, context); + if (suppress_next_result_ || IsTextInputTypeNone()) + return; + + // |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) { + OnHidePreeditText(context); + return; + } + + ExtractCompositionTextFromIBusPreedit(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()) { + SendFakeProcessKeyEvent(true); + GetTextInputClient()->SetCompositionText(composition_); + SendFakeProcessKeyEvent(false); + composition_changed_ = false; + composition_.Clear(); + } +} + +void InputMethodIBus::OnHidePreeditText(IBusInputContext* context) { + DCHECK_EQ(context_, context); + 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::OnEnable(IBusInputContext* context) { + DCHECK_EQ(context_, context); + enabled_ = true; + OnInputMethodChanged(); +} + +void InputMethodIBus::OnDisable(IBusInputContext* context) { + DCHECK_EQ(context_, context); + enabled_ = false; + OnInputMethodChanged(); +} + +void InputMethodIBus::OnDestroy(IBusInputContext* context) { + DCHECK_EQ(context_, context); + g_object_unref(context_); + context_ = NULL; + context_focused_ = false; + + ConfirmCompositionText(); + + // We are dead, so we need to ask the client to stop relying on us. + // We cannot do it in DestroyContext(), because OnDestroy() may be called + // automatically. + enabled_ = false; + OnInputMethodChanged(); +} + +void InputMethodIBus::OnFakeDestroy(IBusInputContext* context) { + DCHECK_EQ(fake_context_, context); + g_object_unref(fake_context_); + fake_context_ = NULL; +} + +void InputMethodIBus::OnIBusConnected(IBusBus* bus) { + DCHECK_EQ(GetIBus(), bus); + DCHECK(ibus_bus_is_connected(bus)); + + DestroyContext(); + CreateContext(); +} + +void InputMethodIBus::OnIBusDisconnected(IBusBus* bus) { + DCHECK_EQ(GetIBus(), bus); + + // TODO(suzhe): Make sure if we really do not need to handle this signal. + // And I'm actually wondering if ibus-daemon will release the resource of the + // |context_| correctly when the connection is lost. +} + +// static +IBusBus* InputMethodIBus::GetIBus() { + // Everything happens in UI thread, so we do not need to care about + // synchronization issue. + static IBusBus* ibus = NULL; + + if (!ibus) { + ibus_init(); + ibus = ibus_bus_new(); + DCHECK(ibus); + } + return ibus; +} + +// static +void InputMethodIBus::ProcessKeyEventDone(IBusInputContext* context, + GAsyncResult* res, + PendingKeyEvent* data) { + DCHECK(data); + DCHECK(!data->input_method() || + data->input_method()->context_ == context || + data->input_method()->fake_context_ == context); + + gboolean handled = FALSE; + if (!ibus_input_context_process_key_event_async_finish(context, res, + &handled, NULL)) { + handled = FALSE; + } + + data->ProcessPostIME(handled); + delete data; +} + +// static +void InputMethodIBus::CreateInputContextDone(IBusBus* bus, + GAsyncResult* res, + PendingCreateICRequest* data) { + DCHECK_EQ(GetIBus(), bus); + DCHECK(data); + IBusInputContext* ic = + ibus_bus_create_input_context_async_finish(bus, res, NULL); + if (ic) + data->StoreOrAbandonInputContext(ic); + delete data; +} + +} // namespace views diff --git a/views/ime/input_method_ibus.h b/views/ime/input_method_ibus.h new file mode 100644 index 0000000..ac1dcbb --- /dev/null +++ b/views/ime/input_method_ibus.h @@ -0,0 +1,204 @@ +// 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_INPUT_METHOD_IBUS_H_ +#define VIEWS_IME_INPUT_METHOD_IBUS_H_ +#pragma once + +#include <set> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/scoped_ptr.h" +#include "ui/base/gtk/gtk_signal.h" +#include "views/events/event.h" +#include "views/ime/input_method_base.h" +#include "views/view.h" + +// Forward declarations, so that we don't need to include ibus.h in this file. +typedef struct _IBusBus IBusBus; +typedef struct _IBusInputContext IBusInputContext; +typedef struct _IBusText IBusText; + +namespace views { + +// An InputMethod implementation based on IBus. +class InputMethodIBus : public InputMethodBase { + public: + explicit InputMethodIBus(internal::InputMethodDelegate* delegate); + virtual ~InputMethodIBus(); + + // Overridden from InputMethod: + virtual void Init(Widget* widget) OVERRIDE; + virtual void OnFocus() OVERRIDE; + virtual void OnBlur() OVERRIDE; + virtual void DispatchKeyEvent(const KeyEvent& key) OVERRIDE; + virtual void OnTextInputTypeChanged(View* view) OVERRIDE; + virtual void OnCaretBoundsChanged(View* view) OVERRIDE; + virtual void CancelComposition(View* view) OVERRIDE; + virtual std::string GetInputLocale() OVERRIDE; + virtual base::i18n::TextDirection GetInputTextDirection() OVERRIDE; + virtual bool IsActive() OVERRIDE; + + private: + // A class to hold all data related to a key event being processed by the + // input method but still has no result back yet. + class PendingKeyEvent; + + // A class to hold information of a pending request for creating an ibus input + // context. + class PendingCreateICRequest; + + // Overridden from InputMethodBase: + virtual void FocusedViewWillChange() OVERRIDE; + virtual void FocusedViewDidChange() OVERRIDE; + + // Creates |context_| instance asynchronously. + void CreateContext(); + + // Sets |context_| or |fake_context_| and hooks necessary signals. + void SetContext(IBusInputContext* ic, bool fake); + + // Destroys |context_| instance. + void DestroyContext(); + + // Asks the client to confirm current composition text. + void ConfirmCompositionText(); + + // Resets |context_| and abandon all pending results and key events. + void ResetContext(); + + // Checks the availability of focused text input client and update focus state + // of |context_| and |context_simple_| accordingly. + void UpdateContextFocusState(); + + // Updates focus state of |fake_context_| accordingly. + void UpdateFakeContextFocusState(); + + // Process a key returned from the input method. + void ProcessKeyEventPostIME(const KeyEvent& key, 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); + + // Sends input method result caused by the given key event to the focused text + // input client. + void ProcessInputMethodResult(const KeyEvent& key, bool filtered); + + // Checks if the pending input method result needs inserting into the focused + // text input client as a single character. + bool NeedInsertChar() const; + + // Checks if there is pending input method result. + bool HasInputMethodResult() const; + + // Fabricates a key event with VKEY_PROCESSKEY key code and dispatches it to + // 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); + + // Abandons all pending key events. It usually happends when we lose keyboard + // focus, the text input type is changed or we are destroyed. + void AbandonAllPendingKeyEvents(); + + // Event handlers for IBusInputContext: + CHROMEG_CALLBACK_1(InputMethodIBus, void, OnCommitText, + IBusInputContext*, IBusText*); + CHROMEG_CALLBACK_3(InputMethodIBus, void, OnForwardKeyEvent, + IBusInputContext*, guint, guint, guint); + CHROMEG_CALLBACK_0(InputMethodIBus, void, OnShowPreeditText, + IBusInputContext*); + CHROMEG_CALLBACK_3(InputMethodIBus, void, OnUpdatePreeditText, + IBusInputContext*, IBusText*, guint, gboolean); + CHROMEG_CALLBACK_0(InputMethodIBus, void, OnHidePreeditText, + IBusInputContext*); + CHROMEG_CALLBACK_0(InputMethodIBus, void, OnEnable, IBusInputContext*); + CHROMEG_CALLBACK_0(InputMethodIBus, void, OnDisable, IBusInputContext*); + CHROMEG_CALLBACK_0(InputMethodIBus, void, OnDestroy, IBusInputContext*); + CHROMEG_CALLBACK_0(InputMethodIBus, void, OnFakeDestroy, IBusInputContext*); + + // Event handlers for IBusBus: + CHROMEG_CALLBACK_0(InputMethodIBus, void, OnIBusConnected, IBusBus*); + CHROMEG_CALLBACK_0(InputMethodIBus, void, OnIBusDisconnected, IBusBus*); + + // Returns the global IBusBus instance. + static IBusBus* GetIBus(); + + // Callback function for ibus_input_context_process_key_event_async(). + static void ProcessKeyEventDone(IBusInputContext* context, + GAsyncResult* res, + PendingKeyEvent* data); + + // Callback function for ibus_bus_create_input_context_async(). + static void CreateInputContextDone(IBusBus* bus, + GAsyncResult* res, + PendingCreateICRequest* data); + + // The input context for actual text input. + IBusInputContext* context_; + + // The "fake" input context for hotkey handling, it is only used when there + // is no focused view, or it doesn't support text input. + IBusInputContext* fake_context_; + + // All pending key events. Note: we do not own these object, we just save + // pointers to these object so that we can abandon them when necessary. + // They will be deleted in ProcessKeyEventDone(). + std::set<PendingKeyEvent*> pending_key_events_; + + // The pending request for creating the |context_| instance. We need to keep + // this pointer so that we can receive or abandon the result. + PendingCreateICRequest* pending_create_ic_request_; + + // The pending request for creating the |context_| instance. We need to keep + // this pointer so that we can receive or abandon the result. + PendingCreateICRequest* pending_create_fake_ic_request_; + + // Pending composition text generated by the current pending key event. + // It'll be sent to the focused text input client as soon as we receive the + // processing result of the pending key event. + ui::CompositionText composition_; + + // Pending result text generated by the current pending key event. + // It'll be sent to the focused text input client as soon as we receive the + // processing result of the pending key event. + string16 result_text_; + + // Indicates if |context_| and |context_simple_| are focused or not. + bool context_focused_; + + // Indicates if there is an ongoing composition text. + bool composing_text_; + + // Indicates if the composition text is changed or deleted. + bool composition_changed_; + + // If it's true then all input method result received before the next key + // event will be discarded. + bool suppress_next_result_; + + // Indicates if |context_| is enabled. + bool enabled_; + + DISALLOW_COPY_AND_ASSIGN(InputMethodIBus); +}; + +} // namespace views + +#endif // VIEWS_IME_INPUT_METHOD_IBUS_H_ diff --git a/views/views.gyp b/views/views.gyp index 26e4d6d..c7fac12 100644 --- a/views/views.gyp +++ b/views/views.gyp @@ -285,15 +285,14 @@ 'focus/focus_util_win.h', 'focus/view_storage.cc', 'focus/view_storage.h', - 'ime/ibus_ime_context.cc', - 'ime/ime_context.cc', - 'ime/ime_context.h', 'ime/input_method.h', 'ime/input_method_delegate.h', 'ime/input_method_base.cc', 'ime/input_method_base.h', 'ime/input_method_gtk.cc', 'ime/input_method_gtk.h', + 'ime/input_method_ibus.cc', + 'ime/input_method_ibus.h', 'ime/input_method_win.cc', 'ime/input_method_win.h', 'ime/text_input_client.h', @@ -446,18 +445,25 @@ ['exclude', 'touchui/touch_factory.h'], ], }], - ['"<!@(<(pkg-config) --atleast-version=1.3.99 ibus-1.0 || echo $?)"!=""', { + # TODO(suzhe): We should not check ibus version here. Instead, we + # should use a variable to control whether or not to use ibus. + ['"<!@(<(pkg-config) --atleast-version=1.3.99 ibus-1.0 || echo $?)"==""', { + 'defines': ['HAVE_IBUS=1'], 'sources/': [ - ['exclude', 'ime/ibus_ime_context.cc'], + ['exclude', 'ime/input_method_gtk.cc'], + ['exclude', 'ime/input_method_gtk.h'], + ], + }, { # else: no ibus + 'sources/': [ + ['exclude', 'ime/input_method_ibus.cc'], + ['exclude', 'ime/input_method_ibus.h'], ], - 'defines': ['USE_DUMMY_IME_CONTEXT'], }], ], }, { # else: touchui != 1 'sources!': [ - 'ime/ibus_ime_context.cc', - 'ime/ime_context.cc', - 'ime/ime_context.h', + 'ime/input_method_ibus.cc', + 'ime/input_method_ibus.h', ], }], ['OS=="win"', { diff --git a/views/widget/widget_gtk.cc b/views/widget/widget_gtk.cc index da998b4..9de4fa1 100644 --- a/views/widget/widget_gtk.cc +++ b/views/widget/widget_gtk.cc @@ -44,6 +44,12 @@ #endif #endif +#if defined(TOUCH_UI) && defined(HAVE_IBUS) +#include "views/ime/input_method_ibus.h" +#else +#include "views/ime/input_method_gtk.h" +#endif + using ui::OSExchangeData; using ui::OSExchangeDataProviderGtk; using ui::ActiveWindowWatcherX; @@ -505,12 +511,13 @@ void WidgetGtk::Init(GtkWidget* parent, // already created at this point. // TODO(suzhe): Always enable input method when we start to use // RenderWidgetHostViewViews in normal ChromeOS. -#if !defined(TOUCH_UI) - if (type_ != TYPE_CHILD && NativeTextfieldViews::IsTextfieldViewsEnabled()) { -#else +#if defined(TOUCH_UI) && defined(HAVE_IBUS) if (type_ != TYPE_CHILD) { -#endif + input_method_.reset(new InputMethodIBus(this)); +#else + if (type_ != TYPE_CHILD && NativeTextfieldViews::IsTextfieldViewsEnabled()) { input_method_.reset(new InputMethodGtk(this)); +#endif input_method_->Init(GetWidget()); } diff --git a/views/widget/widget_gtk.h b/views/widget/widget_gtk.h index 4028997..3217900 100644 --- a/views/widget/widget_gtk.h +++ b/views/widget/widget_gtk.h @@ -14,7 +14,6 @@ #include "ui/gfx/size.h" #include "views/focus/focus_manager.h" #include "views/ime/input_method_delegate.h" -#include "views/ime/input_method_gtk.h" #include "views/widget/native_widget.h" #include "views/widget/widget.h" @@ -32,6 +31,7 @@ using ui::OSExchangeDataProviderGtk; namespace views { class DropTargetGtk; +class InputMethod; class TooltipManagerGtk; class View; class WindowGtk; |