diff options
-rw-r--r-- | ui/base/ime/composition_text.h | 18 | ||||
-rw-r--r-- | ui/base/ime/composition_underline.h | 13 | ||||
-rw-r--r-- | ui/base/ime/ibus_client.h | 109 | ||||
-rw-r--r-- | ui/base/ime/ibus_client_impl.cc | 242 | ||||
-rw-r--r-- | ui/base/ime/ibus_client_impl.h | 58 | ||||
-rw-r--r-- | ui/base/ime/input_method_ibus.cc | 355 | ||||
-rw-r--r-- | ui/base/ime/input_method_ibus.h | 43 | ||||
-rw-r--r-- | ui/base/ime/input_method_ibus_unittest.cc | 452 | ||||
-rw-r--r-- | ui/base/ime/mock_ibus_client.cc | 240 | ||||
-rw-r--r-- | ui/base/ime/mock_ibus_client.h | 87 | ||||
-rw-r--r-- | ui/ui.gyp | 28 | ||||
-rw-r--r-- | ui/ui_unittests.gypi | 11 |
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_ @@ -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'], ], }], ], |