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

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

Powered by Google App Engine
This is Rietveld 408576698