Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(180)

Side by Side Diff: views/ime/input_method_gtk.cc

Issue 6688049: New InputMethod api for Views. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Add InputMethodWin. Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "views/ime/input_method_gtk.h"
6
7 #include <gdk/gdk.h>
8 #include <gdk/gdkkeysyms.h>
9
10 #include <algorithm>
11
12 #include "base/basictypes.h"
13 #include "base/logging.h"
14 #include "base/string_util.h"
15 #include "base/third_party/icu/icu_utf.h"
16 #include "base/utf_string_conversions.h"
17 #include "ui/base/gtk/event_synthesis_gtk.h"
18 #include "ui/base/gtk/gtk_im_context_util.h"
19 #include "ui/base/keycodes/keyboard_codes.h"
20 #include "ui/base/keycodes/keyboard_code_conversion_gtk.h"
21 #include "views/events/event.h"
22 #include "views/widget/widget.h"
23
24 namespace views {
25
26 InputMethodGtk::InputMethodGtk(internal::InputMethodDelegate* delegate)
27 : delegate_(delegate),
28 widget_(NULL),
29 focused_view_(NULL),
30 context_(NULL),
31 context_simple_(NULL),
32 widget_realize_id_(0),
33 widget_unrealize_id_(0),
34 widget_focus_in_id_(0),
35 widget_focus_out_id_(0),
36 widget_focused_(false),
37 context_focused_(false),
38 handling_key_event_(false),
39 composing_text_(false),
40 composition_changed_(false),
41 suppress_next_result_(false) {
42 }
43
44 InputMethodGtk::~InputMethodGtk() {
45 if (widget_) {
46 widget_->GetFocusManager()->RemoveFocusChangeListener(this);
47 GtkWidget* native_view = widget_->GetNativeView();
48 if (native_view) {
49 g_signal_handler_disconnect(native_view, widget_realize_id_);
50 g_signal_handler_disconnect(native_view, widget_unrealize_id_);
51 g_signal_handler_disconnect(native_view, widget_focus_in_id_);
52 g_signal_handler_disconnect(native_view, widget_focus_out_id_);
53 }
54 widget_ = NULL;
55 }
56 if (context_) {
57 g_object_unref(context_);
58 context_ = NULL;
59 }
60 if (context_simple_) {
61 g_object_unref(context_simple_);
62 context_simple_ = NULL;
63 }
64 }
65
66 void InputMethodGtk::set_delegate(internal::InputMethodDelegate* delegate) {
67 delegate_ = delegate;
oshima 2011/03/21 20:30:56 can delegate be NULL? DCHECK if not.
James Su 2011/03/21 21:21:16 Done.
68 }
69
70 void InputMethodGtk::Init(Widget* widget) {
71 DCHECK(widget);
72 DCHECK(GTK_IS_WIDGET(widget->GetNativeView()));
73 DCHECK(widget->GetFocusManager());
74
75 if (widget_) {
76 NOTREACHED() << "The input method is already initialized.";
77 return;
78 }
79
80 widget_ = widget;
81 widget->GetFocusManager()->AddFocusChangeListener(this);
82
83 widget_realize_id_ =
84 g_signal_connect(widget->GetNativeView(), "realize",
85 G_CALLBACK(OnWidgetRealizeThunk), this);
86 widget_unrealize_id_ =
87 g_signal_connect(widget->GetNativeView(), "unrealize",
88 G_CALLBACK(OnWidgetUnrealizeThunk), this);
89 widget_focus_in_id_ =
90 g_signal_connect(widget->GetNativeView(), "focus-in-event",
91 G_CALLBACK(OnWidgetFocusInThunk), this);
92 widget_focus_out_id_ =
93 g_signal_connect(widget->GetNativeView(), "focus-out-event",
94 G_CALLBACK(OnWidgetFocusOutThunk), this);
95
96 context_ = gtk_im_multicontext_new();
97 context_simple_ = gtk_im_context_simple_new();
98
99 // context_ and context_simple_ share the same callback handlers.
100 // All data come from them are treated equally.
101 // context_ is for full input method support.
102 // context_simple_ is for supporting dead/compose keys when input method is
103 // disabled, eg. in password input box.
104 g_signal_connect(context_, "commit",
105 G_CALLBACK(OnCommitThunk), this);
106 g_signal_connect(context_, "preedit_start",
107 G_CALLBACK(OnPreeditStartThunk), this);
108 g_signal_connect(context_, "preedit_end",
109 G_CALLBACK(OnPreeditEndThunk), this);
110 g_signal_connect(context_, "preedit_changed",
111 G_CALLBACK(OnPreeditChangedThunk), this);
112
113 g_signal_connect(context_simple_, "commit",
114 G_CALLBACK(OnCommitThunk), this);
115 g_signal_connect(context_simple_, "preedit_start",
116 G_CALLBACK(OnPreeditStartThunk), this);
117 g_signal_connect(context_simple_, "preedit_end",
118 G_CALLBACK(OnPreeditEndThunk), this);
119 g_signal_connect(context_simple_, "preedit_changed",
120 G_CALLBACK(OnPreeditChangedThunk), this);
121
122 // Set client window if the widget is already realized.
123 OnWidgetRealize(widget->GetNativeView());
124 }
125
126 void InputMethodGtk::DispatchKeyEvent(const KeyEvent& key) {
127 suppress_next_result_ = false;
128
129 if (!context_focused_) {
130 DispatchKeyEventPostIME(key);
131 return;
132 }
133
134 handling_key_event_ = true;
135 composition_changed_ = false;
136 result_text_.clear();
137
138 GdkEvent* event = key.native_event();
139 DCHECK(!event || event->type == GDK_KEY_PRESS ||
140 event->type == GDK_KEY_RELEASE);
141
142 // If it's a fake key event, then we need to synthesis a GdkEventKey.
143 bool need_free_event = false;
144 if (!event) {
145 event = SynthesizeGdkEventKey(key);
146 need_free_event = true;
147 }
148
149 gboolean filtered = false;
150 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_TEXT)
151 filtered = gtk_im_context_filter_keypress(context_, &event->key);
152 else
153 filtered = gtk_im_context_filter_keypress(context_simple_, &event->key);
154
155 handling_key_event_ = false;
156
157 View* old_focused_view = focused_view_;
158 if (key.type() == ui::ET_KEY_PRESSED && filtered)
159 ProcessFilteredKeyPressEvent(key);
160
161 if (old_focused_view != focused_view_)
162 return;
163
164 if (HasInputMethodResult())
165 ProcessInputMethodResult(key, filtered);
166
167 if (old_focused_view != focused_view_)
168 return;
169
170 if (key.type() == ui::ET_KEY_PRESSED && !filtered)
171 ProcessUnfilteredKeyPressEvent(key);
172 else if (key.type() == ui::ET_KEY_RELEASED)
173 DispatchKeyEventPostIME(key);
174
175 if (need_free_event)
176 gdk_event_free(event);
177 }
178
179 void InputMethodGtk::OnTextInputTypeChanged(View* view) {
180 if (IsViewFocused(view))
181 UpdateContextFocusState();
182 }
183
184 void InputMethodGtk::OnCaretBoundsChanged(View* view) {
185 if (!IsViewFocused(view))
186 return;
187
188 TextInputClient* client = view->GetTextInputClient();
189 if (!client)
190 return;
191
192 gfx::Rect rect = client->GetCaretBounds();
193 gfx::Point origin = rect.origin();
194 gfx::Point end = gfx::Point(rect.right(), rect.bottom());
195
196 View::ConvertPointToWidget(view, &origin);
197 View::ConvertPointToWidget(view, &end);
198
199 GdkRectangle gdk_rect =
200 { origin.x(), origin.y(), end.x() - origin.x(), end.y() - origin.y() };
201
202 // We need to translate the coordinates to the toplevel widget if the view is
203 // inside a child widget.
204 if (view->GetWidget() != widget_) {
205 gboolean result = gtk_widget_translate_coordinates(
206 view->GetWidget()->GetNativeView(), widget_->GetNativeView(),
207 gdk_rect.x, gdk_rect.y, &gdk_rect.x, &gdk_rect.y);
208 DCHECK(result);
209 }
210
211 gtk_im_context_set_cursor_location(context_, &gdk_rect);
212 }
213
214 void InputMethodGtk::CancelComposition(View* view) {
215 if (IsViewFocused(view))
216 ResetContext();
217 }
218
219 std::string InputMethodGtk::GetInputLocale() {
220 // Not supported.
221 return std::string("");
222 }
223
224 base::i18n::TextDirection InputMethodGtk::GetInputTextDirection() {
225 // Not supported.
226 return base::i18n::UNKNOWN_DIRECTION;
227 }
228
229 bool InputMethodGtk::IsActive() {
230 // We always need to send keyboard events to either |context_| or
231 // |context_simple_|, so just return true here.
232 return true;
233 }
234
235 void InputMethodGtk::FocusWillChange(View* focused_before, View* focused) {
236 DCHECK_EQ(focused_view_, focused_before);
237 ConfirmComposition();
238 focused_view_ = focused;
239 UpdateContextFocusState();
240 }
241
242 void InputMethodGtk::ConfirmComposition() {
243 TextInputClient* client = GetTextInputClient();
244 if (client && client->HasComposition())
245 client->ConfirmComposition();
246
247 ResetContext();
248 }
249
250 void InputMethodGtk::ResetContext() {
251 if (!context_focused_)
252 return;
253
254 DCHECK(widget_focused_);
255 DCHECK(focused_view_);
256 DCHECK(!handling_key_event_);
257
258 // To prevent any text from being committed when resetting the |context_|;
oshima 2011/03/21 20:30:56 Does this mean filtered = gtk_im_context_filter_
James Su 2011/03/21 21:21:16 yes.
259 handling_key_event_ = true;
260 suppress_next_result_ = true;
261
262 gtk_im_context_reset(context_);
263 gtk_im_context_reset(context_simple_);
264
265 // Some input methods may not honour the reset call. Focusing out/in the
266 // |context_| to make sure it gets reset correctly.
267 gtk_im_context_focus_out(context_);
268 gtk_im_context_focus_in(context_);
oshima 2011/03/21 20:30:56 don't we have to do this for contet_simple_? And i
James Su 2011/03/21 21:21:16 context_simple_ doesn't have this problem, reset i
269
270 composition_.Clear();
271 result_text_.clear();
272 handling_key_event_ = false;
273 composing_text_ = false;
274 composition_changed_ = false;
275 }
276
277 void InputMethodGtk::UpdateContextFocusState() {
278 bool old_context_focused = context_focused_;
279 context_focused_ = (GetTextInputClient() != NULL);
280
281 if (old_context_focused && !context_focused_) {
282 gtk_im_context_focus_out(context_);
283 gtk_im_context_focus_out(context_simple_);
284 } else if (!old_context_focused && context_focused_) {
285 gtk_im_context_focus_in(context_);
286 gtk_im_context_focus_in(context_simple_);
287 }
288 }
289
290 void InputMethodGtk::ProcessFilteredKeyPressEvent(const KeyEvent& key) {
291 if (NeedInsertChar()) {
292 DispatchKeyEventPostIME(key);
293 } else {
294 KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, key.flags());
295 DispatchKeyEventPostIME(key);
296 }
297 }
298
299 void InputMethodGtk::ProcessUnfilteredKeyPressEvent(const KeyEvent& key) {
300 DispatchKeyEventPostIME(key);
301
302 // If a key event was not filtered by |context_| or |context_simple_|, then
303 // it means the key event didn't generate any result text. For some cases,
304 // the key event may still generate a valid character, eg. a control-key
305 // event (ctrl-a, return, tab, etc.). We need to send the character to the
306 // focused text input client by calling TextInputClient::InsertChar().
307 char16 ch = key.GetCharacter();
308 TextInputClient* client = GetTextInputClient();
309 if (ch && client)
310 client->InsertChar(ch, key.flags());
311 }
312
313 void InputMethodGtk::ProcessInputMethodResult(const KeyEvent& key,
314 bool filtered) {
315 TextInputClient* client = GetTextInputClient();
316 DCHECK(client);
317
318 if (result_text_.length()) {
319 if (filtered && NeedInsertChar()) {
320 for (string16::const_iterator i = result_text_.begin();
321 i != result_text_.end(); ++i) {
322 client->InsertChar(*i, key.flags());
323 }
324 } else {
325 client->InsertText(result_text_);
326 composing_text_ = false;
327 }
328 }
329
330 if (composition_changed_ && GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE) {
331 if (composition_.text.length()) {
332 composing_text_ = true;
333 client->SetComposition(composition_);
334 } else if (result_text_.empty()) {
335 client->ClearComposition();
336 }
337 }
338 }
339
340 TextInputClient* InputMethodGtk::GetTextInputClient() const {
341 return (widget_focused_ && focused_view_) ?
342 focused_view_->GetTextInputClient() : NULL;
343 }
344
345 ui::TextInputType InputMethodGtk::GetTextInputType() const {
346 TextInputClient* client = GetTextInputClient();
347 return client ? client->GetTextInputType() : ui::TEXT_INPUT_TYPE_NONE;
348 }
349
350 bool InputMethodGtk::IsViewFocused(View* view) const {
351 return widget_focused_ && view && focused_view_ == view;
352 }
353
354 bool InputMethodGtk::NeedInsertChar() const {
355 return GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE ||
356 (!composing_text_ && result_text_.length() == 1);
357 }
358
359 bool InputMethodGtk::HasInputMethodResult() const {
360 return result_text_.length() || composition_changed_;
361 }
362
363 void InputMethodGtk::SendFakeProcessKeyEvent(bool pressed) const {
364 KeyEvent key(pressed ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED,
365 ui::VKEY_PROCESSKEY, 0);
366 DispatchKeyEventPostIME(key);
367 }
368
369 GdkEvent* InputMethodGtk::SynthesizeGdkEventKey(const KeyEvent& key) const {
370 guint keyval =
371 ui::GdkKeyCodeForWindowsKeyCode(key.key_code(), key.IsShiftDown());
372 guint state = 0;
373 if (key.IsShiftDown())
374 state |= GDK_SHIFT_MASK;
375 if (key.IsControlDown())
376 state |= GDK_CONTROL_MASK;
377 if (key.IsAltDown())
378 state |= GDK_MOD1_MASK;
379 if (key.IsCapsLockDown())
380 state |= GDK_LOCK_MASK;
381
382 DCHECK(widget_->GetNativeView()->window);
383 return ui::SynthesizeKeyEvent(widget_->GetNativeView()->window,
384 key.type() == ui::ET_KEY_PRESSED,
385 keyval, state);
386 }
387
388 void InputMethodGtk::DispatchKeyEventPostIME(const KeyEvent& key) const {
389 if (delegate_)
390 delegate_->DispatchKeyEventPostIME(key);
391 }
392
393 void InputMethodGtk::OnCommit(GtkIMContext* context, gchar* text) {
394 if (suppress_next_result_) {
395 suppress_next_result_ = false;
396 return;
397 }
398
399 if (!context_focused_)
400 return;
401
402 string16 utf16_text(UTF8ToUTF16(text));
403
404 // Append the text to the buffer, because commit signal might be fired
405 // multiple times when processing a key event.
406 result_text_.append(utf16_text);
407 if (!handling_key_event_) {
408 TextInputClient* client = GetTextInputClient();
409 if (client && client->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE) {
410 SendFakeProcessKeyEvent(true);
411 client->InsertText(utf16_text);
412 SendFakeProcessKeyEvent(false);
413 }
414 }
415 }
416
417 void InputMethodGtk::OnPreeditStart(GtkIMContext* context) {
418 if (suppress_next_result_ || !context_focused_)
419 return;
420
421 composing_text_ = true;
422 }
423
424 void InputMethodGtk::OnPreeditChanged(GtkIMContext* context) {
425 if (suppress_next_result_ || !context_focused_)
426 return;
427
428 gchar* text = NULL;
429 PangoAttrList* attrs = NULL;
430 gint cursor_position = 0;
431 gtk_im_context_get_preedit_string(context, &text, &attrs, &cursor_position);
432
433 ui::ExtractCompositionInfoFromGtkPreedit(text, attrs, cursor_position,
434 &composition_);
435 composition_changed_ = true;
436
437 g_free(text);
438 pango_attr_list_unref(attrs);
439
440 if (composition_.text.length())
441 composing_text_ = true;
442
443 if (!handling_key_event_) {
444 TextInputClient* client = GetTextInputClient();
445 if (client && client->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE) {
446 SendFakeProcessKeyEvent(true);
447 client->SetComposition(composition_);
448 SendFakeProcessKeyEvent(false);
449 }
450 }
451 }
452
453 void InputMethodGtk::OnPreeditEnd(GtkIMContext* context) {
454 if (!context_focused_ || composition_.text.empty())
455 return;
456
457 composition_changed_ = true;
458 composition_.Clear();
459
460 if (!handling_key_event_) {
461 TextInputClient* client = GetTextInputClient();
462 if (client && client->HasComposition())
463 client->ClearComposition();
464 }
465 }
466
467 void InputMethodGtk::OnWidgetRealize(GtkWidget* widget) {
468 // We should only set im context's client window once, because when setting
469 // client window, im context may destroy and recreate its internal states and
470 // objects.
471 if (widget->window) {
472 gtk_im_context_set_client_window(context_, widget->window);
473 gtk_im_context_set_client_window(context_simple_, widget->window);
474 }
475 }
476
477 void InputMethodGtk::OnWidgetUnrealize(GtkWidget* widget) {
478 gtk_im_context_set_client_window(context_, NULL);
479 gtk_im_context_set_client_window(context_simple_, NULL);
480 }
481
482 gboolean InputMethodGtk::OnWidgetFocusIn(GtkWidget* widget,
483 GdkEventFocus* event) {
484 if (!widget_focused_) {
485 widget_focused_ = true;
486 UpdateContextFocusState();
487 }
488 return false;
489 }
490
491 gboolean InputMethodGtk::OnWidgetFocusOut(GtkWidget* widget,
492 GdkEventFocus* event) {
493 if (widget_focused_) {
494 ConfirmComposition();
495 widget_focused_ = false;
496 UpdateContextFocusState();
497 }
498 return false;
499 }
500
501 } // namespace views
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698