summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ui/base/ime/composition_text.h18
-rw-r--r--ui/base/ime/composition_underline.h13
-rw-r--r--ui/base/ime/ibus_client.h109
-rw-r--r--ui/base/ime/ibus_client_impl.cc242
-rw-r--r--ui/base/ime/ibus_client_impl.h58
-rw-r--r--ui/base/ime/input_method_ibus.cc355
-rw-r--r--ui/base/ime/input_method_ibus.h43
-rw-r--r--ui/base/ime/input_method_ibus_unittest.cc452
-rw-r--r--ui/base/ime/mock_ibus_client.cc240
-rw-r--r--ui/base/ime/mock_ibus_client.h87
-rw-r--r--ui/ui.gyp28
-rw-r--r--ui/ui_unittests.gypi11
12 files changed, 1396 insertions, 260 deletions
diff --git a/ui/base/ime/composition_text.h b/ui/base/ime/composition_text.h
index 89632d93..8d76495 100644
--- a/ui/base/ime/composition_text.h
+++ b/ui/base/ime/composition_text.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -18,6 +18,22 @@ struct UI_EXPORT CompositionText {
CompositionText();
~CompositionText();
+ bool operator==(const CompositionText& rhs) const {
+ if ((this->text != rhs.text) ||
+ (this->selection != rhs.selection) ||
+ (this->underlines.size() != rhs.underlines.size()))
+ return false;
+ for (size_t i = 0; i < this->underlines.size(); ++i) {
+ if (this->underlines[i] != rhs.underlines[i])
+ return false;
+ }
+ return true;
+ }
+
+ bool operator!=(const CompositionText& rhs) const {
+ return !(*this == rhs);
+ }
+
void Clear();
// Content of the composition text.
diff --git a/ui/base/ime/composition_underline.h b/ui/base/ime/composition_underline.h
index fc1538d..448789c 100644
--- a/ui/base/ime/composition_underline.h
+++ b/ui/base/ime/composition_underline.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -27,6 +27,17 @@ struct CompositionUnderline {
color(c),
thick(t) {}
+ bool operator==(const CompositionUnderline& rhs) const {
+ return (this->start_offset == rhs.start_offset) &&
+ (this->end_offset == rhs.end_offset) &&
+ (this->color == rhs.color) &&
+ (this->thick == rhs.thick);
+ }
+
+ bool operator!=(const CompositionUnderline& rhs) const {
+ return !(*this == rhs);
+ }
+
// Though use of unsigned is discouraged, we use it here to make sure it's
// identical to WebKit::WebCompositionUnderline.
unsigned start_offset;
diff --git a/ui/base/ime/ibus_client.h b/ui/base/ime/ibus_client.h
new file mode 100644
index 0000000..a430023
--- /dev/null
+++ b/ui/base/ime/ibus_client.h
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_IME_IBUS_CLIENT_H_
+#define UI_BASE_IME_IBUS_CLIENT_H_
+#pragma once
+
+#include <glib/gtypes.h>
+
+#include "base/basictypes.h"
+#include "base/event_types.h"
+#include "base/string16.h"
+#include "ui/base/events.h"
+#include "ui/base/ui_export.h"
+
+typedef struct _IBusBus IBusBus;
+typedef struct _IBusInputContext IBusInputContext;
+typedef struct _IBusText IBusText;
+
+namespace ui {
+
+class CompositionText;
+
+namespace internal {
+
+// An interface implemented by the object that sends and receives an event to
+// and from ibus-daemon.
+class UI_EXPORT IBusClient {
+ public:
+ // 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 {
+ public:
+ virtual ~PendingKeyEvent() {}
+ // Process this pending key event after we receive its result from the input
+ // method. It just call through InputMethodIBus::ProcessKeyEventPostIME().
+ virtual void ProcessPostIME(bool handled) = 0;
+ };
+
+ // A class to hold information of a pending request for creating an ibus input
+ // context.
+ class PendingCreateICRequest {
+ public:
+ virtual ~PendingCreateICRequest() {};
+ // Stores the result input context to |input_method_|, or abandon it if
+ // |input_method_| is NULL.
+ virtual void StoreOrAbandonInputContext(IBusInputContext* ic) = 0;
+ };
+
+ virtual ~IBusClient() {}
+
+ // Gets a D-Bus connection to ibus-daemon. An implementation should establish
+ // a connection to the daemon when the method is called first. After that, the
+ // implementation should return the same object as before.
+ virtual IBusBus* GetConnection() = 0;
+
+ // Returns true if |bus| is connected.
+ virtual bool IsConnected(IBusBus* bus) = 0;
+
+ // Creates a new input context asynchronously. An implementation has to call
+ // PendingCreateICRequest::StoreOrAbandonInputContext() with the newly created
+ // context when the asynchronous request succeeds.
+ virtual void CreateContext(IBusBus* bus,
+ PendingCreateICRequest* request) = 0;
+
+ // Destroys the proxy object for the |context|. An implementation must send
+ // "destroy" signal to the context object.
+ virtual void DestroyProxy(IBusInputContext* context) = 0;
+
+ // Updates the set of capabilities of the |context|.
+ virtual void SetCapabilities(IBusInputContext* context) = 0;
+
+ // Focuses the |context| asynchronously.
+ virtual void FocusIn(IBusInputContext* context) = 0;
+ // Blurs the |context| asynchronously.
+ virtual void FocusOut(IBusInputContext* context) = 0;
+ // Resets the |context| asynchronously.
+ virtual void Reset(IBusInputContext* context) = 0;
+
+ // Resets the cursor location asynchronously.
+ virtual void SetCursorLocation(IBusInputContext* context,
+ int32 x,
+ int32 y,
+ int32 w,
+ int32 h) = 0;
+
+ // Sends the key to ibus-daemon asynchronously.
+ virtual void SendKeyEvent(IBusInputContext* context,
+ uint32 keyval,
+ uint32 keycode,
+ uint32 state,
+ PendingKeyEvent* pending_key) = 0;
+
+ // Called by InputMethodIBus::OnUpdatePreeditText to convert |text| into a
+ // CompositionText.
+ virtual void ExtractCompositionText(IBusText* text,
+ guint cursor_position,
+ CompositionText* out_composition) = 0;
+
+ // Called by InputMethodIBus::OnCommitText to convert |text| into a Unicode
+ // string.
+ virtual string16 ExtractCommitText(IBusText* text) = 0;
+};
+
+} // namespace internal
+} // namespace ui
+
+#endif // UI_BASE_IME_IBUS_CLIENT_H_
diff --git a/ui/base/ime/ibus_client_impl.cc b/ui/base/ime/ibus_client_impl.cc
new file mode 100644
index 0000000..4438b63
--- /dev/null
+++ b/ui/base/ime/ibus_client_impl.cc
@@ -0,0 +1,242 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/ime/ibus_client_impl.h"
+
+#include <ibus.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#undef FocusIn
+#undef FocusOut
+
+#include "base/basictypes.h"
+#include "base/i18n/char_iterator.h"
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "ui/base/ime/composition_text.h"
+
+// input_method_ibus.cc assumes X and IBus use the same mask for Lock, Control,
+// Shift, Alt, and buttons. Check if the assumption is really correct.
+COMPILE_ASSERT(IBUS_LOCK_MASK == LockMask, test_mask);
+COMPILE_ASSERT(IBUS_CONTROL_MASK == ControlMask, test_mask);
+COMPILE_ASSERT(IBUS_SHIFT_MASK == ShiftMask, test_mask);
+COMPILE_ASSERT(IBUS_MOD1_MASK == Mod1Mask, test_mask);
+COMPILE_ASSERT(IBUS_BUTTON1_MASK == Button1Mask, test_mask);
+COMPILE_ASSERT(IBUS_BUTTON2_MASK == Button2Mask, test_mask);
+COMPILE_ASSERT(IBUS_BUTTON3_MASK == Button3Mask, test_mask);
+COMPILE_ASSERT(IBUS_RELEASE_MASK == ui::kIBusReleaseMask, test_mask);
+
+namespace ui {
+namespace internal {
+
+namespace {
+
+const char kClientName[] = "chrome";
+
+XKeyEvent* GetKeyEvent(XEvent* event) {
+ DCHECK(event && (event->type == KeyPress || event->type == KeyRelease));
+ return &event->xkey;
+}
+
+void ProcessKeyEventDone(IBusInputContext* context,
+ GAsyncResult* res,
+ IBusClient::PendingKeyEvent* data) {
+ DCHECK(context);
+ DCHECK(res);
+ DCHECK(data);
+ const gboolean handled = ibus_input_context_process_key_event_async_finish(
+ context, res, NULL);
+ data->ProcessPostIME(handled);
+ delete data;
+}
+
+void CreateInputContextDone(IBusBus* bus,
+ GAsyncResult* res,
+ IBusClient::PendingCreateICRequest* data) {
+ DCHECK(bus);
+ DCHECK(res);
+ DCHECK(data);
+ IBusInputContext* context =
+ ibus_bus_create_input_context_async_finish(bus, res, NULL);
+ if (context)
+ data->StoreOrAbandonInputContext(context);
+ delete data;
+}
+
+} // namespace
+
+IBusClientImpl::IBusClientImpl() {
+}
+
+IBusClientImpl::~IBusClientImpl() {
+}
+
+IBusBus* IBusClientImpl::GetConnection() {
+ ibus_init();
+ return ibus_bus_new();
+}
+
+bool IBusClientImpl::IsConnected(IBusBus* bus) {
+ return ibus_bus_is_connected(bus) == TRUE;
+}
+
+void IBusClientImpl::CreateContext(IBusBus* bus,
+ PendingCreateICRequest* request) {
+ ibus_bus_create_input_context_async(
+ bus,
+ kClientName,
+ -1, // no timeout
+ NULL, // no cancellation object
+ reinterpret_cast<GAsyncReadyCallback>(CreateInputContextDone),
+ request);
+}
+
+void IBusClientImpl::DestroyProxy(IBusInputContext* context) {
+ // ibus_proxy_destroy() will not really release the object, caller still need
+ // to call g_object_unref() explicitly.
+ ibus_proxy_destroy(IBUS_PROXY(context));
+}
+
+void IBusClientImpl::SetCapabilities(IBusInputContext* context) {
+ // TODO(penghuang): support surrounding text.
+ static const guint32 kCapabilities = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS;
+ ibus_input_context_set_capabilities(context, kCapabilities);
+}
+
+void IBusClientImpl::FocusIn(IBusInputContext* context) {
+ ibus_input_context_focus_in(context);
+}
+
+void IBusClientImpl::FocusOut(IBusInputContext* context) {
+ ibus_input_context_focus_out(context);
+}
+
+void IBusClientImpl::Reset(IBusInputContext* context) {
+ ibus_input_context_reset(context);
+}
+
+void IBusClientImpl::SetCursorLocation(IBusInputContext* context,
+ int32 x,
+ int32 y,
+ int32 w,
+ int32 h) {
+ ibus_input_context_set_cursor_location(context, x, y, w, h);
+}
+
+void IBusClientImpl::SendKeyEvent(IBusInputContext* context,
+ uint32 keyval,
+ uint32 keycode,
+ uint32 state,
+ PendingKeyEvent* pending_key) {
+ // 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,
+ keyval,
+ keycode,
+ state,
+ -1, // no timeout
+ NULL, // no cancellation object
+ reinterpret_cast<GAsyncReadyCallback>(ProcessKeyEventDone),
+ pending_key);
+}
+
+// TODO(yusukes): Write a unit test for this function once build bots start
+// supporting ibus-1.4.
+void IBusClientImpl::ExtractCompositionText(IBusText* text,
+ guint cursor_position,
+ CompositionText* out_composition) {
+ out_composition->Clear();
+ out_composition->text = UTF8ToUTF16(text->text);
+
+ if (out_composition->text.empty())
+ return;
+
+ // ibus uses character index for cursor position and attribute range, but we
+ // use char16 offset for them. So we need to do conversion here.
+ std::vector<size_t> char16_offsets;
+ size_t length = out_composition->text.length();
+ base::i18n::UTF16CharIterator char_iterator(&out_composition->text);
+ do {
+ char16_offsets.push_back(char_iterator.array_pos());
+ } while (char_iterator.Advance());
+
+ // The text length in Unicode characters.
+ 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)];
+
+ out_composition->selection = 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;
+ 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) {
+ out_composition->selection.set_start(underline.end_offset);
+ out_composition->selection.set_end(cursor_offset);
+ } else if (underline.end_offset == cursor_offset) {
+ out_composition->selection.set_start(underline.start_offset);
+ out_composition->selection.set_end(cursor_offset);
+ }
+ } 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;
+ }
+ out_composition->underlines.push_back(underline);
+ }
+ }
+
+ // Use a black thin underline by default.
+ if (out_composition->underlines.empty()) {
+ out_composition->underlines.push_back(CompositionUnderline(
+ 0, length, SK_ColorBLACK, false /* thick */));
+ }
+}
+
+string16 IBusClientImpl::ExtractCommitText(IBusText* text) {
+ if (!text || !text->text)
+ return WideToUTF16(L"");
+ return UTF8ToUTF16(text->text);
+}
+
+} // namespace internal
+} // namespace ui
diff --git a/ui/base/ime/ibus_client_impl.h b/ui/base/ime/ibus_client_impl.h
new file mode 100644
index 0000000..4d076a7
--- /dev/null
+++ b/ui/base/ime/ibus_client_impl.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_IME_IBUS_CLIENT_IMPL_H_
+#define UI_BASE_IME_IBUS_CLIENT_IMPL_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "ui/base/ime/ibus_client.h"
+
+namespace ui {
+
+static const int kIBusReleaseMask = 1 << 30;
+
+namespace internal {
+
+// An interface implemented by the object that sends and receives an event to
+// and from ibus-daemon.
+class UI_EXPORT IBusClientImpl : public IBusClient {
+public:
+ IBusClientImpl();
+ virtual ~IBusClientImpl();
+
+ // ui::internal::IBusClient overrides:
+ virtual IBusBus* GetConnection() OVERRIDE;
+ virtual bool IsConnected(IBusBus* bus) OVERRIDE;
+ virtual void CreateContext(IBusBus* bus,
+ PendingCreateICRequest* request) OVERRIDE;
+ virtual void DestroyProxy(IBusInputContext* context) OVERRIDE;
+ virtual void SetCapabilities(IBusInputContext* context) OVERRIDE;
+ virtual void FocusIn(IBusInputContext* context) OVERRIDE;
+ virtual void FocusOut(IBusInputContext* context) OVERRIDE;
+ virtual void Reset(IBusInputContext* context) OVERRIDE;
+ virtual void SetCursorLocation(IBusInputContext* context,
+ int32 x,
+ int32 y,
+ int32 w,
+ int32 h) OVERRIDE;
+ virtual void SendKeyEvent(IBusInputContext* context,
+ uint32 keyval,
+ uint32 keycode,
+ uint32 state,
+ PendingKeyEvent* pending_key) OVERRIDE;
+ virtual void ExtractCompositionText(
+ IBusText* text,
+ guint cursor_position,
+ CompositionText* out_composition) OVERRIDE;
+ virtual string16 ExtractCommitText(IBusText* text) OVERRIDE;
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(IBusClientImpl);
+};
+
+} // namespace internal
+} // namespace ui
+
+#endif // UI_BASE_IME_IBUS_CLIENT_IMPL_H_
diff --git a/ui/base/ime/input_method_ibus.cc b/ui/base/ime/input_method_ibus.cc
index 441e713..2cc7820 100644
--- a/ui/base/ime/input_method_ibus.cc
+++ b/ui/base/ime/input_method_ibus.cc
@@ -1,14 +1,16 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/ime/input_method_ibus.h"
-#include <ibus.h>
-
+#include <glib.h>
+#include <glib-object.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
+#undef FocusIn
+#undef FocusOut
#include <algorithm>
#include <cstring>
@@ -16,16 +18,17 @@
#include <vector>
#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/events.h"
+#include "ui/base/ime/ibus_client_impl.h"
+#include "ui/base/ime/mock_ibus_client.h"
#include "ui/base/ime/text_input_client.h"
-#include "ui/base/keycodes/keyboard_codes.h"
#include "ui/base/keycodes/keyboard_code_conversion.h"
#include "ui/base/keycodes/keyboard_code_conversion_x.h"
+#include "ui/base/keycodes/keyboard_codes.h"
#include "ui/gfx/rect.h"
namespace {
@@ -35,31 +38,21 @@ XKeyEvent* GetKeyEvent(XEvent* event) {
return &event->xkey;
}
-// Converts ibus key state flags to event flags.
-int EventFlagsFromIBusState(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_MOUSE_BUTTON : 0) |
- (state & IBUS_BUTTON2_MASK ? ui::EF_MIDDLE_MOUSE_BUTTON : 0) |
- (state & IBUS_BUTTON3_MASK ? ui::EF_RIGHT_MOUSE_BUTTON : 0);
+// Converts X (and ibus) flags to event flags.
+int EventFlagsFromXFlags(unsigned int flags) {
+ return (flags & LockMask ? ui::EF_CAPS_LOCK_DOWN : 0) |
+ (flags & ControlMask ? ui::EF_CONTROL_DOWN : 0) |
+ (flags & ShiftMask ? ui::EF_SHIFT_DOWN : 0) |
+ (flags & Mod1Mask ? ui::EF_ALT_DOWN : 0) |
+ (flags & Button1Mask ? ui::EF_LEFT_MOUSE_BUTTON : 0) |
+ (flags & Button2Mask ? ui::EF_MIDDLE_MOUSE_BUTTON : 0) |
+ (flags & Button3Mask ? ui::EF_RIGHT_MOUSE_BUTTON : 0);
}
// Converts X flags to ibus key state flags.
guint32 IBusStateFromXFlags(unsigned int flags) {
- return (flags & LockMask ? IBUS_LOCK_MASK : 0U) |
- (flags & ControlMask ? IBUS_CONTROL_MASK : 0U) |
- (flags & ShiftMask ? IBUS_SHIFT_MASK : 0U) |
- (flags & Mod1Mask ? IBUS_MOD1_MASK : 0U) |
- (flags & Button1Mask ? IBUS_BUTTON1_MASK : 0U) |
- (flags & Button2Mask ? IBUS_BUTTON2_MASK : 0U) |
- (flags & Button3Mask ? IBUS_BUTTON3_MASK : 0U);
-}
-
-// Converts X flags to event flags.
-guint32 EventFlagsFromXFlags(unsigned int flags) {
- return EventFlagsFromIBusState(IBusStateFromXFlags(flags));
+ return (flags & (LockMask | ControlMask | ShiftMask | Mod1Mask |
+ Button1Mask | Button2Mask | Button3Mask));
}
void IBusKeyEventFromNativeKeyEvent(const base::NativeEvent& native_event,
@@ -77,103 +70,30 @@ void IBusKeyEventFromNativeKeyEvent(const base::NativeEvent& native_event,
*ibus_keycode = x_key->keycode;
*ibus_state = IBusStateFromXFlags(x_key->state);
if (native_event->type == KeyRelease)
- *ibus_state |= IBUS_RELEASE_MASK;
-}
-
-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 */));
- }
+ *ibus_state |= ui::kIBusReleaseMask;
}
} // namespace
namespace ui {
-// InputMethodIBus::PendingKeyEvent implementation ------------------------
-class InputMethodIBus::PendingKeyEvent {
+// InputMethodIBus::PendingKeyEventImpl implementation ------------------------
+class InputMethodIBus::PendingKeyEventImpl
+ : public internal::IBusClient::PendingKeyEvent {
public:
- PendingKeyEvent(InputMethodIBus* input_method,
- const base::NativeEvent& native_event,
- guint32 ibus_keyval);
- ~PendingKeyEvent();
+ PendingKeyEventImpl(InputMethodIBus* input_method,
+ const base::NativeEvent& native_event,
+ guint32 ibus_keyval);
+ virtual ~PendingKeyEventImpl();
+
+ // internal::IBusClient::PendingKeyEvent overrides:
+ virtual void ProcessPostIME(bool handled) OVERRIDE;
// 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_;
@@ -188,10 +108,10 @@ class InputMethodIBus::PendingKeyEvent {
const guint32 ibus_keyval_;
- DISALLOW_COPY_AND_ASSIGN(PendingKeyEvent);
+ DISALLOW_COPY_AND_ASSIGN(PendingKeyEventImpl);
};
-InputMethodIBus::PendingKeyEvent::PendingKeyEvent(
+InputMethodIBus::PendingKeyEventImpl::PendingKeyEventImpl(
InputMethodIBus* input_method,
const base::NativeEvent& native_event,
guint32 ibus_keyval)
@@ -204,12 +124,12 @@ InputMethodIBus::PendingKeyEvent::PendingKeyEvent(
x_event_ = *GetKeyEvent(native_event);
}
-InputMethodIBus::PendingKeyEvent::~PendingKeyEvent() {
+InputMethodIBus::PendingKeyEventImpl::~PendingKeyEventImpl() {
if (input_method_)
input_method_->FinishPendingKeyEvent(this);
}
-void InputMethodIBus::PendingKeyEvent::ProcessPostIME(bool handled) {
+void InputMethodIBus::PendingKeyEventImpl::ProcessPostIME(bool handled) {
if (!input_method_)
return;
@@ -225,50 +145,64 @@ void InputMethodIBus::PendingKeyEvent::ProcessPostIME(bool handled) {
// and 'unmodified_character_' to support i18n VKs like a French VK!
}
-// InputMethodIBus::PendingCreateICRequest implementation -----------------
-class InputMethodIBus::PendingCreateICRequest {
+// InputMethodIBus::PendingCreateICRequestImpl implementation -----------------
+class InputMethodIBus::PendingCreateICRequestImpl
+ : public internal::IBusClient::PendingCreateICRequest {
public:
- PendingCreateICRequest(InputMethodIBus* input_method,
- PendingCreateICRequest** request_ptr);
- ~PendingCreateICRequest();
+ PendingCreateICRequestImpl(InputMethodIBus* input_method,
+ internal::IBusClient* ibus_client,
+ PendingCreateICRequestImpl** request_ptr);
+ virtual ~PendingCreateICRequestImpl();
+
+ // internal::IBusClient::PendingCreateICRequest overrides:
+ virtual void StoreOrAbandonInputContext(IBusInputContext* ic) OVERRIDE;
// Abandon this pending key event. Its result will just be discarded.
void Abandon() {
input_method_ = NULL;
request_ptr_ = NULL;
+ // Do not reset |ibus_client_| here.
}
- // 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_;
+ internal::IBusClient* ibus_client_;
+ PendingCreateICRequestImpl** request_ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(PendingCreateICRequestImpl);
};
-InputMethodIBus::PendingCreateICRequest::PendingCreateICRequest(
+InputMethodIBus::PendingCreateICRequestImpl::PendingCreateICRequestImpl(
InputMethodIBus* input_method,
- PendingCreateICRequest** request_ptr)
+ internal::IBusClient* ibus_client,
+ PendingCreateICRequestImpl** request_ptr)
: input_method_(input_method),
+ ibus_client_(ibus_client),
request_ptr_(request_ptr) {
}
-InputMethodIBus::PendingCreateICRequest::~PendingCreateICRequest() {
+InputMethodIBus::PendingCreateICRequestImpl::~PendingCreateICRequestImpl() {
if (request_ptr_) {
DCHECK_EQ(*request_ptr_, this);
*request_ptr_ = NULL;
}
}
-void InputMethodIBus::PendingCreateICRequest::StoreOrAbandonInputContext(
+void InputMethodIBus::PendingCreateICRequestImpl::StoreOrAbandonInputContext(
IBusInputContext* ic) {
+ // TODO(yusukes): If the connection between Chrome and ibus-daemon terminates
+ // for some reason, the create ic request will fail. In that case, NULL ic
+ // will be passed to this function. We might want to call
+ // ibus_client_->CreateContext() again after some delay.
+ if (!ic)
+ return;
+
if (input_method_) {
input_method_->SetContext(ic);
} else {
- // ibus_proxy_destroy() will not really release the object, we still need
+ // Since DestroyProxy() will not really release the object, we still need
// to call g_object_unref() explicitly.
- ibus_proxy_destroy(reinterpret_cast<IBusProxy *>(ic));
+ ibus_client_->DestroyProxy(ic);
g_object_unref(ic);
}
}
@@ -276,7 +210,13 @@ void InputMethodIBus::PendingCreateICRequest::StoreOrAbandonInputContext(
// InputMethodIBus implementation -----------------------------------------
InputMethodIBus::InputMethodIBus(
internal::InputMethodDelegate* delegate)
- : context_(NULL),
+ :
+#if defined(HAVE_IBUS)
+ ibus_client_(new internal::IBusClientImpl),
+#else
+ ibus_client_(new internal::MockIBusClient),
+#endif
+ context_(NULL),
pending_create_ic_request_(NULL),
context_focused_(false),
composing_text_(false),
@@ -291,9 +231,18 @@ InputMethodIBus::~InputMethodIBus() {
// Disconnect bus signals
g_signal_handlers_disconnect_by_func(
- GetIBus(), reinterpret_cast<gpointer>(OnIBusConnectedThunk), this);
+ GetBus(), reinterpret_cast<gpointer>(OnIBusConnectedThunk), this);
g_signal_handlers_disconnect_by_func(
- GetIBus(), reinterpret_cast<gpointer>(OnIBusDisconnectedThunk), this);
+ GetBus(), reinterpret_cast<gpointer>(OnIBusDisconnectedThunk), this);
+}
+
+void InputMethodIBus::set_ibus_client(
+ scoped_ptr<internal::IBusClient> new_client) {
+ ibus_client_.swap(new_client);
+}
+
+internal::IBusClient* InputMethodIBus::ibus_client() const {
+ return ibus_client_.get();
}
void InputMethodIBus::OnFocus() {
@@ -311,7 +260,7 @@ void InputMethodIBus::Init(bool focused) {
// 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();
+ IBusBus* bus = GetBus();
// connect bus signals
g_signal_connect(bus, "connected",
@@ -321,7 +270,7 @@ void InputMethodIBus::Init(bool focused) {
// Creates the |context_| if the connection is already established. In such
// case, we will not get "connected" signal.
- if (ibus_bus_is_connected(bus))
+ if (ibus_client_->IsConnected(bus))
CreateContext();
InputMethodBase::Init(focused);
@@ -352,32 +301,15 @@ void InputMethodIBus::DispatchKeyEvent(const base::NativeEvent& native_event) {
return;
}
- PendingKeyEvent* pending_key =
- new PendingKeyEvent(this, native_event, ibus_keyval);
+ PendingKeyEventImpl* pending_key =
+ new PendingKeyEventImpl(this, native_event, ibus_keyval);
pending_key_events_.insert(pending_key);
- // 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_,
- ibus_keyval, ibus_keycode, ibus_state, -1, NULL,
- reinterpret_cast<GAsyncReadyCallback>(ProcessKeyEventDone),
- pending_key);
+ ibus_client_->SendKeyEvent(context_,
+ ibus_keyval,
+ ibus_keycode,
+ ibus_state,
+ 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.
@@ -401,7 +333,7 @@ void InputMethodIBus::OnCaretBoundsChanged(const TextInputClient* client) {
const gfx::Rect rect = GetTextInputClient()->GetCaretBounds();
// This function runs asynchronously.
- ibus_input_context_set_cursor_location(
+ ibus_client_->SetCursorLocation(
context_, rect.x(), rect.y(), rect.width(), rect.height());
}
@@ -439,23 +371,19 @@ void InputMethodIBus::OnDidChangeFocusedClient(TextInputClient* focused_before,
UpdateContextFocusState();
// Force to update caret bounds, in case the client thinks that the caret
// bounds has not changed.
- if (context_focused_)
- OnCaretBoundsChanged(focused);
+ OnCaretBoundsChanged(focused);
}
void InputMethodIBus::CreateContext() {
DCHECK(!context_);
- DCHECK(GetIBus());
- DCHECK(ibus_bus_is_connected(GetIBus()));
+ DCHECK(GetBus());
+ DCHECK(ibus_client_->IsConnected(GetBus()));
DCHECK(!pending_create_ic_request_);
// Creates the input context asynchronously.
- pending_create_ic_request_ = new PendingCreateICRequest(
- this, &pending_create_ic_request_);
- ibus_bus_create_input_context_async(
- GetIBus(), "chrome", -1, NULL,
- reinterpret_cast<GAsyncReadyCallback>(CreateInputContextDone),
- pending_create_ic_request_);
+ pending_create_ic_request_ = new PendingCreateICRequestImpl(
+ this, ibus_client_.get(), &pending_create_ic_request_);
+ ibus_client_->CreateContext(GetBus(), pending_create_ic_request_);
}
void InputMethodIBus::SetContext(IBusInputContext* ic) {
@@ -477,11 +405,13 @@ void InputMethodIBus::SetContext(IBusInputContext* ic) {
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);
+ ibus_client_->SetCapabilities(ic);
UpdateContextFocusState();
+ // Since ibus-daemon is launched in an on-demand basis on Chrome OS, RWHVA (or
+ // equivalents) might call OnCaretBoundsChanged() before the daemon starts. To
+ // save the case, call OnCaretBoundsChanged() here.
+ OnCaretBoundsChanged(GetTextInputClient());
OnInputMethodChanged();
}
@@ -492,10 +422,10 @@ void InputMethodIBus::DestroyContext() {
pending_create_ic_request_->Abandon();
pending_create_ic_request_ = NULL;
} else if (context_) {
- // ibus_proxy_destroy() will not really release the resource of |context_|
+ // DestroyProxy() 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_));
+ ibus_client_->DestroyProxy(context_);
DCHECK(!context_);
}
}
@@ -538,7 +468,7 @@ void InputMethodIBus::ResetContext() {
// 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_);
+ ibus_client_->Reset(context_);
character_composer_.Reset();
}
@@ -564,9 +494,9 @@ void InputMethodIBus::UpdateContextFocusState() {
// 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_);
+ ibus_client_->FocusOut(context_);
else if (!old_context_focused && context_focused_)
- ibus_input_context_focus_in(context_);
+ ibus_client_->FocusIn(context_);
}
void InputMethodIBus::ProcessKeyEventPostIME(
@@ -743,7 +673,7 @@ void InputMethodIBus::SendFakeProcessKeyEvent(bool pressed) const {
0);
}
-void InputMethodIBus::FinishPendingKeyEvent(PendingKeyEvent* pending_key) {
+void InputMethodIBus::FinishPendingKeyEvent(PendingKeyEventImpl* pending_key) {
DCHECK(pending_key_events_.count(pending_key));
// |pending_key| will be deleted in ProcessKeyEventDone().
@@ -751,8 +681,8 @@ void InputMethodIBus::FinishPendingKeyEvent(PendingKeyEvent* pending_key) {
}
void InputMethodIBus::AbandonAllPendingKeyEvents() {
- for (std::set<PendingKeyEvent*>::iterator i = pending_key_events_.begin();
- i != pending_key_events_.end(); ++i) {
+ std::set<PendingKeyEventImpl*>::iterator i;
+ for (i = pending_key_events_.begin(); i != pending_key_events_.end(); ++i) {
// The object will be deleted in ProcessKeyEventDone().
(*i)->Abandon();
}
@@ -762,7 +692,7 @@ void InputMethodIBus::AbandonAllPendingKeyEvents() {
void InputMethodIBus::OnCommitText(
IBusInputContext* context, IBusText* text) {
DCHECK_EQ(context_, context);
- if (suppress_next_result_ || !text || !text->text)
+ if (suppress_next_result_ || !text)
return;
// We need to receive input method result even if the text input type is
@@ -771,7 +701,9 @@ void InputMethodIBus::OnCommitText(
if (!GetTextInputClient())
return;
- string16 utf16_text(UTF8ToUTF16(text->text));
+ const string16 utf16_text = ibus_client_->ExtractCommitText(text);
+ if (utf16_text.empty())
+ return;
// Append the text to the buffer, because commit signal might be fired
// multiple times when processing a key event.
@@ -797,19 +729,21 @@ void InputMethodIBus::OnForwardKeyEvent(IBusInputContext* context,
if (!ui_key_code)
return;
- const EventType event =
- (state & IBUS_RELEASE_MASK) ? ET_KEY_RELEASED : ET_KEY_PRESSED;
- const int flags = EventFlagsFromIBusState(state);
+ const EventType event_type =
+ (state & kIBusReleaseMask) ? ET_KEY_RELEASED : ET_KEY_PRESSED;
+ const int event_flags = EventFlagsFromXFlags(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 (event == ET_KEY_PRESSED)
- ProcessUnfilteredFabricatedKeyPressEvent(event, ui_key_code, flags, keyval);
- else
- DispatchFabricatedKeyEventPostIME(event, ui_key_code, flags);
+ if (event_type == ET_KEY_PRESSED) {
+ ProcessUnfilteredFabricatedKeyPressEvent(
+ event_type, ui_key_code, event_flags, keyval);
+ } else {
+ DispatchFabricatedKeyEventPostIME(event_type, ui_key_code, event_flags);
+ }
}
void InputMethodIBus::OnShowPreeditText(IBusInputContext* context) {
@@ -840,7 +774,7 @@ void InputMethodIBus::OnUpdatePreeditText(IBusInputContext* context,
return;
}
- ExtractCompositionTextFromIBusPreedit(text, cursor_pos, &composition_);
+ ibus_client_->ExtractCompositionText(text, cursor_pos, &composition_);
composition_changed_ = true;
@@ -891,60 +825,31 @@ void InputMethodIBus::OnDestroy(IBusInputContext* context) {
}
void InputMethodIBus::OnIBusConnected(IBusBus* bus) {
- DCHECK_EQ(GetIBus(), bus);
- DCHECK(ibus_bus_is_connected(bus));
+ DCHECK_EQ(GetBus(), bus);
+ DCHECK(ibus_client_->IsConnected(bus));
DestroyContext();
CreateContext();
}
void InputMethodIBus::OnIBusDisconnected(IBusBus* bus) {
- DCHECK_EQ(GetIBus(), bus);
+ DCHECK_EQ(GetBus(), 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() {
+IBusBus* InputMethodIBus::GetBus() {
// 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();
+ ibus = ibus_client_->GetConnection();
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);
-
- gboolean handled = ibus_input_context_process_key_event_async_finish(
- context, res, NULL);
- 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 ui
diff --git a/ui/base/ime/input_method_ibus.h b/ui/base/ime/input_method_ibus.h
index 624ff38..30f7e90 100644
--- a/ui/base/ime/input_method_ibus.h
+++ b/ui/base/ime/input_method_ibus.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -16,6 +16,7 @@
#include "ui/base/glib/glib_signal.h"
#include "ui/base/ime/character_composer.h"
#include "ui/base/ime/composition_text.h"
+#include "ui/base/ime/ibus_client.h"
#include "ui/base/ime/input_method_base.h"
// Forward declarations, so that we don't need to include ibus.h in this file.
@@ -45,14 +46,21 @@ class UI_EXPORT InputMethodIBus : public InputMethodBase {
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;
+ protected:
+ // Sets |new_client| as a new IBusClient. InputMethodIBus owns the object.
+ // This method has to be called before InputMethodIBus::Init() is called.
+ // Protected: for testing.
+ void set_ibus_client(scoped_ptr<internal::IBusClient> new_client);
+
+ // Protected: for testing. The caller is not allowed to deleted the object.
+ internal::IBusClient* ibus_client() const;
+
+ // Returns the global IBusBus instance. Protected: for testing.
+ IBusBus* GetBus();
- // A class to hold information of a pending request for creating an ibus input
- // context.
- class PendingCreateICRequest;
+ private:
+ class PendingKeyEventImpl;
+ class PendingCreateICRequestImpl;
// Overridden from InputMethodBase:
virtual void OnWillChangeFocusedClient(TextInputClient* focused_before,
@@ -114,7 +122,7 @@ class UI_EXPORT InputMethodIBus : public InputMethodBase {
// Called when a pending key event has finished. The event will be removed
// from |pending_key_events_|.
- void FinishPendingKeyEvent(PendingKeyEvent* pending_key);
+ void FinishPendingKeyEvent(PendingKeyEventImpl* 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.
@@ -137,18 +145,7 @@ class UI_EXPORT InputMethodIBus : public InputMethodBase {
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);
+ scoped_ptr<internal::IBusClient> ibus_client_;
// The input context for actual text input. Note that we don't have to support
// a "fake" IBus input context anymore since the latest Chrome for Chrome OS
@@ -158,11 +155,11 @@ class UI_EXPORT InputMethodIBus : public InputMethodBase {
// 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_;
+ std::set<PendingKeyEventImpl*> 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_;
+ PendingCreateICRequestImpl* pending_create_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
diff --git a/ui/base/ime/input_method_ibus_unittest.cc b/ui/base/ime/input_method_ibus_unittest.cc
new file mode 100644
index 0000000..025846b
--- /dev/null
+++ b/ui/base/ime/input_method_ibus_unittest.cc
@@ -0,0 +1,452 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <glib-object.h>
+
+#include <cstring>
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/ime/input_method_delegate.h"
+#include "ui/base/ime/input_method_ibus.h"
+#include "ui/base/ime/mock_ibus_client.h"
+#include "ui/base/ime/text_input_client.h"
+#include "ui/gfx/rect.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ui {
+
+class TestableInputMethodIBus : public InputMethodIBus {
+ public:
+ TestableInputMethodIBus(internal::InputMethodDelegate* delegate,
+ scoped_ptr<internal::IBusClient> new_client)
+ : InputMethodIBus(delegate) {
+ set_ibus_client(new_client.Pass());
+ }
+
+ // Change access rights.
+ using InputMethodIBus::ibus_client;
+ using InputMethodIBus::GetBus;
+};
+
+class InputMethodIBusTest : public internal::InputMethodDelegate,
+ public testing::Test,
+ public TextInputClient {
+ public:
+ InputMethodIBusTest() {
+ ResetFlags();
+ }
+
+ virtual ~InputMethodIBusTest() {
+ }
+
+ // testing::Test overrides:
+ virtual void SetUp() OVERRIDE {
+ scoped_ptr<internal::IBusClient> client(new internal::MockIBusClient);
+ ime_.reset(new TestableInputMethodIBus(this, client.Pass()));
+ ime_->SetFocusedTextInputClient(this);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ if (ime_.get())
+ ime_->SetFocusedTextInputClient(NULL);
+ ime_.reset();
+ }
+
+ // ui::internal::InputMethodDelegate overrides:
+ virtual void DispatchKeyEventPostIME(
+ const base::NativeEvent& native_key_event) OVERRIDE {
+ dispatched_native_event_ = native_key_event;
+ }
+ virtual void DispatchFabricatedKeyEventPostIME(ui::EventType type,
+ ui::KeyboardCode key_code,
+ int flags) OVERRIDE {
+ dispatched_fabricated_event_type_ = type;
+ dispatched_fabricated_event_key_code_ = key_code;
+ dispatched_fabricated_event_flags_ = flags;
+ }
+
+ // ui::TextInputClient overrides:
+ virtual void SetCompositionText(
+ const CompositionText& composition) OVERRIDE {
+ composition_text_ = composition;
+ }
+ virtual void ConfirmCompositionText() OVERRIDE {
+ confirmed_text_ = composition_text_;
+ composition_text_.Clear();
+ }
+ virtual void ClearCompositionText() OVERRIDE {
+ composition_text_.Clear();
+ }
+ virtual void InsertText(const string16& text) OVERRIDE {
+ inserted_text_ = text;
+ }
+ virtual void InsertChar(char16 ch, int flags) OVERRIDE {
+ inserted_char_ = ch;
+ inserted_char_flags_ = flags;
+ }
+ virtual TextInputType GetTextInputType() const OVERRIDE {
+ return input_type_;
+ }
+ virtual gfx::Rect GetCaretBounds() OVERRIDE {
+ return caret_bounds_;
+ }
+ virtual bool HasCompositionText() OVERRIDE {
+ CompositionText empty;
+ return composition_text_ != empty;
+ }
+ virtual bool GetTextRange(Range* range) OVERRIDE { return false; }
+ virtual bool GetCompositionTextRange(Range* range) OVERRIDE { return false; }
+ virtual bool GetSelectionRange(Range* range) OVERRIDE { return false; }
+ virtual bool SetSelectionRange(const Range& range) OVERRIDE { return false; }
+ virtual bool DeleteRange(const Range& range) OVERRIDE { return false; }
+ virtual bool GetTextFromRange(const Range& range,
+ string16* text) OVERRIDE { return false; }
+ virtual void OnInputMethodChanged() OVERRIDE {
+ ++on_input_method_changed_call_count_;
+ }
+ virtual bool ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection direction) OVERRIDE { return false; }
+
+ IBusBus* GetBus() {
+ return ime_->GetBus();
+ }
+
+ internal::MockIBusClient* GetClient() {
+ return static_cast<internal::MockIBusClient*>(ime_->ibus_client());
+ }
+
+ bool HasNativeEvent() const {
+ base::NativeEvent empty;
+ std::memset(&empty, 0, sizeof(empty));
+ return !!std::memcmp(&dispatched_native_event_,
+ &empty,
+ sizeof(dispatched_native_event_));
+ }
+
+ void ResetFlags() {
+ std::memset(&dispatched_native_event_, 0, sizeof(dispatched_native_event_));
+ DCHECK(!HasNativeEvent());
+ dispatched_fabricated_event_type_ = ET_UNKNOWN;
+ dispatched_fabricated_event_key_code_ = VKEY_UNKNOWN;
+ dispatched_fabricated_event_flags_ = 0;
+
+ composition_text_.Clear();
+ confirmed_text_.Clear();
+ inserted_text_.clear();
+ inserted_char_ = 0;
+ inserted_char_flags_ = 0;
+ on_input_method_changed_call_count_ = 0;
+
+ input_type_ = TEXT_INPUT_TYPE_NONE;
+ caret_bounds_ = gfx::Rect();
+ }
+
+ scoped_ptr<TestableInputMethodIBus> ime_;
+
+ // Variables for remembering the parameters that are passed to
+ // ui::internal::InputMethodDelegate functions.
+ base::NativeEvent dispatched_native_event_;
+ ui::EventType dispatched_fabricated_event_type_;
+ ui::KeyboardCode dispatched_fabricated_event_key_code_;
+ int dispatched_fabricated_event_flags_;
+
+ // Variables for remembering the parameters that are passed to
+ // ui::TextInputClient functions.
+ CompositionText composition_text_;
+ CompositionText confirmed_text_;
+ string16 inserted_text_;
+ char16 inserted_char_;
+ unsigned int on_input_method_changed_call_count_;
+ int inserted_char_flags_;
+
+ // Variables that will be returned from the ui::TextInputClient functions.
+ TextInputType input_type_;
+ gfx::Rect caret_bounds_;
+};
+
+// Tests public APIs in ui::InputMethod first.
+
+TEST_F(InputMethodIBusTest, GetInputLocale) {
+ // ui::InputMethodIBus does not support the API.
+ ime_->Init(true);
+ EXPECT_EQ("", ime_->GetInputLocale());
+}
+
+TEST_F(InputMethodIBusTest, GetInputTextDirection) {
+ // ui::InputMethodIBus does not support the API.
+ ime_->Init(true);
+ EXPECT_EQ(base::i18n::UNKNOWN_DIRECTION, ime_->GetInputTextDirection());
+}
+
+TEST_F(InputMethodIBusTest, IsActive) {
+ ime_->Init(true);
+ // ui::InputMethodIBus always returns true.
+ EXPECT_TRUE(ime_->IsActive());
+}
+
+TEST_F(InputMethodIBusTest, GetInputTextType) {
+ ime_->Init(true);
+ EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType());
+ input_type_ = TEXT_INPUT_TYPE_PASSWORD;
+ ime_->OnTextInputTypeChanged(this);
+ EXPECT_EQ(TEXT_INPUT_TYPE_PASSWORD, ime_->GetTextInputType());
+ input_type_ = TEXT_INPUT_TYPE_TEXT;
+ ime_->OnTextInputTypeChanged(this);
+ EXPECT_EQ(TEXT_INPUT_TYPE_TEXT, ime_->GetTextInputType());
+}
+
+TEST_F(InputMethodIBusTest, GetTextInputClient) {
+ ime_->Init(true);
+ EXPECT_EQ(this, ime_->GetTextInputClient());
+ ime_->SetFocusedTextInputClient(NULL);
+ EXPECT_EQ(NULL, ime_->GetTextInputClient());
+}
+
+TEST_F(InputMethodIBusTest, GetInputTextType_WithoutFocusedClient) {
+ ime_->Init(true);
+ EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType());
+ ime_->SetFocusedTextInputClient(NULL);
+ input_type_ = TEXT_INPUT_TYPE_PASSWORD;
+ ime_->OnTextInputTypeChanged(this);
+ // The OnTextInputTypeChanged() call above should be ignored since |this| is
+ // not the current focused client.
+ EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType());
+
+ ime_->SetFocusedTextInputClient(this);
+ ime_->OnTextInputTypeChanged(this);
+ EXPECT_EQ(TEXT_INPUT_TYPE_PASSWORD, ime_->GetTextInputType());
+}
+
+TEST_F(InputMethodIBusTest, GetInputTextType_WithoutFocusedWindow) {
+ ime_->Init(true);
+ EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType());
+ ime_->OnBlur();
+ input_type_ = TEXT_INPUT_TYPE_PASSWORD;
+ ime_->OnTextInputTypeChanged(this);
+ // The OnTextInputTypeChanged() call above should be ignored since the top-
+ // level window which the ime_ is attached to is not currently focused.
+ EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType());
+
+ ime_->OnFocus();
+ ime_->OnTextInputTypeChanged(this);
+ EXPECT_EQ(TEXT_INPUT_TYPE_PASSWORD, ime_->GetTextInputType());
+}
+
+TEST_F(InputMethodIBusTest, GetInputTextType_WithoutFocusedWindow2) {
+ ime_->Init(false); // the top-level is initially unfocused.
+ EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType());
+ input_type_ = TEXT_INPUT_TYPE_PASSWORD;
+ ime_->OnTextInputTypeChanged(this);
+ EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType());
+
+ ime_->OnFocus();
+ ime_->OnTextInputTypeChanged(this);
+ EXPECT_EQ(TEXT_INPUT_TYPE_PASSWORD, ime_->GetTextInputType());
+}
+
+// Then, tests internal behavior of ui::InputMethodIBus, especially if the input
+// method implementation calls ui::internal::IBusClient APIs as expected.
+
+// Start ibus-daemon first, then create ui::InputMethodIBus. Check if a new
+// input context is created.
+TEST_F(InputMethodIBusTest, InitiallyConnected) {
+ GetClient()->is_connected_ = true;
+ ime_->Init(true);
+ // An input context should be created immediately since is_connected_ is true.
+ EXPECT_EQ(1U, GetClient()->create_context_call_count_);
+ EXPECT_EQ(1U, GetClient()->set_capabilities_call_count_);
+ // However, since the current text input type is 'NONE' (the default), FocusIn
+ // shouldn't be called.
+ EXPECT_EQ(0U, GetClient()->focus_in_call_count_);
+}
+
+// Create ui::InputMethodIBus, then start ibus-daemon.
+TEST_F(InputMethodIBusTest, InitiallyDisconnected) {
+ ime_->Init(true);
+ // A context shouldn't be created since the daemon is not running.
+ EXPECT_EQ(0U, GetClient()->create_context_call_count_);
+ // Start the daemon.
+ GetClient()->is_connected_ = true;
+ g_signal_emit_by_name(GetBus(), "connected");
+ // A context should be created upon the signal delivery.
+ EXPECT_EQ(1U, GetClient()->create_context_call_count_);
+ EXPECT_EQ(1U, GetClient()->set_capabilities_call_count_);
+ EXPECT_EQ(0U, GetClient()->focus_in_call_count_);
+}
+
+// Confirm that ui::InputMethodIBus does not crash on "disconnected" signal
+// delivery.
+TEST_F(InputMethodIBusTest, Disconnect) {
+ GetClient()->is_connected_ = true;
+ ime_->Init(true);
+ EXPECT_EQ(1U, GetClient()->create_context_call_count_);
+ GetClient()->is_connected_ = false;
+ g_signal_emit_by_name(GetBus(), "disconnected");
+}
+
+// Confirm that ui::InputMethodIBus re-creates an input context when ibus-daemon
+// restarts.
+TEST_F(InputMethodIBusTest, DisconnectThenReconnect) {
+ GetClient()->is_connected_ = true;
+ ime_->Init(true);
+ EXPECT_EQ(1U, GetClient()->create_context_call_count_);
+ EXPECT_EQ(1U, GetClient()->set_capabilities_call_count_);
+ EXPECT_EQ(0U, GetClient()->destroy_proxy_call_count_);
+ GetClient()->is_connected_ = false;
+ g_signal_emit_by_name(GetBus(), "disconnected");
+ GetClient()->is_connected_ = true;
+ g_signal_emit_by_name(GetBus(), "connected");
+ // Check if the old context is deleted.
+ EXPECT_EQ(1U, GetClient()->destroy_proxy_call_count_);
+ // Check if a new context is created.
+ EXPECT_EQ(2U, GetClient()->create_context_call_count_);
+ EXPECT_EQ(2U, GetClient()->set_capabilities_call_count_);
+}
+
+// Confirm that ui::InputMethodIBus does not crash even if NULL context is
+// passed.
+// TODO(yusukes): Currently, ui::InputMethodIBus does not try to create ic once
+// it fails (unless ibus sends the "connected" signal to Chrome again). It might
+// be better to add some retry logic. Will revisit later.
+TEST_F(InputMethodIBusTest, CreateContextFail) {
+ GetClient()->is_connected_ = true;
+ GetClient()->create_context_result_ =
+ internal::MockIBusClient::kCreateContextFail;
+ ime_->Init(true);
+ EXPECT_EQ(1U, GetClient()->create_context_call_count_);
+ // |set_capabilities_call_count_| should be zero since a context is not
+ // created yet.
+ EXPECT_EQ(0U, GetClient()->set_capabilities_call_count_);
+}
+
+// Confirm that ui::InputMethodIBus does not crash even if ibus-daemon does not
+// respond.
+TEST_F(InputMethodIBusTest, CreateContextNoResp) {
+ GetClient()->is_connected_ = true;
+ GetClient()->create_context_result_ =
+ internal::MockIBusClient::kCreateContextNoResponse;
+ ime_->Init(true);
+ EXPECT_EQ(1U, GetClient()->create_context_call_count_);
+ EXPECT_EQ(0U, GetClient()->set_capabilities_call_count_);
+}
+
+// Confirm that ui::InputMethodIBus does not crash even if ibus-daemon responds
+// after ui::InputMethodIBus is deleted. See comments in ~MockIBusClient() as
+// well.
+TEST_F(InputMethodIBusTest, CreateContextDelayed) {
+ GetClient()->is_connected_ = true;
+ GetClient()->create_context_result_ =
+ internal::MockIBusClient::kCreateContextDelayed;
+ ime_->Init(true);
+ EXPECT_EQ(1U, GetClient()->create_context_call_count_);
+ EXPECT_EQ(0U, GetClient()->set_capabilities_call_count_);
+ // After this line, the destructor for |ime_| will run first. Then, the
+ // destructor for the client will run. In the latter function, a new input
+ // context will be created and passed to StoreOrAbandonInputContext().
+}
+
+// Confirm that IBusClient::FocusIn is called on "connected" if input_type_ is
+// TEXT.
+TEST_F(InputMethodIBusTest, FocusIn_Text) {
+ ime_->Init(true);
+ // A context shouldn't be created since the daemon is not running.
+ EXPECT_EQ(0U, GetClient()->create_context_call_count_);
+ EXPECT_EQ(0U, on_input_method_changed_call_count_);
+ // Click a text input form.
+ input_type_ = TEXT_INPUT_TYPE_TEXT;
+ ime_->OnTextInputTypeChanged(this);
+ // Start the daemon.
+ GetClient()->is_connected_ = true;
+ g_signal_emit_by_name(GetBus(), "connected");
+ // A context should be created upon the signal delivery.
+ EXPECT_EQ(1U, GetClient()->create_context_call_count_);
+ // Since a form has focus, IBusClient::FocusIn() should be called.
+ EXPECT_EQ(1U, GetClient()->focus_in_call_count_);
+ EXPECT_EQ(1U, GetClient()->set_cursor_location_call_count_);
+ // ui::TextInputClient::OnInputMethodChanged() should be called when
+ // ui::InputMethodIBus connects/disconnects to/from ibus-daemon and the
+ // current text input type is not NONE.
+ EXPECT_EQ(1U, on_input_method_changed_call_count_);
+}
+
+// Confirm that IBusClient::FocusIn is NOT called on "connected" if input_type_
+// is PASSWORD.
+TEST_F(InputMethodIBusTest, FocusIn_Password) {
+ ime_->Init(true);
+ EXPECT_EQ(0U, GetClient()->create_context_call_count_);
+ EXPECT_EQ(0U, on_input_method_changed_call_count_);
+ input_type_ = TEXT_INPUT_TYPE_PASSWORD;
+ ime_->OnTextInputTypeChanged(this);
+ GetClient()->is_connected_ = true;
+ g_signal_emit_by_name(GetBus(), "connected");
+ EXPECT_EQ(1U, GetClient()->create_context_call_count_);
+ // Since a form has focus, IBusClient::FocusIn() should NOT be called.
+ EXPECT_EQ(0U, GetClient()->focus_in_call_count_);
+ EXPECT_EQ(1U, on_input_method_changed_call_count_);
+}
+
+// Confirm that IBusClient::FocusOut is called as expected.
+TEST_F(InputMethodIBusTest, FocusOut_None) {
+ input_type_ = TEXT_INPUT_TYPE_TEXT;
+ GetClient()->is_connected_ = true;
+ ime_->Init(true);
+ EXPECT_EQ(1U, GetClient()->create_context_call_count_);
+ EXPECT_EQ(1U, GetClient()->focus_in_call_count_);
+ EXPECT_EQ(0U, GetClient()->focus_out_call_count_);
+ input_type_ = TEXT_INPUT_TYPE_NONE;
+ ime_->OnTextInputTypeChanged(this);
+ EXPECT_EQ(1U, GetClient()->focus_in_call_count_);
+ EXPECT_EQ(1U, GetClient()->focus_out_call_count_);
+}
+
+// Confirm that IBusClient::FocusOut is called as expected.
+TEST_F(InputMethodIBusTest, FocusOut_Password) {
+ input_type_ = TEXT_INPUT_TYPE_TEXT;
+ GetClient()->is_connected_ = true;
+ ime_->Init(true);
+ EXPECT_EQ(1U, GetClient()->create_context_call_count_);
+ EXPECT_EQ(1U, GetClient()->focus_in_call_count_);
+ EXPECT_EQ(0U, GetClient()->focus_out_call_count_);
+ input_type_ = TEXT_INPUT_TYPE_PASSWORD;
+ ime_->OnTextInputTypeChanged(this);
+ EXPECT_EQ(1U, GetClient()->focus_in_call_count_);
+ EXPECT_EQ(1U, GetClient()->focus_out_call_count_);
+}
+
+// Confirm that IBusClient::FocusOut is NOT called.
+TEST_F(InputMethodIBusTest, FocusOut_Url) {
+ input_type_ = TEXT_INPUT_TYPE_TEXT;
+ GetClient()->is_connected_ = true;
+ ime_->Init(true);
+ EXPECT_EQ(1U, GetClient()->create_context_call_count_);
+ EXPECT_EQ(1U, GetClient()->focus_in_call_count_);
+ EXPECT_EQ(0U, GetClient()->focus_out_call_count_);
+ input_type_ = TEXT_INPUT_TYPE_URL;
+ ime_->OnTextInputTypeChanged(this);
+ EXPECT_EQ(1U, GetClient()->focus_in_call_count_);
+ EXPECT_EQ(0U, GetClient()->focus_out_call_count_);
+}
+
+// Test if the new |caret_bounds_| is correctly sent to ibus-daemon.
+TEST_F(InputMethodIBusTest, OnCaretBoundsChanged) {
+ GetClient()->is_connected_ = true;
+ input_type_ = TEXT_INPUT_TYPE_TEXT;
+ ime_->Init(true);
+ EXPECT_EQ(0U, GetClient()->set_cursor_location_call_count_);
+ caret_bounds_ = gfx::Rect(1, 2, 3, 4);
+ ime_->OnCaretBoundsChanged(this);
+ EXPECT_EQ(1U, GetClient()->set_cursor_location_call_count_);
+ caret_bounds_ = gfx::Rect(0, 2, 3, 4);
+ ime_->OnCaretBoundsChanged(this);
+ EXPECT_EQ(2U, GetClient()->set_cursor_location_call_count_);
+ caret_bounds_ = gfx::Rect(0, 2, 3, 4); // unchanged
+ ime_->OnCaretBoundsChanged(this);
+ // Current InputMethodIBus implementation performs the IPC regardless of the
+ // bounds are changed or not.
+ EXPECT_EQ(3U, GetClient()->set_cursor_location_call_count_);
+}
+
+// TODO(yusukes): Write more tests, especially for key event functions.
+
+} // namespace ui
diff --git a/ui/base/ime/mock_ibus_client.cc b/ui/base/ime/mock_ibus_client.cc
new file mode 100644
index 0000000..f4ada55
--- /dev/null
+++ b/ui/base/ime/mock_ibus_client.cc
@@ -0,0 +1,240 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/ime/mock_ibus_client.h"
+
+#include <glib-object.h>
+
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+
+// Define a dummy IBusBus class. A real GObject is necessary here to make it
+// possible to send/receive GObject signals through the object.
+typedef struct _IBusBus {
+ GObject parent;
+} IBusBus;
+typedef struct _IBusBusClass {
+ GObjectClass parent;
+} IBusBusClass;
+G_DEFINE_TYPE(IBusBus, ibus_bus, G_TYPE_OBJECT)
+
+// Define a dummy IBusText class.
+typedef struct _IBusText {
+ GObject parent;
+} IBusText;
+typedef struct _IBusTextClass {
+ GObjectClass parent;
+} IBusTextClass;
+G_DEFINE_TYPE(IBusText, ibus_text, G_TYPE_OBJECT)
+
+// Define a dummy IBusInputContext class.
+typedef struct _IBusInputContext {
+ GObject parent;
+} IBusInputContext;
+typedef struct _IBusInputContextClass {
+ GObjectClass parent;
+} IBusInputContextClass;
+G_DEFINE_TYPE(IBusInputContext, ibus_input_context, G_TYPE_OBJECT)
+
+namespace {
+
+IBusBus* CreateBusObject() {
+ return reinterpret_cast<IBusBus*>(g_object_new(ibus_bus_get_type(), NULL));
+}
+
+IBusInputContext* CreateContextObject() {
+ return reinterpret_cast<IBusInputContext*>(
+ g_object_new(ibus_input_context_get_type(), NULL));
+}
+
+void AddVoidSignal(gpointer klass, const char* signal_name) {
+ g_signal_new(signal_name,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ 0, // class_offset
+ NULL, // accumulator
+ NULL, // accu_data
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+} // namespace
+
+static void ibus_bus_init(IBusBus* obj) {}
+static void ibus_bus_class_init(IBusBusClass* klass) {
+ AddVoidSignal(klass, "connected");
+ AddVoidSignal(klass, "disconnected");
+}
+static void ibus_text_init(IBusText* obj) {}
+static void ibus_text_class_init(IBusTextClass* klass) {}
+static void ibus_input_context_init(IBusInputContext* obj) {}
+static void ibus_input_context_class_init(IBusInputContextClass* klass) {
+ AddVoidSignal(klass, "show-preedit-text");
+ AddVoidSignal(klass, "hide-preedit-text");
+ AddVoidSignal(klass, "destroy");
+
+ g_signal_new("commit-text",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ ibus_text_get_type());
+
+ // TODO(yusukes): Support forward-key-event and update-preedit-text signals.
+ // To do that, we have to generate custom marshallers using glib-genmarshal.
+
+ /*
+ g_signal_new("forward-key-event",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ marshal_VOID__UINT_UNIT_UNIT,
+ G_TYPE_NONE,
+ 3,
+ G_TYPE_UINT,
+ G_TYPE_UINT,
+ G_TYPE_UINT);
+
+ g_signal_new("update-preedit-text",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ marshal_VOID__OBJECT_UINT_BOOLEAN,
+ G_TYPE_NONE,
+ 3,
+ IBUS_TYPE_TEXT,
+ G_TYPE_UINT,
+ G_TYPE_BOOLEAN);
+ */
+}
+
+namespace ui {
+namespace internal {
+
+MockIBusClient::MockIBusClient() {
+ ResetFlags();
+}
+
+MockIBusClient::~MockIBusClient() {
+ if (create_ic_request_.get() &&
+ (create_context_result_ == kCreateContextDelayed)) {
+ // The destructor is called after ui::InputMethodIBus is destructed. Check
+ // whether the new context created by CreateContextObject() is immediately
+ // destroyed by checking the counter in MockIBusClient::DestroyProxy().
+ const unsigned int initial_call_count = destroy_proxy_call_count_;
+ create_ic_request_->StoreOrAbandonInputContext(CreateContextObject());
+ DCHECK_EQ(initial_call_count + 1, destroy_proxy_call_count_);
+ }
+}
+
+IBusBus* MockIBusClient::GetConnection() {
+ g_type_init();
+ return CreateBusObject();
+}
+
+bool MockIBusClient::IsConnected(IBusBus* bus) {
+ return is_connected_;
+}
+
+void MockIBusClient::CreateContext(IBusBus* bus,
+ PendingCreateICRequest* request) {
+ ++create_context_call_count_;
+
+ switch (create_context_result_) {
+ case kCreateContextSuccess:
+ // Create a new context immediately.
+ request->StoreOrAbandonInputContext(CreateContextObject());
+ delete request;
+ break;
+ case kCreateContextFail:
+ // Emulate an IPC failure. Pass NULL to the request object.
+ request->StoreOrAbandonInputContext(NULL);
+ delete request;
+ break;
+ case kCreateContextNoResponse:
+ // Emulate ibus-daemon hang-up. Do not call StoreOrAbandonInputContext.
+ create_ic_request_.reset(request);
+ break;
+ case kCreateContextDelayed:
+ // Emulate overloaded ibus-daemon. Call StoreOrAbandonInputContext later.
+ create_ic_request_.reset(request);
+ break;
+ }
+}
+
+void MockIBusClient::DestroyProxy(IBusInputContext* context) {
+ ++destroy_proxy_call_count_;
+ g_signal_emit_by_name(context, "destroy");
+}
+
+void MockIBusClient::SetCapabilities(IBusInputContext* context) {
+ ++set_capabilities_call_count_;
+}
+
+void MockIBusClient::FocusIn(IBusInputContext* context) {
+ ++focus_in_call_count_;
+}
+
+void MockIBusClient::FocusOut(IBusInputContext* context) {
+ ++focus_out_call_count_;
+}
+
+void MockIBusClient::Reset(IBusInputContext* context) {
+ ++reset_call_count_;
+}
+
+void MockIBusClient::SetCursorLocation(IBusInputContext* context,
+ int32 x,
+ int32 y,
+ int32 w,
+ int32 h) {
+ ++set_cursor_location_call_count_;
+}
+
+void MockIBusClient::SendKeyEvent(IBusInputContext* context,
+ uint32 keyval,
+ uint32 keycode,
+ uint32 state,
+ PendingKeyEvent* pending_key) {
+ // TODO(yusukes): implement this function.
+}
+
+void MockIBusClient::ExtractCompositionText(IBusText* text,
+ guint cursor_position,
+ CompositionText* out_composition) {
+ *out_composition = composition_text_;
+}
+
+string16 MockIBusClient::ExtractCommitText(IBusText* text) {
+ return commit_text_;
+}
+
+void MockIBusClient::ResetFlags() {
+ create_context_result_ = kCreateContextSuccess;
+ create_ic_request_.reset();
+
+ is_connected_ = false;
+ composition_text_.Clear();
+ commit_text_.clear();
+
+ create_context_call_count_ = 0;
+ destroy_proxy_call_count_ = 0;
+ set_capabilities_call_count_ = 0;
+ focus_in_call_count_ = 0;
+ focus_out_call_count_ = 0;
+ reset_call_count_ = 0;
+ set_cursor_location_call_count_ = 0;
+}
+
+} // namespace internal
+} // namespace ui
diff --git a/ui/base/ime/mock_ibus_client.h b/ui/base/ime/mock_ibus_client.h
new file mode 100644
index 0000000..56c2e22
--- /dev/null
+++ b/ui/base/ime/mock_ibus_client.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_IME_MOCK_IBUS_CLIENT_H_
+#define UI_BASE_IME_MOCK_IBUS_CLIENT_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/ime/composition_text.h"
+#include "ui/base/ime/ibus_client.h"
+
+namespace ui {
+namespace internal {
+
+// A dummy IBusClient implementation for testing which requires neither
+// ibus-daemon nor ibus header files.
+class UI_EXPORT MockIBusClient : public IBusClient {
+public:
+ MockIBusClient();
+ virtual ~MockIBusClient();
+
+ // ui::internal::IBusClient overrides:
+ virtual IBusBus* GetConnection() OVERRIDE;
+ virtual bool IsConnected(IBusBus* bus) OVERRIDE;
+ virtual void CreateContext(IBusBus* bus,
+ PendingCreateICRequest* request) OVERRIDE;
+ virtual void DestroyProxy(IBusInputContext* context) OVERRIDE;
+ virtual void SetCapabilities(IBusInputContext* context) OVERRIDE;
+ virtual void FocusIn(IBusInputContext* context) OVERRIDE;
+ virtual void FocusOut(IBusInputContext* context) OVERRIDE;
+ virtual void Reset(IBusInputContext* context) OVERRIDE;
+ virtual void SetCursorLocation(IBusInputContext* context,
+ int32 x,
+ int32 y,
+ int32 w,
+ int32 h) OVERRIDE;
+ virtual void SendKeyEvent(IBusInputContext* context,
+ uint32 keyval,
+ uint32 keycode,
+ uint32 state,
+ PendingKeyEvent* pending_key) OVERRIDE;
+ virtual void ExtractCompositionText(
+ IBusText* text,
+ guint cursor_position,
+ CompositionText* out_composition) OVERRIDE;
+ virtual string16 ExtractCommitText(IBusText* text) OVERRIDE;
+
+ // See comments in CreateContext().
+ enum CreateContextResult {
+ kCreateContextSuccess,
+ kCreateContextFail,
+ kCreateContextNoResponse,
+ kCreateContextDelayed,
+ };
+
+ // Resets all member variables below to the initial state.
+ void ResetFlags();
+
+ // Controls the behavior of CreateContext().
+ CreateContextResult create_context_result_;
+ scoped_ptr<PendingCreateICRequest> create_ic_request_;
+
+ // A value which IsConnected() will return.
+ bool is_connected_;
+ // A text which ExtractCompositionText() will return.
+ CompositionText composition_text_;
+ // A text which ExtractCommitText() will return.
+ string16 commit_text_;
+
+ unsigned int create_context_call_count_;
+ unsigned int destroy_proxy_call_count_;
+ unsigned int set_capabilities_call_count_;
+ unsigned int focus_in_call_count_;
+ unsigned int focus_out_call_count_;
+ unsigned int reset_call_count_;
+ unsigned int set_cursor_location_call_count_;
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(MockIBusClient);
+};
+
+} // namespace internal
+} // namespace ui
+
+#endif // UI_BASE_IME_MOCK_IBUS_CLIENT_H_
diff --git a/ui/ui.gyp b/ui/ui.gyp
index dcaa2a9..6568234 100644
--- a/ui/ui.gyp
+++ b/ui/ui.gyp
@@ -136,6 +136,9 @@
'base/ime/composition_text.cc',
'base/ime/composition_text.h',
'base/ime/composition_underline.h',
+ 'base/ime/ibus_client.h',
+ 'base/ime/ibus_client_impl.cc',
+ 'base/ime/ibus_client_impl.h',
'base/ime/input_method.h',
'base/ime/input_method_base.cc',
'base/ime/input_method_base.h',
@@ -144,6 +147,8 @@
'base/ime/input_method_factory.h',
'base/ime/input_method_ibus.cc',
'base/ime/input_method_ibus.h',
+ 'base/ime/mock_ibus_client.cc',
+ 'base/ime/mock_ibus_client.h',
'base/ime/mock_input_method.cc',
'base/ime/mock_input_method.h',
'base/ime/text_input_client.cc',
@@ -375,10 +380,17 @@
],
}, { # use_aura!=1
'sources!': [
+ 'base/ime/character_composer.cc',
+ 'base/ime/character_composer.h',
+ 'base/ime/ibus_client.h',
+ 'base/ime/ibus_client_impl.cc',
+ 'base/ime/ibus_client_impl.h',
'base/ime/input_method_factory.cc',
'base/ime/input_method_factory.h',
'base/ime/input_method_ibus.cc',
'base/ime/input_method_ibus.h',
+ 'base/ime/mock_ibus_client.cc',
+ 'base/ime/mock_ibus_client.h',
'base/ime/mock_input_method.cc',
'base/ime/mock_input_method.h',
'gfx/native_theme_aura.cc',
@@ -404,8 +416,8 @@
],
}, { # else: use_ibus != 1
'sources/': [
- ['exclude', 'base/ime/input_method_ibus.cc'],
- ['exclude', 'base/ime/input_method_ibus.h'],
+ ['exclude', 'base/ime/ibus_client_impl.cc'],
+ ['exclude', 'base/ime/ibus_client_impl.h'],
],
}],
@@ -487,12 +499,6 @@
['include', 'gfx/linux_util.h'],
],
}],
- ['use_ibus != 1', {
- 'sources/': [
- ['exclude', 'base/ime/character_composer.cc'],
- ['exclude', 'base/ime/character_composer.h'],
- ],
- }],
['OS=="win"', {
'sources': [
'gfx/gdi_util.cc',
@@ -592,6 +598,12 @@
'base/keycodes/keyboard_code_conversion_x.cc',
'base/keycodes/keyboard_code_conversion_x.h',
'base/x/',
+ 'base/ime/input_method_ibus.cc',
+ 'base/ime/input_method_ibus.h',
+ 'base/ime/mock_ibus_client.cc',
+ 'base/ime/mock_ibus_client.h',
+ 'base/ime/character_composer.cc',
+ 'base/ime/character_composer.h',
],
}],
['chromeos==1', {
diff --git a/ui/ui_unittests.gypi b/ui/ui_unittests.gypi
index 607a6b4..c3f2e7e 100644
--- a/ui/ui_unittests.gypi
+++ b/ui/ui_unittests.gypi
@@ -55,6 +55,7 @@
'base/gtk/gtk_expanded_container_unittest.cc',
'base/gtk/gtk_im_context_util_unittest.cc',
'base/ime/character_composer_unittest.cc',
+ 'base/ime/input_method_ibus_unittest.cc',
'base/l10n/l10n_util_mac_unittest.mm',
'base/l10n/l10n_util_unittest.cc',
'base/models/list_model_unittest.cc',
@@ -155,6 +156,12 @@
}],
],
}],
+ ['use_x11 == 0', {
+ 'sources!': [
+ 'base/ime/character_composer_unittest.cc',
+ 'base/ime/input_method_ibus_unittest.cc',
+ ],
+ }],
['toolkit_uses_gtk == 1', {
'sources': [
'base/dragdrop/gtk_dnd_util_unittest.cc',
@@ -173,10 +180,10 @@
'base/view_prop_unittest.cc',
'gfx/screen_unittest.cc',
],
- }],
- ['use_ibus != 1', {
+ }, { # else: use_aura != 1
'sources/': [
['exclude', 'base/ime/character_composer_unittest.cc'],
+ ['exclude', 'base/ime/input_method_ibus_unittest.cc'],
],
}],
],