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

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

Powered by Google App Engine
This is Rietveld 408576698