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

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 according to 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 if (!context_focused_) {
113 DispatchKeyEventPostIME(key);
114 return;
115 }
116
117 handling_key_event_ = true;
118 composition_changed_ = false;
119 result_text_.clear();
120
121 GdkEvent* event = key.native_event();
122 DCHECK(!event || event->type == GDK_KEY_PRESS ||
123 event->type == GDK_KEY_RELEASE);
124
125 // If it's a fake key event, then we need to synthesis a GdkEventKey.
126 bool need_free_event = false;
127 if (!event) {
128 event = SynthesizeGdkEventKey(key);
129 need_free_event = true;
130 }
131
132 gboolean filtered = false;
133 if (GetTextInputType() == ui::TEXT_INPUT_TYPE_TEXT)
134 filtered = gtk_im_context_filter_keypress(context_, &event->key);
135 else
136 filtered = gtk_im_context_filter_keypress(context_simple_, &event->key);
137
138 handling_key_event_ = false;
139
140 View* old_focused_view = focused_view();
141 if (key.type() == ui::ET_KEY_PRESSED && filtered)
142 ProcessFilteredKeyPressEvent(key);
143
144 // In case the focus was changed by the key event.
145 if (old_focused_view != focused_view())
146 return;
147
148 if (HasInputMethodResult())
149 ProcessInputMethodResult(key, filtered);
150
151 // In case the focus was changed when sending input method results to the
152 // focused View.
153 if (old_focused_view != focused_view())
154 return;
155
156 if (key.type() == ui::ET_KEY_PRESSED && !filtered)
157 ProcessUnfilteredKeyPressEvent(key);
158 else if (key.type() == ui::ET_KEY_RELEASED)
159 DispatchKeyEventPostIME(key);
160
161 if (need_free_event)
162 gdk_event_free(event);
163 }
164
165 void InputMethodGtk::OnTextInputTypeChanged(View* view) {
166 if (IsViewFocused(view))
167 UpdateContextFocusState();
168 }
169
170 void InputMethodGtk::OnCaretBoundsChanged(View* view) {
171 gfx::Rect rect;
172 if (!IsViewFocused(view) || !GetCaretBoundsInWidget(&rect))
173 return;
174
175 GdkRectangle gdk_rect = rect.ToGdkRectangle();
176 gtk_im_context_set_cursor_location(context_, &gdk_rect);
177 }
178
179 void InputMethodGtk::CancelComposition(View* view) {
180 if (IsViewFocused(view))
181 ResetContext();
182 }
183
184 std::string InputMethodGtk::GetInputLocale() {
185 // Not supported.
186 return std::string("");
187 }
188
189 base::i18n::TextDirection InputMethodGtk::GetInputTextDirection() {
190 // Not supported.
191 return base::i18n::UNKNOWN_DIRECTION;
192 }
193
194 bool InputMethodGtk::IsActive() {
195 // We always need to send keyboard events to either |context_| or
196 // |context_simple_|, so just return true here.
197 return true;
198 }
199
200 void InputMethodGtk::FocusedViewWillChange() {
201 ConfirmCompositionText();
202 }
203
204 void InputMethodGtk::FocusedViewDidChange() {
205 UpdateContextFocusState();
206 }
207
208 void InputMethodGtk::ConfirmCompositionText() {
209 TextInputClient* client = GetTextInputClient();
210 if (client && client->HasCompositionText())
211 client->ConfirmCompositionText();
212
213 ResetContext();
214 }
215
216 void InputMethodGtk::ResetContext() {
217 if (!context_focused_)
218 return;
219
220 DCHECK(widget_focused());
221 DCHECK(focused_view());
222 DCHECK(!handling_key_event_);
223
224 // To prevent any text from being committed when resetting the |context_|;
225 handling_key_event_ = true;
226 suppress_next_result_ = true;
227
228 gtk_im_context_reset(context_);
229 gtk_im_context_reset(context_simple_);
230
231 // Some input methods may not honour the reset call. Focusing out/in the
232 // |context_| to make sure it gets reset correctly.
233 gtk_im_context_focus_out(context_);
234 gtk_im_context_focus_in(context_);
235
236 composition_.Clear();
237 result_text_.clear();
238 handling_key_event_ = false;
239 composing_text_ = false;
240 composition_changed_ = false;
241 }
242
243 void InputMethodGtk::UpdateContextFocusState() {
244 bool old_context_focused = context_focused_;
245 context_focused_ = (GetTextInputClient() != NULL);
246
247 if (old_context_focused && !context_focused_) {
248 gtk_im_context_focus_out(context_);
249 gtk_im_context_focus_out(context_simple_);
250 } else if (!old_context_focused && context_focused_) {
251 gtk_im_context_focus_in(context_);
252 gtk_im_context_focus_in(context_simple_);
253 }
254 }
255
256 void InputMethodGtk::ProcessFilteredKeyPressEvent(const KeyEvent& key) {
257 if (NeedInsertChar()) {
258 DispatchKeyEventPostIME(key);
259 } else {
260 KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, key.flags());
261 DispatchKeyEventPostIME(key);
262 }
263 }
264
265 void InputMethodGtk::ProcessUnfilteredKeyPressEvent(const KeyEvent& key) {
266 DispatchKeyEventPostIME(key);
267
268 // If a key event was not filtered by |context_| or |context_simple_|, then
269 // it means the key event didn't generate any result text. For some cases,
270 // the key event may still generate a valid character, eg. a control-key
271 // event (ctrl-a, return, tab, etc.). We need to send the character to the
272 // focused text input client by calling TextInputClient::InsertChar().
273 char16 ch = key.GetCharacter();
274 TextInputClient* client = GetTextInputClient();
275 if (ch && client)
276 client->InsertChar(ch, key.flags());
277 }
278
279 void InputMethodGtk::ProcessInputMethodResult(const KeyEvent& key,
280 bool filtered) {
281 TextInputClient* client = GetTextInputClient();
282 DCHECK(client);
283
284 if (result_text_.length()) {
285 if (filtered && NeedInsertChar()) {
286 for (string16::const_iterator i = result_text_.begin();
287 i != result_text_.end(); ++i) {
288 client->InsertChar(*i, key.flags());
289 }
290 } else {
291 client->InsertText(result_text_);
292 composing_text_ = false;
293 }
294 }
295
296 if (composition_changed_ && IsClientSupportTextInput()) {
297 if (composition_.text.length()) {
298 composing_text_ = true;
299 client->SetCompositionText(composition_);
300 } else if (result_text_.empty()) {
301 client->ClearCompositionText();
302 }
303 }
304 }
305
306 bool InputMethodGtk::NeedInsertChar() const {
307 return !IsClientSupportTextInput() ||
308 (!composing_text_ && result_text_.length() == 1);
309 }
310
311 bool InputMethodGtk::HasInputMethodResult() const {
312 return result_text_.length() || composition_changed_;
313 }
314
315 void InputMethodGtk::SendFakeProcessKeyEvent(bool pressed) const {
316 KeyEvent key(pressed ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED,
317 ui::VKEY_PROCESSKEY, 0);
318 DispatchKeyEventPostIME(key);
319 }
320
321 GdkEvent* InputMethodGtk::SynthesizeGdkEventKey(const KeyEvent& key) const {
322 guint keyval =
323 ui::GdkKeyCodeForWindowsKeyCode(key.key_code(), key.IsShiftDown());
324 guint state = 0;
325 if (key.IsShiftDown())
326 state |= GDK_SHIFT_MASK;
327 if (key.IsControlDown())
328 state |= GDK_CONTROL_MASK;
329 if (key.IsAltDown())
330 state |= GDK_MOD1_MASK;
331 if (key.IsCapsLockDown())
332 state |= GDK_LOCK_MASK;
333
334 DCHECK(widget()->GetNativeView()->window);
335 return ui::SynthesizeKeyEvent(widget()->GetNativeView()->window,
336 key.type() == ui::ET_KEY_PRESSED,
337 keyval, state);
338 }
339
340 void InputMethodGtk::OnCommit(GtkIMContext* context, gchar* text) {
341 if (suppress_next_result_) {
342 suppress_next_result_ = false;
343 return;
344 }
345
346 if (!context_focused_)
347 return;
348
349 string16 utf16_text(UTF8ToUTF16(text));
350
351 // Append the text to the buffer, because commit signal might be fired
352 // multiple times when processing a key event.
353 result_text_.append(utf16_text);
354 if (!handling_key_event_ && IsClientSupportTextInput()) {
355 SendFakeProcessKeyEvent(true);
356 GetTextInputClient()->InsertText(utf16_text);
357 SendFakeProcessKeyEvent(false);
358 }
359 }
360
361 void InputMethodGtk::OnPreeditStart(GtkIMContext* context) {
362 if (suppress_next_result_ || !context_focused_)
363 return;
364
365 composing_text_ = true;
366 }
367
368 void InputMethodGtk::OnPreeditChanged(GtkIMContext* context) {
369 if (suppress_next_result_ || !context_focused_)
370 return;
371
372 gchar* text = NULL;
373 PangoAttrList* attrs = NULL;
374 gint cursor_position = 0;
375 gtk_im_context_get_preedit_string(context, &text, &attrs, &cursor_position);
376
377 ui::ExtractCompositionTextFromGtkPreedit(text, attrs, cursor_position,
378 &composition_);
379 composition_changed_ = true;
380
381 g_free(text);
382 pango_attr_list_unref(attrs);
383
384 if (composition_.text.length())
385 composing_text_ = true;
386
387 if (!handling_key_event_ && IsClientSupportTextInput()) {
388 SendFakeProcessKeyEvent(true);
389 GetTextInputClient()->SetCompositionText(composition_);
390 SendFakeProcessKeyEvent(false);
391 }
392 }
393
394 void InputMethodGtk::OnPreeditEnd(GtkIMContext* context) {
395 if (!context_focused_ || composition_.text.empty())
396 return;
397
398 composition_changed_ = true;
399 composition_.Clear();
400
401 if (!handling_key_event_) {
402 TextInputClient* client = GetTextInputClient();
403 if (client && client->HasCompositionText())
404 client->ClearCompositionText();
405 }
406 }
407
408 void InputMethodGtk::OnWidgetRealize(GtkWidget* widget) {
409 // We should only set im context's client window once, because when setting
410 // client window, im context may destroy and recreate its internal states and
411 // objects.
412 if (widget->window) {
413 gtk_im_context_set_client_window(context_, widget->window);
414 gtk_im_context_set_client_window(context_simple_, widget->window);
415 }
416 }
417
418 void InputMethodGtk::OnWidgetUnrealize(GtkWidget* widget) {
419 gtk_im_context_set_client_window(context_, NULL);
420 gtk_im_context_set_client_window(context_simple_, NULL);
421 }
422
423 gboolean InputMethodGtk::OnWidgetFocusIn(GtkWidget* widget,
424 GdkEventFocus* event) {
425 if (!widget_focused()) {
426 set_widget_focused(true);
427 UpdateContextFocusState();
428 }
429 return false;
430 }
431
432 gboolean InputMethodGtk::OnWidgetFocusOut(GtkWidget* widget,
433 GdkEventFocus* event) {
434 if (widget_focused()) {
435 ConfirmCompositionText();
436 set_widget_focused(false);
437 UpdateContextFocusState();
438 }
439 return false;
440 }
441
442 } // namespace views
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698