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

Side by Side Diff: chrome/browser/renderer_host/gtk_im_context_wrapper.cc

Issue 6709023: Move some common ime code to ui/base/ (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Always put the composition cursor to the selection end. 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
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
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « chrome/browser/renderer_host/gtk_im_context_wrapper.h ('k') | chrome/browser/renderer_host/render_widget_host_view_win.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698