OLD | NEW |
---|---|
(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 | |
OLD | NEW |