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

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: Address review feedbacks. 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 : 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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698