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 : context_(NULL), | |
28 context_simple_(NULL), | |
29 widget_realize_id_(0), | |
30 widget_unrealize_id_(0), | |
31 widget_focus_in_id_(0), | |
32 widget_focus_out_id_(0), | |
33 context_focused_(false), | |
34 handling_key_event_(false), | |
35 composing_text_(false), | |
36 composition_changed_(false), | |
37 suppress_next_result_(false) { | |
38 set_delegate(delegate); | |
39 } | |
40 | |
41 InputMethodGtk::~InputMethodGtk() { | |
42 if (widget()) { | |
43 GtkWidget* native_view = widget()->GetNativeView(); | |
44 if (native_view) { | |
45 g_signal_handler_disconnect(native_view, widget_realize_id_); | |
46 g_signal_handler_disconnect(native_view, widget_unrealize_id_); | |
47 g_signal_handler_disconnect(native_view, widget_focus_in_id_); | |
48 g_signal_handler_disconnect(native_view, widget_focus_out_id_); | |
49 } | |
50 } | |
51 if (context_) { | |
52 g_object_unref(context_); | |
53 context_ = NULL; | |
54 } | |
55 if (context_simple_) { | |
56 g_object_unref(context_simple_); | |
57 context_simple_ = NULL; | |
58 } | |
59 } | |
60 | |
61 void InputMethodGtk::Init(Widget* widget) { | |
62 InputMethodBase::Init(widget); | |
63 | |
64 DCHECK(GTK_IS_WIDGET(widget->GetNativeView())); | |
65 | |
66 widget_realize_id_ = | |
67 g_signal_connect(widget->GetNativeView(), "realize", | |
68 G_CALLBACK(OnWidgetRealizeThunk), this); | |
69 widget_unrealize_id_ = | |
70 g_signal_connect(widget->GetNativeView(), "unrealize", | |
71 G_CALLBACK(OnWidgetUnrealizeThunk), this); | |
72 widget_focus_in_id_ = | |
73 g_signal_connect(widget->GetNativeView(), "focus-in-event", | |
74 G_CALLBACK(OnWidgetFocusInThunk), this); | |
75 widget_focus_out_id_ = | |
76 g_signal_connect(widget->GetNativeView(), "focus-out-event", | |
77 G_CALLBACK(OnWidgetFocusOutThunk), this); | |
78 | |
79 context_ = gtk_im_multicontext_new(); | |
80 context_simple_ = gtk_im_context_simple_new(); | |
81 | |
82 // context_ and context_simple_ share the same callback handlers. | |
83 // All data come from them are treated equally. | |
84 // context_ is for full input method support. | |
85 // context_simple_ is for supporting dead/compose keys when input method is | |
86 // disabled, eg. in password input box. | |
87 g_signal_connect(context_, "commit", | |
88 G_CALLBACK(OnCommitThunk), this); | |
89 g_signal_connect(context_, "preedit_start", | |
90 G_CALLBACK(OnPreeditStartThunk), this); | |
91 g_signal_connect(context_, "preedit_end", | |
92 G_CALLBACK(OnPreeditEndThunk), this); | |
93 g_signal_connect(context_, "preedit_changed", | |
94 G_CALLBACK(OnPreeditChangedThunk), this); | |
95 | |
96 g_signal_connect(context_simple_, "commit", | |
97 G_CALLBACK(OnCommitThunk), this); | |
98 g_signal_connect(context_simple_, "preedit_start", | |
99 G_CALLBACK(OnPreeditStartThunk), this); | |
100 g_signal_connect(context_simple_, "preedit_end", | |
101 G_CALLBACK(OnPreeditEndThunk), this); | |
102 g_signal_connect(context_simple_, "preedit_changed", | |
103 G_CALLBACK(OnPreeditChangedThunk), this); | |
104 | |
105 // Set client window if the widget is already realized. | |
106 OnWidgetRealize(widget->GetNativeView()); | |
107 } | |
108 | |
109 void InputMethodGtk::DispatchKeyEvent(const KeyEvent& key) { | |
110 suppress_next_result_ = false; | |
111 | |
112 // We should bypass |context_| and |context_simple_| only if there is no | |
113 // text input client is focused at all. Otherwise, we should always send the | |
114 // key event to either |context_| or |context_simple_| even if the text input | |
115 // type is ui::TEXT_INPUT_TYPE_NONE, to make sure we can get correct character | |
116 // result. | |
117 if (!GetTextInputClient()) { | |
118 DispatchKeyEventPostIME(key); | |
119 return; | |
120 } | |
121 | |
122 handling_key_event_ = true; | |
123 composition_changed_ = false; | |
124 result_text_.clear(); | |
125 | |
126 GdkEvent* event = key.native_event(); | |
127 DCHECK(!event || event->type == GDK_KEY_PRESS || | |
128 event->type == GDK_KEY_RELEASE); | |
129 | |
130 // If it's a fake key event, then we need to synthesis a GdkEventKey. | |
131 bool need_free_event = false; | |
132 if (!event) { | |
133 event = SynthesizeGdkEventKey(key); | |
134 need_free_event = true; | |
135 } | |
136 | |
137 gboolean filtered = false; | |
138 if (context_focused_) | |
139 filtered = gtk_im_context_filter_keypress(context_, &event->key); | |
140 else | |
141 filtered = gtk_im_context_filter_keypress(context_simple_, &event->key); | |
142 | |
143 handling_key_event_ = false; | |
144 | |
145 View* old_focused_view = focused_view(); | |
146 if (key.type() == ui::ET_KEY_PRESSED && filtered) | |
147 ProcessFilteredKeyPressEvent(key); | |
148 | |
149 // In case the focus was changed by the key event. | |
150 if (old_focused_view != focused_view()) | |
151 return; | |
152 | |
153 if (HasInputMethodResult()) | |
154 ProcessInputMethodResult(key, filtered); | |
155 | |
156 // In case the focus was changed when sending input method results to the | |
157 // focused View. | |
158 if (old_focused_view != focused_view()) | |
159 return; | |
160 | |
161 if (key.type() == ui::ET_KEY_PRESSED && !filtered) | |
Peng
2011/03/24 20:26:48
I mean, if the event is not not filtered by imcont
| |
162 ProcessUnfilteredKeyPressEvent(key); | |
163 else if (key.type() == ui::ET_KEY_RELEASED) | |
164 DispatchKeyEventPostIME(key); | |
165 | |
166 if (need_free_event) | |
167 gdk_event_free(event); | |
168 } | |
169 | |
170 void InputMethodGtk::OnTextInputTypeChanged(View* view) { | |
171 if (IsViewFocused(view)) | |
172 UpdateContextFocusState(); | |
173 } | |
174 | |
175 void InputMethodGtk::OnCaretBoundsChanged(View* view) { | |
176 gfx::Rect rect; | |
177 if (!IsViewFocused(view) || !GetCaretBoundsInWidget(&rect)) | |
178 return; | |
179 | |
180 GdkRectangle gdk_rect = rect.ToGdkRectangle(); | |
181 gtk_im_context_set_cursor_location(context_, &gdk_rect); | |
182 } | |
183 | |
184 void InputMethodGtk::CancelComposition(View* view) { | |
185 if (IsViewFocused(view)) | |
186 ResetContext(); | |
187 } | |
188 | |
189 std::string InputMethodGtk::GetInputLocale() { | |
190 // Not supported. | |
191 return std::string(""); | |
192 } | |
193 | |
194 base::i18n::TextDirection InputMethodGtk::GetInputTextDirection() { | |
195 // Not supported. | |
196 return base::i18n::UNKNOWN_DIRECTION; | |
197 } | |
198 | |
199 bool InputMethodGtk::IsActive() { | |
200 // We always need to send keyboard events to either |context_| or | |
201 // |context_simple_|, so just return true here. | |
202 return true; | |
203 } | |
204 | |
205 void InputMethodGtk::FocusedViewWillChange() { | |
206 ConfirmCompositionText(); | |
207 } | |
208 | |
209 void InputMethodGtk::FocusedViewDidChange() { | |
210 UpdateContextFocusState(); | |
211 } | |
212 | |
213 void InputMethodGtk::ConfirmCompositionText() { | |
214 TextInputClient* client = GetTextInputClient(); | |
215 if (client && client->HasCompositionText()) | |
216 client->ConfirmCompositionText(); | |
217 | |
218 ResetContext(); | |
219 } | |
220 | |
221 void InputMethodGtk::ResetContext() { | |
222 if (!GetTextInputClient()) | |
223 return; | |
224 | |
225 DCHECK(widget_focused()); | |
226 DCHECK(focused_view()); | |
227 DCHECK(!handling_key_event_); | |
228 | |
229 // To prevent any text from being committed when resetting the |context_|; | |
230 handling_key_event_ = true; | |
231 suppress_next_result_ = true; | |
232 | |
233 gtk_im_context_reset(context_); | |
234 gtk_im_context_reset(context_simple_); | |
235 | |
236 // Some input methods may not honour the reset call. Focusing out/in the | |
237 // |context_| to make sure it gets reset correctly. | |
238 if (context_focused_) { | |
239 gtk_im_context_focus_out(context_); | |
240 gtk_im_context_focus_in(context_); | |
241 } | |
242 | |
243 composition_.Clear(); | |
244 result_text_.clear(); | |
245 handling_key_event_ = false; | |
246 composing_text_ = false; | |
247 composition_changed_ = false; | |
248 } | |
249 | |
250 void InputMethodGtk::UpdateContextFocusState() { | |
251 bool old_context_focused = context_focused_; | |
252 // Use switch here in case we are going to add more text input types. | |
253 switch (GetTextInputType()) { | |
254 case ui::TEXT_INPUT_TYPE_NONE: | |
255 case ui::TEXT_INPUT_TYPE_PASSWORD: | |
256 context_focused_ = false; | |
257 break; | |
258 default: | |
259 context_focused_ = true; | |
260 break; | |
261 } | |
262 | |
263 // We only focus in |context_| when the focus in in a normal textfield. | |
264 if (old_context_focused && !context_focused_) | |
265 gtk_im_context_focus_out(context_); | |
266 else if (!old_context_focused && context_focused_) | |
267 gtk_im_context_focus_in(context_); | |
268 | |
269 // |context_simple_| can be used in any textfield, including password box, and | |
270 // even if the focused text input client's text input type is | |
271 // ui::TEXT_INPUT_TYPE_NONE. | |
272 if (GetTextInputClient()) | |
273 gtk_im_context_focus_in(context_simple_); | |
274 else | |
275 gtk_im_context_focus_out(context_simple_); | |
276 } | |
277 | |
278 void InputMethodGtk::ProcessFilteredKeyPressEvent(const KeyEvent& key) { | |
279 if (NeedInsertChar()) { | |
280 DispatchKeyEventPostIME(key); | |
281 } else { | |
282 KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, key.flags()); | |
283 DispatchKeyEventPostIME(key); | |
284 } | |
285 } | |
286 | |
287 void InputMethodGtk::ProcessUnfilteredKeyPressEvent(const KeyEvent& key) { | |
288 DispatchKeyEventPostIME(key); | |
289 | |
290 // If a key event was not filtered by |context_| or |context_simple_|, then | |
291 // it means the key event didn't generate any result text. For some cases, | |
292 // the key event may still generate a valid character, eg. a control-key | |
293 // event (ctrl-a, return, tab, etc.). We need to send the character to the | |
294 // focused text input client by calling TextInputClient::InsertChar(). | |
Peng
2011/03/24 03:49:02
Should do this? Maybe some view will handle them b
James Su
2011/03/24 18:01:35
We need it to keep consistent with Windows. And a
Peng
2011/03/24 18:39:03
I do not understand. I am thinking in some cases,
James Su
2011/03/24 18:56:28
Of course, it's free for a View to ignore any char
Peng
2011/03/24 19:25:08
So do you mean the view can still receive this eve
James Su
2011/03/24 19:48:49
I don't understand your question. Which event do y
Peng
2011/03/24 20:26:48
I think InsertChar() & InsertText() are very clear
James Su
2011/03/24 21:01:23
DispatchKeyEventPostIME() is called 7 lines above
| |
295 char16 ch = key.GetCharacter(); | |
296 TextInputClient* client = GetTextInputClient(); | |
297 if (ch && client) | |
298 client->InsertChar(ch, key.flags()); | |
299 } | |
300 | |
301 void InputMethodGtk::ProcessInputMethodResult(const KeyEvent& key, | |
302 bool filtered) { | |
303 TextInputClient* client = GetTextInputClient(); | |
304 DCHECK(client); | |
305 | |
306 if (result_text_.length()) { | |
307 if (filtered && NeedInsertChar()) { | |
308 for (string16::const_iterator i = result_text_.begin(); | |
309 i != result_text_.end(); ++i) { | |
310 client->InsertChar(*i, key.flags()); | |
311 } | |
312 } else { | |
313 client->InsertText(result_text_); | |
314 composing_text_ = false; | |
315 } | |
316 } | |
317 | |
318 if (composition_changed_ && !IsTextInputTypeNone()) { | |
319 if (composition_.text.length()) { | |
320 composing_text_ = true; | |
321 client->SetCompositionText(composition_); | |
322 } else if (result_text_.empty()) { | |
323 client->ClearCompositionText(); | |
324 } | |
325 } | |
326 } | |
327 | |
328 bool InputMethodGtk::NeedInsertChar() const { | |
329 return IsTextInputTypeNone() || | |
330 (!composing_text_ && result_text_.length() == 1); | |
331 } | |
332 | |
333 bool InputMethodGtk::HasInputMethodResult() const { | |
334 return result_text_.length() || composition_changed_; | |
335 } | |
336 | |
337 void InputMethodGtk::SendFakeProcessKeyEvent(bool pressed) const { | |
338 KeyEvent key(pressed ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED, | |
339 ui::VKEY_PROCESSKEY, 0); | |
340 DispatchKeyEventPostIME(key); | |
341 } | |
342 | |
343 GdkEvent* InputMethodGtk::SynthesizeGdkEventKey(const KeyEvent& key) const { | |
344 guint keyval = | |
345 ui::GdkKeyCodeForWindowsKeyCode(key.key_code(), key.IsShiftDown()); | |
346 guint state = 0; | |
347 if (key.IsShiftDown()) | |
348 state |= GDK_SHIFT_MASK; | |
349 if (key.IsControlDown()) | |
350 state |= GDK_CONTROL_MASK; | |
351 if (key.IsAltDown()) | |
352 state |= GDK_MOD1_MASK; | |
353 if (key.IsCapsLockDown()) | |
354 state |= GDK_LOCK_MASK; | |
355 | |
356 DCHECK(widget()->GetNativeView()->window); | |
357 return ui::SynthesizeKeyEvent(widget()->GetNativeView()->window, | |
358 key.type() == ui::ET_KEY_PRESSED, | |
359 keyval, state); | |
360 } | |
361 | |
362 void InputMethodGtk::OnCommit(GtkIMContext* context, gchar* text) { | |
363 if (suppress_next_result_) { | |
364 suppress_next_result_ = false; | |
365 return; | |
366 } | |
367 | |
368 // We need to receive input method result even if the text input type is | |
369 // ui::TEXT_INPUT_TYPE_NONE, to make sure we can always send correct | |
370 // character for each key event to the focused text input client. | |
371 if (!GetTextInputClient()) | |
372 return; | |
373 | |
374 string16 utf16_text(UTF8ToUTF16(text)); | |
375 | |
376 // Append the text to the buffer, because commit signal might be fired | |
377 // multiple times when processing a key event. | |
378 result_text_.append(utf16_text); | |
379 | |
380 // If we are not handling key event, do not bother sending text result if the | |
381 // focused text input client does not support text input. | |
382 if (!handling_key_event_ && !IsTextInputTypeNone()) { | |
383 SendFakeProcessKeyEvent(true); | |
384 GetTextInputClient()->InsertText(utf16_text); | |
385 SendFakeProcessKeyEvent(false); | |
386 } | |
387 } | |
388 | |
389 void InputMethodGtk::OnPreeditStart(GtkIMContext* context) { | |
390 if (suppress_next_result_ || IsTextInputTypeNone()) | |
391 return; | |
392 | |
393 composing_text_ = true; | |
394 } | |
395 | |
396 void InputMethodGtk::OnPreeditChanged(GtkIMContext* context) { | |
397 if (suppress_next_result_ || IsTextInputTypeNone()) | |
398 return; | |
399 | |
400 gchar* text = NULL; | |
401 PangoAttrList* attrs = NULL; | |
402 gint cursor_position = 0; | |
403 gtk_im_context_get_preedit_string(context, &text, &attrs, &cursor_position); | |
404 | |
405 ui::ExtractCompositionTextFromGtkPreedit(text, attrs, cursor_position, | |
406 &composition_); | |
407 composition_changed_ = true; | |
408 | |
409 g_free(text); | |
410 pango_attr_list_unref(attrs); | |
411 | |
412 if (composition_.text.length()) | |
413 composing_text_ = true; | |
414 | |
415 if (!handling_key_event_ && !IsTextInputTypeNone()) { | |
Peng
2011/03/24 03:49:02
I suggest always send the fake key event, even if
James Su
2011/03/24 18:01:35
No. This method could be called for multiple times
| |
416 SendFakeProcessKeyEvent(true); | |
417 GetTextInputClient()->SetCompositionText(composition_); | |
418 SendFakeProcessKeyEvent(false); | |
419 } | |
420 } | |
421 | |
422 void InputMethodGtk::OnPreeditEnd(GtkIMContext* context) { | |
423 if (composition_.text.empty() || IsTextInputTypeNone()) | |
424 return; | |
425 | |
426 composition_changed_ = true; | |
427 composition_.Clear(); | |
428 | |
429 if (!handling_key_event_) { | |
430 TextInputClient* client = GetTextInputClient(); | |
431 if (client && client->HasCompositionText()) | |
432 client->ClearCompositionText(); | |
433 } | |
434 } | |
435 | |
436 void InputMethodGtk::OnWidgetRealize(GtkWidget* widget) { | |
437 // We should only set im context's client window once, because when setting | |
438 // client window, im context may destroy and recreate its internal states and | |
439 // objects. | |
440 if (widget->window) { | |
Peng
2011/03/24 03:49:02
How about create GtkIMContext here?
James Su
2011/03/24 18:01:35
If the creation of GtkIMContext happens here, then
| |
441 gtk_im_context_set_client_window(context_, widget->window); | |
442 gtk_im_context_set_client_window(context_simple_, widget->window); | |
443 } | |
444 } | |
445 | |
446 void InputMethodGtk::OnWidgetUnrealize(GtkWidget* widget) { | |
Peng
2011/03/24 03:49:02
And destroy IMContext here?
| |
447 gtk_im_context_set_client_window(context_, NULL); | |
448 gtk_im_context_set_client_window(context_simple_, NULL); | |
449 } | |
450 | |
451 gboolean InputMethodGtk::OnWidgetFocusIn(GtkWidget* widget, | |
452 GdkEventFocus* event) { | |
453 if (!widget_focused()) { | |
454 set_widget_focused(true); | |
455 UpdateContextFocusState(); | |
456 } | |
457 return false; | |
458 } | |
459 | |
460 gboolean InputMethodGtk::OnWidgetFocusOut(GtkWidget* widget, | |
461 GdkEventFocus* event) { | |
462 if (widget_focused()) { | |
463 ConfirmCompositionText(); | |
464 set_widget_focused(false); | |
465 UpdateContextFocusState(); | |
466 } | |
467 return false; | |
468 } | |
469 | |
470 } // namespace views | |
OLD | NEW |