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 "ui/views/ime/input_method_gtk.h" | |
6 | |
7 #include <gdk/gdk.h> | |
8 #include <gdk/gdkkeysyms.h> | |
9 | |
10 #include <algorithm> | |
11 #include <string> | |
12 | |
13 #include "base/basictypes.h" | |
14 #include "base/logging.h" | |
15 #include "base/string_util.h" | |
16 #include "base/third_party/icu/icu_utf.h" | |
17 #include "base/utf_string_conversions.h" | |
18 #include "ui/base/gtk/event_synthesis_gtk.h" | |
19 #include "ui/base/gtk/gtk_im_context_util.h" | |
20 #include "ui/base/keycodes/keyboard_code_conversion_gtk.h" | |
21 #include "ui/base/keycodes/keyboard_codes.h" | |
22 #include "ui/views/events/event.h" | |
23 #include "ui/views/widget/widget.h" | |
24 | |
25 namespace views { | |
26 | |
27 InputMethodGtk::InputMethodGtk(internal::InputMethodDelegate* delegate) | |
28 : context_(NULL), | |
29 context_simple_(NULL), | |
30 widget_realize_id_(0), | |
31 widget_unrealize_id_(0), | |
32 context_focused_(false), | |
33 handling_key_event_(false), | |
34 composing_text_(false), | |
35 composition_changed_(false), | |
36 suppress_next_result_(false) { | |
37 set_delegate(delegate); | |
38 } | |
39 | |
40 InputMethodGtk::~InputMethodGtk() { | |
41 if (widget()) { | |
42 GtkWidget* native_view = widget()->GetNativeView(); | |
43 if (native_view) { | |
44 g_signal_handler_disconnect(native_view, widget_realize_id_); | |
45 g_signal_handler_disconnect(native_view, widget_unrealize_id_); | |
46 } | |
47 } | |
48 if (context_) { | |
49 g_object_unref(context_); | |
50 context_ = NULL; | |
51 } | |
52 if (context_simple_) { | |
53 g_object_unref(context_simple_); | |
54 context_simple_ = NULL; | |
55 } | |
56 } | |
57 | |
58 void InputMethodGtk::Init(Widget* widget) { | |
59 DCHECK(GTK_IS_WIDGET(widget->GetNativeView())); | |
60 | |
61 widget_realize_id_ = | |
62 g_signal_connect(widget->GetNativeView(), "realize", | |
63 G_CALLBACK(OnWidgetRealizeThunk), this); | |
64 widget_unrealize_id_ = | |
65 g_signal_connect(widget->GetNativeView(), "unrealize", | |
66 G_CALLBACK(OnWidgetUnrealizeThunk), this); | |
67 | |
68 context_ = gtk_im_multicontext_new(); | |
69 context_simple_ = gtk_im_context_simple_new(); | |
70 | |
71 // context_ and context_simple_ share the same callback handlers. | |
72 // All data come from them are treated equally. | |
73 // context_ is for full input method support. | |
74 // context_simple_ is for supporting dead/compose keys when input method is | |
75 // disabled, eg. in password input box. | |
76 g_signal_connect(context_, "commit", | |
77 G_CALLBACK(OnCommitThunk), this); | |
78 g_signal_connect(context_, "preedit_start", | |
79 G_CALLBACK(OnPreeditStartThunk), this); | |
80 g_signal_connect(context_, "preedit_end", | |
81 G_CALLBACK(OnPreeditEndThunk), this); | |
82 g_signal_connect(context_, "preedit_changed", | |
83 G_CALLBACK(OnPreeditChangedThunk), this); | |
84 | |
85 g_signal_connect(context_simple_, "commit", | |
86 G_CALLBACK(OnCommitThunk), this); | |
87 g_signal_connect(context_simple_, "preedit_start", | |
88 G_CALLBACK(OnPreeditStartThunk), this); | |
89 g_signal_connect(context_simple_, "preedit_end", | |
90 G_CALLBACK(OnPreeditEndThunk), this); | |
91 g_signal_connect(context_simple_, "preedit_changed", | |
92 G_CALLBACK(OnPreeditChangedThunk), this); | |
93 | |
94 // Set client window if the widget is already realized. | |
95 OnWidgetRealize(widget->GetNativeView()); | |
96 | |
97 InputMethodBase::Init(widget); | |
98 } | |
99 | |
100 void InputMethodGtk::OnFocus() { | |
101 DCHECK(!widget_focused()); | |
102 InputMethodBase::OnFocus(); | |
103 UpdateContextFocusState(); | |
104 } | |
105 | |
106 void InputMethodGtk::OnBlur() { | |
107 DCHECK(widget_focused()); | |
108 ConfirmCompositionText(); | |
109 InputMethodBase::OnBlur(); | |
110 UpdateContextFocusState(); | |
111 } | |
112 | |
113 void InputMethodGtk::DispatchKeyEvent(const KeyEvent& key) { | |
114 DCHECK(key.type() == ui::ET_KEY_PRESSED || key.type() == ui::ET_KEY_RELEASED); | |
115 suppress_next_result_ = false; | |
116 | |
117 // We should bypass |context_| and |context_simple_| only if there is no | |
118 // text input client focused. Otherwise, always send the key event to either | |
119 // |context_| or |context_simple_| even if the text input type is | |
120 // ui::TEXT_INPUT_TYPE_NONE, to make sure we can get correct character result. | |
121 if (!GetTextInputClient()) { | |
122 DispatchKeyEventPostIME(key); | |
123 return; | |
124 } | |
125 | |
126 handling_key_event_ = true; | |
127 composition_changed_ = false; | |
128 result_text_.clear(); | |
129 | |
130 // If it's a fake key event, then we need to synthesize a GdkEventKey. | |
131 GdkEvent* event = key.gdk_event() ? key.gdk_event() : | |
132 SynthesizeGdkEventKey(key); | |
133 gboolean filtered = gtk_im_context_filter_keypress( | |
134 context_focused_ ? context_ : context_simple_, &event->key); | |
135 | |
136 handling_key_event_ = false; | |
137 | |
138 const View* old_focused_view = GetFocusedView(); | |
139 if (key.type() == ui::ET_KEY_PRESSED && filtered) | |
140 ProcessFilteredKeyPressEvent(key); | |
141 | |
142 // Ensure no focus change from processing the key event. | |
143 if (old_focused_view == GetFocusedView()) { | |
144 if (HasInputMethodResult()) | |
145 ProcessInputMethodResult(key, filtered); | |
146 // Ensure no focus change sending input method results to the focused View. | |
147 if (old_focused_view == GetFocusedView()) { | |
148 if (key.type() == ui::ET_KEY_PRESSED && !filtered) | |
149 ProcessUnfilteredKeyPressEvent(key); | |
150 else if (key.type() == ui::ET_KEY_RELEASED) | |
151 DispatchKeyEventPostIME(key); | |
152 } | |
153 } | |
154 | |
155 // Free the synthesized event if there was no underlying native event. | |
156 if (event != key.gdk_event()) | |
157 gdk_event_free(event); | |
158 } | |
159 | |
160 void InputMethodGtk::OnTextInputTypeChanged(View* view) { | |
161 if (IsViewFocused(view)) { | |
162 DCHECK(!composing_text_); | |
163 UpdateContextFocusState(); | |
164 } | |
165 InputMethodBase::OnTextInputTypeChanged(view); | |
166 } | |
167 | |
168 void InputMethodGtk::OnCaretBoundsChanged(View* view) { | |
169 gfx::Rect rect; | |
170 if (!IsViewFocused(view) || !GetCaretBoundsInWidget(&rect)) | |
171 return; | |
172 | |
173 GdkRectangle gdk_rect = rect.ToGdkRectangle(); | |
174 gtk_im_context_set_cursor_location(context_, &gdk_rect); | |
175 } | |
176 | |
177 void InputMethodGtk::CancelComposition(View* view) { | |
178 if (IsViewFocused(view)) | |
179 ResetContext(); | |
180 } | |
181 | |
182 std::string InputMethodGtk::GetInputLocale() { | |
183 // Not supported. | |
184 return std::string(""); | |
185 } | |
186 | |
187 base::i18n::TextDirection InputMethodGtk::GetInputTextDirection() { | |
188 // Not supported. | |
189 return base::i18n::UNKNOWN_DIRECTION; | |
190 } | |
191 | |
192 bool InputMethodGtk::IsActive() { | |
193 // We always need to send keyboard events to either |context_| or | |
194 // |context_simple_|, so just return true here. | |
195 return true; | |
196 } | |
197 | |
198 void InputMethodGtk::OnWillChangeFocus(View* focused_before, View* focused) { | |
199 ConfirmCompositionText(); | |
200 } | |
201 | |
202 void InputMethodGtk::OnDidChangeFocus(View* focused_before, View* focused) { | |
203 UpdateContextFocusState(); | |
204 | |
205 // Force to update caret bounds, in case the View thinks that the caret | |
206 // bounds has not changed. | |
207 if (context_focused_) | |
208 OnCaretBoundsChanged(GetFocusedView()); | |
209 } | |
210 | |
211 void InputMethodGtk::ConfirmCompositionText() { | |
212 ui::TextInputClient* client = GetTextInputClient(); | |
213 if (client && client->HasCompositionText()) | |
214 client->ConfirmCompositionText(); | |
215 | |
216 ResetContext(); | |
217 } | |
218 | |
219 void InputMethodGtk::ResetContext() { | |
220 if (!GetTextInputClient()) | |
221 return; | |
222 | |
223 DCHECK(widget_focused()); | |
224 DCHECK(GetFocusedView()); | |
225 DCHECK(!handling_key_event_); | |
226 | |
227 // To prevent any text from being committed when resetting the |context_|; | |
228 handling_key_event_ = true; | |
229 suppress_next_result_ = true; | |
230 | |
231 gtk_im_context_reset(context_); | |
232 gtk_im_context_reset(context_simple_); | |
233 | |
234 // Some input methods may not honour the reset call. Focusing out/in the | |
235 // |context_| to make sure it gets reset correctly. | |
236 if (context_focused_) { | |
237 gtk_im_context_focus_out(context_); | |
238 gtk_im_context_focus_in(context_); | |
239 } | |
240 | |
241 composition_.Clear(); | |
242 result_text_.clear(); | |
243 handling_key_event_ = false; | |
244 composing_text_ = false; | |
245 composition_changed_ = false; | |
246 } | |
247 | |
248 void InputMethodGtk::UpdateContextFocusState() { | |
249 bool old_context_focused = context_focused_; | |
250 // Use switch here in case we are going to add more text input types. | |
251 switch (GetTextInputType()) { | |
252 case ui::TEXT_INPUT_TYPE_NONE: | |
253 case ui::TEXT_INPUT_TYPE_PASSWORD: | |
254 context_focused_ = false; | |
255 break; | |
256 default: | |
257 context_focused_ = true; | |
258 break; | |
259 } | |
260 | |
261 // We only focus in |context_| when the focus is in a normal textfield. | |
262 if (old_context_focused && !context_focused_) | |
263 gtk_im_context_focus_out(context_); | |
264 else if (!old_context_focused && context_focused_) | |
265 gtk_im_context_focus_in(context_); | |
266 | |
267 // |context_simple_| can be used in any textfield, including password box, and | |
268 // even if the focused text input client's text input type is | |
269 // ui::TEXT_INPUT_TYPE_NONE. | |
270 if (GetTextInputClient()) | |
271 gtk_im_context_focus_in(context_simple_); | |
272 else | |
273 gtk_im_context_focus_out(context_simple_); | |
274 } | |
275 | |
276 void InputMethodGtk::ProcessFilteredKeyPressEvent(const KeyEvent& key) { | |
277 if (NeedInsertChar()) { | |
278 DispatchKeyEventPostIME(key); | |
279 } else { | |
280 KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, key.flags()); | |
281 DispatchKeyEventPostIME(key); | |
282 } | |
283 } | |
284 | |
285 void InputMethodGtk::ProcessUnfilteredKeyPressEvent(const KeyEvent& key) { | |
286 const View* old_focused_view = GetFocusedView(); | |
287 DispatchKeyEventPostIME(key); | |
288 | |
289 // We shouldn't dispatch the character anymore if the key event caused focus | |
290 // change. | |
291 if (old_focused_view != GetFocusedView()) | |
292 return; | |
293 | |
294 // If a key event was not filtered by |context_| or |context_simple_|, then | |
295 // it means the key event didn't generate any result text. For some cases, | |
296 // the key event may still generate a valid character, eg. a control-key | |
297 // event (ctrl-a, return, tab, etc.). We need to send the character to the | |
298 // focused text input client by calling TextInputClient::InsertChar(). | |
299 char16 ch = key.GetCharacter(); | |
300 ui::TextInputClient* client = GetTextInputClient(); | |
301 if (ch && client) | |
302 client->InsertChar(ch, key.flags()); | |
303 } | |
304 | |
305 void InputMethodGtk::ProcessInputMethodResult(const KeyEvent& key, | |
306 bool filtered) { | |
307 ui::TextInputClient* client = GetTextInputClient(); | |
308 DCHECK(client); | |
309 | |
310 if (result_text_.length()) { | |
311 if (filtered && NeedInsertChar()) { | |
312 for (string16::const_iterator i = result_text_.begin(); | |
313 i != result_text_.end(); ++i) { | |
314 client->InsertChar(*i, key.flags()); | |
315 } | |
316 } else { | |
317 client->InsertText(result_text_); | |
318 composing_text_ = false; | |
319 } | |
320 } | |
321 | |
322 if (composition_changed_ && !IsTextInputTypeNone()) { | |
323 if (composition_.text.length()) { | |
324 composing_text_ = true; | |
325 client->SetCompositionText(composition_); | |
326 } else if (result_text_.empty()) { | |
327 client->ClearCompositionText(); | |
328 } | |
329 } | |
330 } | |
331 | |
332 bool InputMethodGtk::NeedInsertChar() const { | |
333 return IsTextInputTypeNone() || | |
334 (!composing_text_ && result_text_.length() == 1); | |
335 } | |
336 | |
337 bool InputMethodGtk::HasInputMethodResult() const { | |
338 return result_text_.length() || composition_changed_; | |
339 } | |
340 | |
341 void InputMethodGtk::SendFakeProcessKeyEvent(bool pressed) const { | |
342 KeyEvent key(pressed ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED, | |
343 ui::VKEY_PROCESSKEY, 0); | |
344 DispatchKeyEventPostIME(key); | |
345 } | |
346 | |
347 GdkEvent* InputMethodGtk::SynthesizeGdkEventKey(const KeyEvent& key) const { | |
348 guint keyval = | |
349 ui::GdkKeyCodeForWindowsKeyCode(key.key_code(), key.IsShiftDown()); | |
350 guint state = 0; | |
351 state |= key.IsShiftDown() ? GDK_SHIFT_MASK : 0; | |
352 state |= key.IsControlDown() ? GDK_CONTROL_MASK : 0; | |
353 state |= key.IsAltDown() ? GDK_MOD1_MASK : 0; | |
354 state |= key.IsCapsLockDown() ? GDK_LOCK_MASK : 0; | |
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()) { | |
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 ui::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) { | |
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) { | |
447 gtk_im_context_set_client_window(context_, NULL); | |
448 gtk_im_context_set_client_window(context_simple_, NULL); | |
449 } | |
450 | |
451 } // namespace views | |
OLD | NEW |