OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/renderer_host/gtk_im_context_wrapper.h" | 5 #include "chrome/browser/renderer_host/gtk_im_context_wrapper.h" |
6 | 6 |
7 #include <gdk/gdk.h> | 7 #include <gdk/gdk.h> |
8 #include <gdk/gdkkeysyms.h> | 8 #include <gdk/gdkkeysyms.h> |
9 #include <gtk/gtk.h> | 9 #include <gtk/gtk.h> |
10 | 10 |
11 #include <algorithm> | 11 #include <algorithm> |
12 | 12 |
13 #include "base/logging.h" | 13 #include "base/logging.h" |
14 #include "base/string_util.h" | 14 #include "base/string_util.h" |
15 #include "base/third_party/icu/icu_utf.h" | 15 #include "base/third_party/icu/icu_utf.h" |
16 #include "base/utf_string_conversions.h" | 16 #include "base/utf_string_conversions.h" |
17 #include "chrome/app/chrome_command_ids.h" | 17 #include "chrome/app/chrome_command_ids.h" |
18 #include "chrome/browser/ui/gtk/gtk_util.h" | 18 #include "chrome/browser/ui/gtk/gtk_util.h" |
19 #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h" | 19 #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h" |
20 #include "chrome/common/render_messages.h" | 20 #include "chrome/common/render_messages.h" |
21 #include "content/browser/renderer_host/render_widget_host.h" | 21 #include "content/browser/renderer_host/render_widget_host.h" |
22 #include "content/common/native_web_keyboard_event.h" | 22 #include "content/common/native_web_keyboard_event.h" |
23 #include "grit/generated_resources.h" | 23 #include "grit/generated_resources.h" |
24 #include "third_party/skia/include/core/SkColor.h" | 24 #include "third_party/skia/include/core/SkColor.h" |
| 25 #include "third_party/WebKit/Source/WebKit/chromium/public/WebCompositionUnderli
ne.h" |
| 26 #include "ui/base/gtk/gtk_im_context_util.h" |
25 #include "ui/base/l10n/l10n_util.h" | 27 #include "ui/base/l10n/l10n_util.h" |
26 #include "ui/gfx/gtk_util.h" | 28 #include "ui/gfx/gtk_util.h" |
27 #include "ui/gfx/rect.h" | 29 #include "ui/gfx/rect.h" |
28 | 30 |
29 #if !defined(TOOLKIT_VIEWS) | 31 #if !defined(TOOLKIT_VIEWS) |
30 #include "chrome/browser/ui/gtk/menu_gtk.h" | 32 #include "chrome/browser/ui/gtk/menu_gtk.h" |
31 #endif | 33 #endif |
32 | 34 |
33 namespace { | 35 namespace { |
34 // Copied from third_party/WebKit/Source/WebCore/page/EventHandler.cpp | 36 // Copied from third_party/WebKit/Source/WebCore/page/EventHandler.cpp |
35 // | 37 // |
36 // Match key code of composition keydown event on windows. | 38 // Match key code of composition keydown event on windows. |
37 // IE sends VK_PROCESSKEY which has value 229; | 39 // IE sends VK_PROCESSKEY which has value 229; |
38 // | 40 // |
39 // Please refer to following documents for detals: | 41 // Please refer to following documents for detals: |
40 // - Virtual-Key Codes | 42 // - Virtual-Key Codes |
41 // http://msdn.microsoft.com/en-us/library/ms645540(VS.85).aspx | 43 // http://msdn.microsoft.com/en-us/library/ms645540(VS.85).aspx |
42 // - How the IME System Works | 44 // - How the IME System Works |
43 // http://msdn.microsoft.com/en-us/library/cc194848.aspx | 45 // http://msdn.microsoft.com/en-us/library/cc194848.aspx |
44 // - ImmGetVirtualKey Function | 46 // - ImmGetVirtualKey Function |
45 // http://msdn.microsoft.com/en-us/library/dd318570(VS.85).aspx | 47 // http://msdn.microsoft.com/en-us/library/dd318570(VS.85).aspx |
46 const int kCompositionEventKeyCode = 229; | 48 const int kCompositionEventKeyCode = 229; |
47 } // namespace | 49 } // namespace |
48 | 50 |
| 51 // ui::CompositionUnderline should be identical to |
| 52 // WebKit::WebCompositionUnderline, so that we can do reinterpret_cast safely. |
| 53 // TODO(suzhe): remove it after migrating all code in chrome to use |
| 54 // ui::CompositionUnderline. |
| 55 COMPILE_ASSERT(sizeof(ui::CompositionUnderline) == |
| 56 sizeof(WebKit::WebCompositionUnderline), |
| 57 ui_CompositionUnderline__WebKit_WebCompositionUnderline_diff); |
| 58 |
49 GtkIMContextWrapper::GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view) | 59 GtkIMContextWrapper::GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view) |
50 : host_view_(host_view), | 60 : host_view_(host_view), |
51 context_(gtk_im_multicontext_new()), | 61 context_(gtk_im_multicontext_new()), |
52 context_simple_(gtk_im_context_simple_new()), | 62 context_simple_(gtk_im_context_simple_new()), |
53 is_focused_(false), | 63 is_focused_(false), |
54 is_composing_text_(false), | 64 is_composing_text_(false), |
55 is_enabled_(false), | 65 is_enabled_(false), |
56 is_in_key_event_handler_(false), | 66 is_in_key_event_handler_(false), |
57 preedit_selection_start_(0), | 67 is_composition_changed_(false), |
58 preedit_selection_end_(0), | |
59 is_preedit_changed_(false), | |
60 suppress_next_commit_(false), | 68 suppress_next_commit_(false), |
61 last_key_code_(0), | 69 last_key_code_(0), |
62 last_key_was_up_(false), | 70 last_key_was_up_(false), |
63 last_key_filtered_no_result_(false) { | 71 last_key_filtered_no_result_(false) { |
64 DCHECK(context_); | 72 DCHECK(context_); |
65 DCHECK(context_simple_); | 73 DCHECK(context_simple_); |
66 | 74 |
67 // context_ and context_simple_ share the same callback handlers. | 75 // context_ and context_simple_ share the same callback handlers. |
68 // All data come from them are treated equally. | 76 // All data come from them are treated equally. |
69 // context_ is for full input method support. | 77 // context_ is for full input method support. |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
107 } | 115 } |
108 | 116 |
109 void GtkIMContextWrapper::ProcessKeyEvent(GdkEventKey* event) { | 117 void GtkIMContextWrapper::ProcessKeyEvent(GdkEventKey* event) { |
110 suppress_next_commit_ = false; | 118 suppress_next_commit_ = false; |
111 | 119 |
112 // Indicates preedit-changed and commit signal handlers that we are | 120 // Indicates preedit-changed and commit signal handlers that we are |
113 // processing a key event. | 121 // processing a key event. |
114 is_in_key_event_handler_ = true; | 122 is_in_key_event_handler_ = true; |
115 // Reset this flag so that we can know if preedit is changed after | 123 // Reset this flag so that we can know if preedit is changed after |
116 // processing this key event. | 124 // processing this key event. |
117 is_preedit_changed_ = false; | 125 is_composition_changed_ = false; |
118 // Clear it so that we can know if something needs committing after | 126 // Clear it so that we can know if something needs committing after |
119 // processing this key event. | 127 // processing this key event. |
120 commit_text_.clear(); | 128 commit_text_.clear(); |
121 | 129 |
122 // According to Document Object Model (DOM) Level 3 Events Specification | 130 // According to Document Object Model (DOM) Level 3 Events Specification |
123 // Appendix A: Keyboard events and key identifiers | 131 // Appendix A: Keyboard events and key identifiers |
124 // http://www.w3.org/TR/DOM-Level-3-Events/keyset.html: | 132 // http://www.w3.org/TR/DOM-Level-3-Events/keyset.html: |
125 // The event sequence would be: | 133 // The event sequence would be: |
126 // 1. keydown | 134 // 1. keydown |
127 // 2. textInput | 135 // 2. textInput |
(...skipping 210 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
338 gtk_im_context_reset(context_simple_); | 346 gtk_im_context_reset(context_simple_); |
339 | 347 |
340 if (is_focused_) { | 348 if (is_focused_) { |
341 // Some input methods may not honour the reset call. Focusing out/in the | 349 // Some input methods may not honour the reset call. Focusing out/in the |
342 // |context_| to make sure it gets reset correctly. | 350 // |context_| to make sure it gets reset correctly. |
343 gtk_im_context_focus_out(context_); | 351 gtk_im_context_focus_out(context_); |
344 gtk_im_context_focus_in(context_); | 352 gtk_im_context_focus_in(context_); |
345 } | 353 } |
346 | 354 |
347 is_composing_text_ = false; | 355 is_composing_text_ = false; |
348 preedit_text_.clear(); | 356 composition_.Clear(); |
349 preedit_underlines_.clear(); | |
350 commit_text_.clear(); | 357 commit_text_.clear(); |
351 | 358 |
352 is_in_key_event_handler_ = false; | 359 is_in_key_event_handler_ = false; |
353 } | 360 } |
354 | 361 |
355 bool GtkIMContextWrapper::NeedCommitByForwardingCharEvent() const { | 362 bool GtkIMContextWrapper::NeedCommitByForwardingCharEvent() const { |
356 // If there is no composition text and has only one character to be | 363 // If there is no composition text and has only one character to be |
357 // committed, then the character will be send to webkit as a Char event | 364 // committed, then the character will be send to webkit as a Char event |
358 // instead of a confirmed composition text. | 365 // instead of a confirmed composition text. |
359 // It should be fine to handle BMP character only, as non-BMP characters | 366 // It should be fine to handle BMP character only, as non-BMP characters |
360 // can always be committed as confirmed composition text. | 367 // can always be committed as confirmed composition text. |
361 return !is_composing_text_ && commit_text_.length() == 1; | 368 return !is_composing_text_ && commit_text_.length() == 1; |
362 } | 369 } |
363 | 370 |
364 bool GtkIMContextWrapper::HasInputMethodResult() const { | 371 bool GtkIMContextWrapper::HasInputMethodResult() const { |
365 return commit_text_.length() || is_preedit_changed_; | 372 return commit_text_.length() || is_composition_changed_; |
366 } | 373 } |
367 | 374 |
368 void GtkIMContextWrapper::ProcessFilteredKeyPressEvent( | 375 void GtkIMContextWrapper::ProcessFilteredKeyPressEvent( |
369 NativeWebKeyboardEvent* wke) { | 376 NativeWebKeyboardEvent* wke) { |
370 // If IME has filtered this event, then replace virtual key code with | 377 // If IME has filtered this event, then replace virtual key code with |
371 // VK_PROCESSKEY. See comment in ProcessKeyEvent() for details. | 378 // VK_PROCESSKEY. See comment in ProcessKeyEvent() for details. |
372 // It's only required for keydown events. | 379 // It's only required for keydown events. |
373 // To emulate windows behavior, when input method is enabled, if the commit | 380 // To emulate windows behavior, when input method is enabled, if the commit |
374 // text can be emulated by a Char event, then don't do this replacement. | 381 // text can be emulated by a Char event, then don't do this replacement. |
375 if (!NeedCommitByForwardingCharEvent()) { | 382 if (!NeedCommitByForwardingCharEvent()) { |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
437 host->ImeConfirmComposition(commit_text_); | 444 host->ImeConfirmComposition(commit_text_); |
438 // Set this flag to false, as this composition session has been | 445 // Set this flag to false, as this composition session has been |
439 // finished. | 446 // finished. |
440 is_composing_text_ = false; | 447 is_composing_text_ = false; |
441 } | 448 } |
442 } | 449 } |
443 | 450 |
444 // Send preedit text only if it's changed. | 451 // Send preedit text only if it's changed. |
445 // If a text has been committed, then we don't need to send the empty | 452 // If a text has been committed, then we don't need to send the empty |
446 // preedit text again. | 453 // preedit text again. |
447 if (is_preedit_changed_) { | 454 if (is_composition_changed_) { |
448 if (preedit_text_.length()) { | 455 if (composition_.text.length()) { |
449 // Another composition session has been started. | 456 // Another composition session has been started. |
450 is_composing_text_ = true; | 457 is_composing_text_ = true; |
451 host->ImeSetComposition(preedit_text_, preedit_underlines_, | 458 // TODO(suzhe): convert both renderer_host and renderer to use |
452 preedit_selection_start_, preedit_selection_end_); | 459 // ui::CompositionText. |
| 460 const std::vector<WebKit::WebCompositionUnderline>& underlines = |
| 461 reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>( |
| 462 composition_.underlines); |
| 463 host->ImeSetComposition(composition_.text, underlines, |
| 464 composition_.selection.start(), |
| 465 composition_.selection.end()); |
453 } else if (!committed) { | 466 } else if (!committed) { |
454 host->ImeCancelComposition(); | 467 host->ImeCancelComposition(); |
455 } | 468 } |
456 } | 469 } |
457 } | 470 } |
458 | 471 |
459 void GtkIMContextWrapper::ConfirmComposition() { | 472 void GtkIMContextWrapper::ConfirmComposition() { |
460 if (!is_enabled_) | 473 if (!is_enabled_) |
461 return; | 474 return; |
462 | 475 |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
501 is_composing_text_ = true; | 514 is_composing_text_ = true; |
502 } | 515 } |
503 | 516 |
504 void GtkIMContextWrapper::HandlePreeditChanged(const gchar* text, | 517 void GtkIMContextWrapper::HandlePreeditChanged(const gchar* text, |
505 PangoAttrList* attrs, | 518 PangoAttrList* attrs, |
506 int cursor_position) { | 519 int cursor_position) { |
507 // Ignore preedit related signals triggered by CancelComposition() method. | 520 // Ignore preedit related signals triggered by CancelComposition() method. |
508 if (suppress_next_commit_) | 521 if (suppress_next_commit_) |
509 return; | 522 return; |
510 | 523 |
511 // Don't set is_preedit_changed_ to false if there is no change, because | 524 // Don't set is_composition_changed_ to false if there is no change, because |
512 // this handler might be called multiple times with the same data. | 525 // this handler might be called multiple times with the same data. |
513 is_preedit_changed_ = true; | 526 is_composition_changed_ = true; |
514 preedit_text_.clear(); | 527 composition_.Clear(); |
515 preedit_underlines_.clear(); | |
516 preedit_selection_start_ = 0; | |
517 preedit_selection_end_ = 0; | |
518 | 528 |
519 ExtractCompositionInfo(text, attrs, cursor_position, &preedit_text_, | 529 ui::ExtractCompositionTextFromGtkPreedit(text, attrs, cursor_position, |
520 &preedit_underlines_, &preedit_selection_start_, | 530 &composition_); |
521 &preedit_selection_end_); | 531 |
| 532 // TODO(suzhe): due to a bug of webkit, we currently can't use selection range |
| 533 // with composition string. See: https://bugs.webkit.org/show_bug.cgi?id=40805 |
| 534 composition_.selection = ui::Range(cursor_position); |
522 | 535 |
523 // In case we are using a buggy input method which doesn't fire | 536 // In case we are using a buggy input method which doesn't fire |
524 // "preedit_start" signal. | 537 // "preedit_start" signal. |
525 if (preedit_text_.length()) | 538 if (composition_.text.length()) |
526 is_composing_text_ = true; | 539 is_composing_text_ = true; |
527 | 540 |
528 // Nothing needs to do, if it's currently in ProcessKeyEvent() | 541 // Nothing needs to do, if it's currently in ProcessKeyEvent() |
529 // handler, which will send preedit text to webkit later. | 542 // handler, which will send preedit text to webkit later. |
530 // Otherwise, we need send it here if it's been changed. | 543 // Otherwise, we need send it here if it's been changed. |
531 if (!is_in_key_event_handler_ && is_composing_text_ && | 544 if (!is_in_key_event_handler_ && is_composing_text_ && |
532 host_view_->GetRenderWidgetHost()) { | 545 host_view_->GetRenderWidgetHost()) { |
533 // Workaround http://crbug.com/45478 by sending fake key down/up events. | 546 // Workaround http://crbug.com/45478 by sending fake key down/up events. |
534 SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown); | 547 SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown); |
| 548 // TODO(suzhe): convert both renderer_host and renderer to use |
| 549 // ui::CompositionText. |
| 550 const std::vector<WebKit::WebCompositionUnderline>& underlines = |
| 551 reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>( |
| 552 composition_.underlines); |
535 host_view_->GetRenderWidgetHost()->ImeSetComposition( | 553 host_view_->GetRenderWidgetHost()->ImeSetComposition( |
536 preedit_text_, preedit_underlines_, preedit_selection_start_, | 554 composition_.text, underlines, composition_.selection.start(), |
537 preedit_selection_end_); | 555 composition_.selection.end()); |
538 SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp); | 556 SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp); |
539 } | 557 } |
540 } | 558 } |
541 | 559 |
542 void GtkIMContextWrapper::HandlePreeditEnd() { | 560 void GtkIMContextWrapper::HandlePreeditEnd() { |
543 if (preedit_text_.length()) { | 561 if (composition_.text.length()) { |
544 // The composition session has been finished. | 562 // The composition session has been finished. |
545 preedit_text_.clear(); | 563 composition_.Clear(); |
546 preedit_underlines_.clear(); | 564 is_composition_changed_ = true; |
547 is_preedit_changed_ = true; | |
548 | 565 |
549 // If there is still a preedit text when firing "preedit-end" signal, | 566 // If there is still a preedit text when firing "preedit-end" signal, |
550 // we need inform webkit to clear it. | 567 // we need inform webkit to clear it. |
551 // It's only necessary when it's not in ProcessKeyEvent (). | 568 // It's only necessary when it's not in ProcessKeyEvent (). |
552 if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) | 569 if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) |
553 host_view_->GetRenderWidgetHost()->ImeCancelComposition(); | 570 host_view_->GetRenderWidgetHost()->ImeCancelComposition(); |
554 } | 571 } |
555 | 572 |
556 // Don't set is_composing_text_ to false here, because "preedit_end" | 573 // Don't set is_composing_text_ to false here, because "preedit_end" |
557 // signal may be fired before "commit" signal. | 574 // signal may be fired before "commit" signal. |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
609 | 626 |
610 void GtkIMContextWrapper::HandleHostViewRealizeThunk( | 627 void GtkIMContextWrapper::HandleHostViewRealizeThunk( |
611 GtkWidget* widget, GtkIMContextWrapper* self) { | 628 GtkWidget* widget, GtkIMContextWrapper* self) { |
612 self->HandleHostViewRealize(widget); | 629 self->HandleHostViewRealize(widget); |
613 } | 630 } |
614 | 631 |
615 void GtkIMContextWrapper::HandleHostViewUnrealizeThunk( | 632 void GtkIMContextWrapper::HandleHostViewUnrealizeThunk( |
616 GtkWidget* widget, GtkIMContextWrapper* self) { | 633 GtkWidget* widget, GtkIMContextWrapper* self) { |
617 self->HandleHostViewUnrealize(); | 634 self->HandleHostViewUnrealize(); |
618 } | 635 } |
619 | |
620 void GtkIMContextWrapper::ExtractCompositionInfo( | |
621 const gchar* utf8_text, | |
622 PangoAttrList* attrs, | |
623 int cursor_position, | |
624 string16* utf16_text, | |
625 std::vector<WebKit::WebCompositionUnderline>* underlines, | |
626 int* selection_start, | |
627 int* selection_end) { | |
628 *utf16_text = UTF8ToUTF16(utf8_text); | |
629 | |
630 if (utf16_text->empty()) | |
631 return; | |
632 | |
633 // Gtk/Pango uses character index for cursor position and byte index for | |
634 // attribute range, but we use char16 offset for them. So we need to do | |
635 // conversion here. | |
636 std::vector<int> char16_offsets; | |
637 int length = static_cast<int>(utf16_text->length()); | |
638 for (int offset = 0; offset < length; ++offset) { | |
639 char16_offsets.push_back(offset); | |
640 if (CBU16_IS_SURROGATE((*utf16_text)[offset])) | |
641 ++offset; | |
642 } | |
643 | |
644 // The text length in Unicode characters. | |
645 int char_length = static_cast<int>(char16_offsets.size()); | |
646 // Make sure we can convert the value of |char_length| as well. | |
647 char16_offsets.push_back(length); | |
648 | |
649 int cursor_offset = | |
650 char16_offsets[std::max(0, std::min(char_length, cursor_position))]; | |
651 | |
652 // TODO(suzhe): due to a bug of webkit, we currently can't use selection range | |
653 // with composition string. See: https://bugs.webkit.org/show_bug.cgi?id=40805 | |
654 *selection_start = *selection_end = cursor_offset; | |
655 | |
656 if (attrs) { | |
657 int utf8_length = strlen(utf8_text); | |
658 PangoAttrIterator* iter = pango_attr_list_get_iterator(attrs); | |
659 | |
660 // We only care about underline and background attributes and convert | |
661 // background attribute into selection if possible. | |
662 do { | |
663 gint start, end; | |
664 pango_attr_iterator_range(iter, &start, &end); | |
665 | |
666 start = std::min(start, utf8_length); | |
667 end = std::min(end, utf8_length); | |
668 if (start >= end) | |
669 continue; | |
670 | |
671 start = g_utf8_pointer_to_offset(utf8_text, utf8_text + start); | |
672 end = g_utf8_pointer_to_offset(utf8_text, utf8_text + end); | |
673 | |
674 // Double check, in case |utf8_text| is not a valid utf-8 string. | |
675 start = std::min(start, char_length); | |
676 end = std::min(end, char_length); | |
677 if (start >= end) | |
678 continue; | |
679 | |
680 PangoAttribute* background_attr = | |
681 pango_attr_iterator_get(iter, PANGO_ATTR_BACKGROUND); | |
682 PangoAttribute* underline_attr = | |
683 pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE); | |
684 | |
685 if (background_attr || underline_attr) { | |
686 // Use a black thin underline by default. | |
687 WebKit::WebCompositionUnderline underline( | |
688 char16_offsets[start], char16_offsets[end], SK_ColorBLACK, false); | |
689 | |
690 // Always use thick underline for a range with background color, which | |
691 // is usually the selection range. | |
692 if (background_attr) | |
693 underline.thick = true; | |
694 if (underline_attr) { | |
695 int type = reinterpret_cast<PangoAttrInt*>(underline_attr)->value; | |
696 if (type == PANGO_UNDERLINE_DOUBLE) | |
697 underline.thick = true; | |
698 else if (type == PANGO_UNDERLINE_ERROR) | |
699 underline.color = SK_ColorRED; | |
700 } | |
701 underlines->push_back(underline); | |
702 } | |
703 } while (pango_attr_iterator_next(iter)); | |
704 pango_attr_iterator_destroy(iter); | |
705 } | |
706 | |
707 // Use a black thin underline by default. | |
708 if (underlines->empty()) { | |
709 underlines->push_back( | |
710 WebKit::WebCompositionUnderline(0, length, SK_ColorBLACK, false)); | |
711 } | |
712 } | |
OLD | NEW |