summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsuzhe@google.com <suzhe@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-01 22:59:32 +0000
committersuzhe@google.com <suzhe@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-01 22:59:32 +0000
commit4b99402b089ba598ae26c82d99c3c3d2fe0cd574 (patch)
treef4fe9b7a92bc90b364c40b901919fee163b7061b
parent4b8b6d844d019a612a02274d298e3dcffc42276b (diff)
downloadchromium_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.cc4
-rw-r--r--chrome/browser/renderer_host/render_widget_host_view_views.cc473
-rw-r--r--chrome/browser/renderer_host/render_widget_host_view_views.h50
-rw-r--r--content/common/native_web_keyboard_event.h28
-rw-r--r--content/common/native_web_keyboard_event_views.cc86
-rw-r--r--content/content_common.gypi5
-rw-r--r--views/ime/ibus_ime_context.cc398
-rw-r--r--views/ime/ime_context.cc88
-rw-r--r--views/ime/ime_context.h240
-rw-r--r--views/ime/input_method_gtk.cc5
-rw-r--r--views/ime/input_method_ibus.cc998
-rw-r--r--views/ime/input_method_ibus.h204
-rw-r--r--views/views.gyp24
-rw-r--r--views/widget/widget_gtk.cc15
-rw-r--r--views/widget/widget_gtk.h2
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;