diff options
author | oshima@google.com <oshima@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-12 01:00:00 +0000 |
---|---|---|
committer | oshima@google.com <oshima@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-12 01:00:00 +0000 |
commit | 45510d9022d636a163762b67bf36a35b1c36cd3a (patch) | |
tree | dfaefde0ac5bebd02183fc0e08c0b91118105d09 /views/ime | |
parent | 33c90eba54629044ddde7cc3aad60b4318d7688a (diff) | |
download | chromium_src-45510d9022d636a163762b67bf36a35b1c36cd3a.zip chromium_src-45510d9022d636a163762b67bf36a35b1c36cd3a.tar.gz chromium_src-45510d9022d636a163762b67bf36a35b1c36cd3a.tar.bz2 |
Add input method support for views and integrate ibus input framework
BUG=none
TEST=tested on Linux desktop
oshima landed for penghuang
Review URL: http://codereview.chromium.org/6480036
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@74709 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views/ime')
-rw-r--r-- | views/ime/ibus_ime_context.cc | 398 | ||||
-rw-r--r-- | views/ime/ime_context.cc | 88 | ||||
-rw-r--r-- | views/ime/ime_context.h | 240 |
3 files changed, 726 insertions, 0 deletions
diff --git a/views/ime/ibus_ime_context.cc b/views/ime/ibus_ime_context.cc new file mode 100644 index 0000000..130d863 --- /dev/null +++ b/views/ime/ibus_ime_context.cc @@ -0,0 +1,398 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <ibus.h> + +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "gfx/rect.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/base/gtk/gtk_signal.h" +#include "ui/base/keycodes/keyboard_code_conversion_gtk.h" +#include "views/events/event.h" +#include "views/ime/ime_context.h" + +namespace { + +class IBusIMEContext; + +struct ProcessKeyEventData { + ProcessKeyEventData(IBusIMEContext* context, + const views::KeyEvent& event) + : context(context), + event(event.type(), event.key_code(), event.flags()) { + } + + IBusIMEContext* context; + views::KeyEvent event; +}; + +// Implements IMEContext to integrate ibus input method framework +class IBusIMEContext : public views::IMEContext { + public: + explicit IBusIMEContext(views::View* view); + virtual ~IBusIMEContext(); + + // views::IMEContext implementations: + virtual void Focus(); + virtual void Blur(); + virtual void Reset(); + virtual bool FilterKeyEvent(const views::KeyEvent& event); + virtual void SetCursorLocation(const gfx::Rect& caret_rect); + virtual void SetSurrounding(const string16& text, int cursor_pos); + + private: + void CreateContext(); + void DestroyContext(); + + // Event handlers for IBusBus: + CHROMEG_CALLBACK_0(IBusIMEContext, void, OnConnected, IBusBus*); + CHROMEG_CALLBACK_0(IBusIMEContext, void, OnDisconnected, IBusBus*); + + // Event handlers for IBusIMEContext: + CHROMEG_CALLBACK_1(IBusIMEContext, void, OnCommitText, + IBusInputContext*, IBusText*); + CHROMEG_CALLBACK_3(IBusIMEContext, void, OnForwardKeyEvent, + IBusInputContext*, guint, guint, guint); + CHROMEG_CALLBACK_3(IBusIMEContext, void, OnUpdatePreeditText, + IBusInputContext*, IBusText*, guint, gboolean); + CHROMEG_CALLBACK_0(IBusIMEContext, void, OnShowPreeditText, + IBusInputContext*); + CHROMEG_CALLBACK_0(IBusIMEContext, void, OnHidePreeditText, + IBusInputContext*); + CHROMEG_CALLBACK_0(IBusIMEContext, void, OnEnable, IBusInputContext*); + CHROMEG_CALLBACK_0(IBusIMEContext, void, OnDisable, IBusInputContext*); + CHROMEG_CALLBACK_0(IBusIMEContext, void, OnDestroy, IBusInputContext*); + + static void ProcessKeyEventDone(IBusInputContext* context, + GAsyncResult* res, + ProcessKeyEventData *data); + + IBusInputContext* context_; + bool is_focused_; + gfx::Rect caret_rect_; + + static IBusBus* bus_; + + DISALLOW_COPY_AND_ASSIGN(IBusIMEContext); +}; + +IBusBus* IBusIMEContext::bus_ = NULL; + +IBusIMEContext::IBusIMEContext(views::View* view) + : views::IMEContext(view), + context_(NULL), + is_focused_(false) { + // init ibus + if (!bus_) { + ibus_init(); + bus_ = ibus_bus_new(); + } + + // connect bus signals + g_signal_connect(bus_, + "connected", + G_CALLBACK(OnConnectedThunk), + this); + g_signal_connect(bus_, + "disconnected", + G_CALLBACK(OnDisconnectedThunk), + this); + + if (ibus_bus_is_connected(bus_)) + CreateContext(); +} + +IBusIMEContext::~IBusIMEContext() { + // disconnect bus signals + g_signal_handlers_disconnect_by_func(bus_, + reinterpret_cast<gpointer>(OnConnectedThunk), this); + g_signal_handlers_disconnect_by_func(bus_, + reinterpret_cast<gpointer>(OnDisconnectedThunk), this); + + DestroyContext(); +} + +void IBusIMEContext::Focus() { + if (is_focused_) + return; + is_focused_ = true; + + if (context_) + ibus_input_context_focus_in(context_); +} + +void IBusIMEContext::Blur() { + if (!is_focused_) + return; + + is_focused_ = false; + + if (context_) + ibus_input_context_focus_out(context_); +} + +void IBusIMEContext::Reset() { + if (context_) + ibus_input_context_reset(context_); +} + +bool IBusIMEContext::FilterKeyEvent(const views::KeyEvent& event) { + guint keyval = ui::GdkKeyCodeForWindowsKeyCode(event.key_code(), + event.IsShiftDown() ^ event.IsCapsLockDown()); + + if (context_) { + guint modifiers = 0; + + if (event.type() == ui::ET_KEY_RELEASED) + modifiers |= IBUS_RELEASE_MASK; + + if (event.IsShiftDown()) + modifiers |= IBUS_SHIFT_MASK; + if (event.IsControlDown()) + modifiers |= IBUS_CONTROL_MASK; + if (event.IsAltDown()) + modifiers |= IBUS_MOD1_MASK; + if (event.IsCapsLockDown()) + modifiers |= IBUS_LOCK_MASK; + + ibus_input_context_process_key_event(context_, + keyval, 0, modifiers, + -1, + NULL, + reinterpret_cast<GAsyncReadyCallback>(ProcessKeyEventDone), + new ProcessKeyEventData(this, event)); + return true; + } + + return false; +} + +void IBusIMEContext::SetCursorLocation(const gfx::Rect& caret_rect) { + if (context_ && caret_rect_ != caret_rect) { + caret_rect_ = caret_rect; + ibus_input_context_set_cursor_location(context_, + caret_rect_.x(), + caret_rect_.y(), + caret_rect_.width(), + caret_rect_.height()); + } +} + +void IBusIMEContext::SetSurrounding(const string16& text, + int cursor_pos) { + // TODO(penghuang) support surrounding +} + +void IBusIMEContext::CreateContext() { + DCHECK(bus_ != NULL); + DCHECK(ibus_bus_is_connected(bus_)); + + context_ = ibus_bus_create_input_context(bus_, "chrome"); + + // connect input context signals + g_signal_connect(context_, + "commit-text", + G_CALLBACK(OnCommitTextThunk), + this); + g_signal_connect(context_, + "forward-key-event", + G_CALLBACK(OnForwardKeyEventThunk), + this); + g_signal_connect(context_, + "update-preedit-text", + G_CALLBACK(OnUpdatePreeditTextThunk), + this); + g_signal_connect(context_, + "show-preedit-text", + G_CALLBACK(OnShowPreeditTextThunk), + this); + g_signal_connect(context_, + "hide-preedit-text", + G_CALLBACK(OnHidePreeditTextThunk), + this); + g_signal_connect(context_, + "enabled", + G_CALLBACK(OnEnableThunk), + this); + g_signal_connect(context_, + "disabled", + G_CALLBACK(OnDisableThunk), + this); + g_signal_connect(context_, + "destroy", + G_CALLBACK(OnDestroyThunk), + this); + + guint32 caps = IBUS_CAP_PREEDIT_TEXT | + IBUS_CAP_FOCUS | + IBUS_CAP_SURROUNDING_TEXT; + ibus_input_context_set_capabilities(context_, caps); + + if (is_focused_) + ibus_input_context_focus_in(context_); + + ibus_input_context_set_cursor_location(context_, + caret_rect_.x(), + caret_rect_.y(), + caret_rect_.width(), + caret_rect_.height()); +} + +void IBusIMEContext::DestroyContext() { + if (!context_) + return; + + ibus_proxy_destroy(reinterpret_cast<IBusProxy *>(context_)); + + DCHECK(!context_); +} + +void IBusIMEContext::OnConnected(IBusBus *bus) { + DCHECK(bus_ == bus); + DCHECK(ibus_bus_is_connected(bus_)); + + if (context_) + DestroyContext(); + CreateContext(); +} + +void IBusIMEContext::OnDisconnected(IBusBus *bus) { +} + +void IBusIMEContext::OnCommitText(IBusInputContext *context, + IBusText* text) { + DCHECK(context_ == context); + views::IMEContext::CommitText(UTF8ToUTF16(text->text)); +} + +void IBusIMEContext::OnForwardKeyEvent(IBusInputContext *context, + guint keyval, + guint keycode, + guint state) { + DCHECK(context_ == context); + + int flags = 0; + + if (state & IBUS_LOCK_MASK) + flags |= ui::EF_CAPS_LOCK_DOWN; + if (state & IBUS_CONTROL_MASK) + flags |= ui::EF_CONTROL_DOWN; + if (state & IBUS_SHIFT_MASK) + flags |= ui::EF_SHIFT_DOWN; + if (state & IBUS_MOD1_MASK) + flags |= ui::EF_ALT_DOWN; + if (state & IBUS_BUTTON1_MASK) + flags |= ui::EF_LEFT_BUTTON_DOWN; + if (state & IBUS_BUTTON2_MASK) + flags |= ui::EF_MIDDLE_BUTTON_DOWN; + if (state & IBUS_BUTTON3_MASK) + flags |= ui::EF_RIGHT_BUTTON_DOWN; + + ForwardKeyEvent(views::KeyEvent( + state & IBUS_RELEASE_MASK ? + ui::ET_KEY_RELEASED : ui::ET_KEY_PRESSED, + ui::WindowsKeyCodeForGdkKeyCode(keyval), + flags)); +} + +void IBusIMEContext::OnUpdatePreeditText(IBusInputContext *context, + IBusText* text, + guint cursor_pos, + gboolean visible) { + DCHECK(context_ == context); + DCHECK(IBUS_IS_TEXT(text)); + + if (visible) { + views::CompositionAttributeList attributes; + IBusAttrList *attrs = text->attrs; + if (attrs) { + int i = 0; + while (1) { + IBusAttribute *attr = ibus_attr_list_get(attrs, i++); + if (!attr) + break; + if (attr->type == IBUS_ATTR_TYPE_UNDERLINE || + attr->type == IBUS_ATTR_TYPE_BACKGROUND) { + views::CompositionAttribute attribute(attr->start_index, + attr->end_index, + SK_ColorBLACK, + false); + if (attr->type == IBUS_ATTR_TYPE_BACKGROUND) { + attribute.thick = true; + } else if (attr->type == IBUS_ATTR_TYPE_UNDERLINE) { + if (attr->value == IBUS_ATTR_UNDERLINE_DOUBLE) { + attribute.thick = true; + } else if (attr->value == IBUS_ATTR_UNDERLINE_ERROR) { + attribute.color = SK_ColorRED; + } + } + attributes.push_back(attribute); + } + } + } + + SetComposition(UTF8ToUTF16(text->text), + attributes, + cursor_pos); + } else { + EndComposition(); + } +} + +void IBusIMEContext::OnShowPreeditText(IBusInputContext *context) { + DCHECK(context_ == context); +} + +void IBusIMEContext::OnHidePreeditText(IBusInputContext *context) { + DCHECK(context_ == context); + + EndComposition(); +} + +void IBusIMEContext::OnEnable(IBusInputContext *context) { + DCHECK(context_ == context); +} + +void IBusIMEContext::OnDisable(IBusInputContext *context) { + DCHECK(context_ == context); +} + +void IBusIMEContext::OnDestroy(IBusInputContext *context) { + DCHECK(context_ == context); + + g_object_unref(context_); + context_ = NULL; + + EndComposition(); +} + +void IBusIMEContext::ProcessKeyEventDone(IBusInputContext *context, + GAsyncResult* res, + ProcessKeyEventData *data) { + DCHECK(data->context->context_ == context); + + gboolean processed = FALSE; + + if (!ibus_input_context_process_key_event_finish(context, + res, + &processed, + NULL)) + processed = FALSE; + + if (processed == FALSE) + data->context->ForwardKeyEvent(data->event); + + delete data; +} + +} // namespace + +namespace views { + +IMEContext* IMEContext::Create(View* view) { + return new IBusIMEContext(view); +} + +} // namespace views diff --git a/views/ime/ime_context.cc b/views/ime/ime_context.cc new file mode 100644 index 0000000..033e3be --- /dev/null +++ b/views/ime/ime_context.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/ime/ime_context.h" + +namespace { + +#ifdef USE_DUMMY_IME_CONTEXT +class DummyIMEContext : public views::IMEContext { + public: + explicit DummyIMEContext(views::View* view) + : views::IMEContext(view) {} + virtual ~DummyIMEContext() {} + + // views::IMEContext implementations: + virtual void Focus() {} + virtual void Blur() {} + virtual void Reset() {} + virtual bool FilterKeyEvent(const views::KeyEvent& event) { + return false; + } + virtual void SetCursorLocation(const gfx::Rect& caret_rect) {} + virtual void SetSurrounding(const string16& text, int cursor_pos) {} + + private: + DISALLOW_COPY_AND_ASSIGN(DummyIMEContext); +}; +#endif // USE_DUMMY_IME_CONTEXT + +} // namespace + +namespace views { + +IMEContext::IMEContext(View* view) + : view_(view) , + commit_text_listener_(NULL), + composition_listener_(NULL), + forward_key_event_listener_(NULL), + surrounding_listener_(NULL) { +} + +void IMEContext::CommitText(const string16& text) { + if (commit_text_listener_) + commit_text_listener_->OnCommitText(this, text); +} + +void IMEContext::StartComposition() { + if (composition_listener_) + composition_listener_->OnStartComposition(this); +} + +void IMEContext::EndComposition() { + if (composition_listener_) + composition_listener_->OnEndComposition(this); +} + +void IMEContext::SetComposition(const string16& text, + const CompositionAttributeList& attributes, + uint32 cursor_pos) { + if (composition_listener_) + composition_listener_->OnSetComposition(this, text, attributes, cursor_pos); +} + +void IMEContext::ForwardKeyEvent(const KeyEvent& event) { + if (forward_key_event_listener_) + forward_key_event_listener_->OnForwardKeyEvent(this, event); +} + +bool IMEContext::SetSurroundingActive(bool active) { + if (surrounding_listener_) + return surrounding_listener_->OnSetSurroundingActive(this, active); + return false; +} + +bool IMEContext::DeleteSurrounding(int offset, int nchars) { + if (surrounding_listener_) + return surrounding_listener_->OnDeleteSurrounding(this, offset, nchars); + return false; +} + +#ifdef USE_DUMMY_IME_CONTEXT +IMEContext* IMEContext::Create(View* view) { + return new DummyIMEContext(view); +} +#endif // USE_DUMMY_IME_CONTEXT + +} // namespace views diff --git a/views/ime/ime_context.h b/views/ime/ime_context.h new file mode 100644 index 0000000..7261b4e --- /dev/null +++ b/views/ime/ime_context.h @@ -0,0 +1,240 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_IME_IME_CONTEXT_H_ +#define VIEWS_IME_IME_CONTEXT_H_ +#pragma once + +#include <vector> + +#include "base/basictypes.h" +#include "base/string16.h" +#include "third_party/skia/include/core/SkColor.h" + +namespace gfx { +class Rect; +} + +namespace views { + +class IMEContext; +class KeyEvent; +class View; + +// Duplicate WebKit::WebCompositionUnderline +struct CompositionUnderline { + CompositionUnderline() + : start_offset(0), + end_offset(0), + color(0), + thick(false) {} + + CompositionUnderline(uint32 s, uint32 e, SkColor c, bool t) + : start_offset(s), + end_offset(e), + color(c), + thick(t) {} + + uint32 start_offset; + uint32 end_offset; + SkColor color; + bool thick; +}; + +// WebKit only supports underline attribue for composition, so we use +// CompositionUnderline as CompositionAttribute right now. +typedef struct CompositionUnderline CompositionAttribute; +typedef std::vector<CompositionAttribute> CompositionAttributeList; + +// TODO(penghuang) more attributes (background, foreground color and etc) +// class CompositionAttribute { +// public: +// enum CompositionAttributeType{ +// CAT_UNDERLINE = 1, +// CAT_FORGROUND = 2, +// CAT_BACKGRUND = 3, +// }; +// +// CompositionAttributeType GetType() const { return type_; } +// unsigned GetStartOffset() const { return start_offset_; } +// unsigned GetEndOffset() const { return end_offset_; } +// unsigned GetValue() const { return value_; } +// +// private: +// CompositionAttributeType type_; +// unsigned start_offset_; +// unsigned end_offset_; +// unsigned value_; +// }; + +// CommitTextListener is notified when a text is commited from the context. +class CommitTextListener { + public: + virtual void OnCommitText(IMEContext* sender, + const string16& text) = 0; + + protected: + virtual ~CommitTextListener() {} +}; + +// CompositionListener is notified when composition of the context is changed. +class CompositionListener { + public: + virtual void OnStartComposition(IMEContext* sender) = 0; + virtual void OnEndComposition(IMEContext* sender) = 0; + virtual void OnSetComposition(IMEContext* sender, + const string16& text, + const CompositionAttributeList& attributes, + uint32 cursor_pos) = 0; + + protected: + virtual ~CompositionListener() {} +}; + +// CompositionListener is notified when a key event is forwarded from +// the context. +class ForwardKeyEventListener { + public: + virtual void OnForwardKeyEvent(IMEContext* sender, + const KeyEvent& event) = 0; + + protected: + virtual ~ForwardKeyEventListener() {} +}; + +// SurroundingListener is notified when an input method context needs to +// manipulate the text surround the input cursor. If associated view supports +// surrounding, it should set the listener to input method context. Some input +// methods generate different result depends on the chars before or after the +// input cursor. +// +// For example: +// Some Korean input method: +// Key events Unicode char +// 1 'C' +U314a +// 2 'K' +U314f +// 3 'C' 'K' +Ucc28 +// +// In case 2 and 3, when users press 'K', the input method will check the char +// before input cursor. If the char is +U314a, it will remove the char and +// insert a new char +Ucc28. If no char before input cursor, it will insert char +// +U314f. +// +// See also OnSetSurroundingActive() and OnDeleteSurrounding(). +class SurroundingListener { + public: + // Activate or inactivate surrounding support. When surrounding is activated, + // The assoicated view should call SetSurrounding() to notify any changes of + // text surround the input cursor. + // Return true if associated view can support surrounding. + virtual bool OnSetSurroundingActive(IMEContext* sender, + bool activate) = 0; + + // Delete a picse of text surround the input cursor. + // Return true if associated view delete the surrounding text successfully. + virtual bool OnDeleteSurrounding(IMEContext* sender, + int offset, + int chars) = 0; + protected: + virtual ~SurroundingListener() {} +}; + +class IMEContext { + public: + virtual ~IMEContext() {} + + // Set associated view. + void set_view(View* view) { view_ = view; } + + // Get associated view. + const View* view() const { return view_; } + + // Set a listener to receive a callback when im context commits a text. + void set_commit_text_listener(CommitTextListener* listener) { + commit_text_listener_ = listener; + } + + // Set a listener to receive a callback when im context changes composition. + void set_composition_listener(CompositionListener* listener) { + composition_listener_ = listener; + } + + // Set a listener to receive a callback when im context forwards a key event. + void set_forward_key_event_listener(ForwardKeyEventListener* listener) { + forward_key_event_listener_ = listener; + } + + // Set a listener to receive a callback when im context need operater + // surrounding. + void set_surrounding_listener(SurroundingListener* listener) { + surrounding_listener_ = listener; + } + + // Tell the context it got/lost focus. + virtual void Focus() = 0; + virtual void Blur() = 0; + + // Reset context, context will be reset to initial state. + virtual void Reset() = 0; + + // Filter key event, returns true, if the key event is handled, + // associated widget should ignore it. + virtual bool FilterKeyEvent(const views::KeyEvent& event) = 0; + + // Set text input cursor location on screen. + virtual void SetCursorLocation(const gfx::Rect& caret_rect) = 0; + + // Set surrounding context. + virtual void SetSurrounding(const string16& text, + int cursor_pos) = 0; + + // Create an im context. + static IMEContext* Create(View* view); + + protected: + explicit IMEContext(View* view); + + // Commit text. + void CommitText(const string16& text); + + // Start compostion. + void StartComposition(); + + // End composition. + void EndComposition(); + + // Set composition. + void SetComposition(const string16& text, + const CompositionAttributeList& attributes, + uint32 cursor_pos); + + // Forward key event. + void ForwardKeyEvent(const KeyEvent& event); + + // Notify the associated view whether or not the input context needs + // surrounding. When surrounding is activated, associated view should + // call SetSurrounding() to update any changes of text arround the input + // cursor. + // Return true if associated view can support surrounding. + bool SetSurroundingActive(bool activate); + + // Delete surrouding arround cursor. Return true, if it is handled. + bool DeleteSurrounding(int offset, int nchars); + + private: + // Client view. + View* view_; + + // Listeners: + CommitTextListener* commit_text_listener_; + CompositionListener* composition_listener_; + ForwardKeyEventListener* forward_key_event_listener_; + SurroundingListener* surrounding_listener_; + + DISALLOW_COPY_AND_ASSIGN(IMEContext); +}; + +} // namespace views + +#endif // VIEWS_IME_IM_CONTEXT_H_ |