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

Side by Side Diff: ui/views/ime/input_method_gtk.cc

Issue 9728002: Removing deprecated GTK-Views code. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 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
« no previous file with comments | « ui/views/ime/input_method_gtk.h ('k') | ui/views/metrics_gtk.cc » ('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 "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
OLDNEW
« no previous file with comments | « ui/views/ime/input_method_gtk.h ('k') | ui/views/metrics_gtk.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698