summaryrefslogtreecommitdiffstats
path: root/views
diff options
context:
space:
mode:
authorsuzhe@google.com <suzhe@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-01 22:05:14 +0000
committersuzhe@google.com <suzhe@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-01 22:05:14 +0000
commit6c8a4312deb82e599b2c9695b2ec22aa1e298271 (patch)
treeb524c5d2d193ca166da62b4906d19e899a850f47 /views
parent38788d94bc411c89d6a0bbebe6e0628e401e43e4 (diff)
downloadchromium_src-6c8a4312deb82e599b2c9695b2ec22aa1e298271.zip
chromium_src-6c8a4312deb82e599b2c9695b2ec22aa1e298271.tar.gz
chromium_src-6c8a4312deb82e599b2c9695b2ec22aa1e298271.tar.bz2
Integrate the new input method API for Views into Chromium.
This CL contains following changes: 1. Improve TextfieldViewsModel to support composition text. 2. Implement TextInputClient interface in NativeTextfieldViews. 3. Fix Textfield and native implementations to avoid calling TextfieldController::ContentsChanged() when the text is changed by calling Textfield::SetText() or AppendText(). 4. Fix a bug in FocusManager that causes NativeTextfieldViewsTest.FocusTraversalTest to fail. 5. Fix accelerator_handler_touch.cc to send key events to the top-level widget's input method instance instead of the root view. 6. Do the same fix in extension_input_api.cc. 7. Hook InputMethod into WidgetGtk and WidgetWin. BUG=75003 TEST=views_unittests --gtest_filter=NativeTextfieldViews* Review URL: http://codereview.chromium.org/6675005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@80226 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views')
-rw-r--r--views/controls/textfield/native_textfield_gtk.cc15
-rw-r--r--views/controls/textfield/native_textfield_gtk.h1
-rw-r--r--views/controls/textfield/native_textfield_views.cc239
-rw-r--r--views/controls/textfield/native_textfield_views.h43
-rw-r--r--views/controls/textfield/native_textfield_views_unittest.cc403
-rw-r--r--views/controls/textfield/native_textfield_win.cc8
-rw-r--r--views/controls/textfield/native_textfield_win.h1
-rw-r--r--views/controls/textfield/native_textfield_wrapper.h5
-rw-r--r--views/controls/textfield/textfield.cc14
-rw-r--r--views/controls/textfield/textfield.h1
-rw-r--r--views/controls/textfield/textfield_controller.h4
-rw-r--r--views/controls/textfield/textfield_views_model.cc308
-rw-r--r--views/controls/textfield/textfield_views_model.h108
-rw-r--r--views/controls/textfield/textfield_views_model_unittest.cc274
-rw-r--r--views/focus/accelerator_handler_touch.cc17
-rw-r--r--views/focus/focus_manager.cc6
-rw-r--r--views/view.cc4
-rw-r--r--views/widget/native_widget.h12
-rw-r--r--views/widget/widget.cc7
-rw-r--r--views/widget/widget.h5
-rw-r--r--views/widget/widget_gtk.cc129
-rw-r--r--views/widget/widget_gtk.h14
-rw-r--r--views/widget/widget_win.cc113
-rw-r--r--views/widget/widget_win.h31
24 files changed, 1439 insertions, 323 deletions
diff --git a/views/controls/textfield/native_textfield_gtk.cc b/views/controls/textfield/native_textfield_gtk.cc
index ec7c271..77b3940 100644
--- a/views/controls/textfield/native_textfield_gtk.cc
+++ b/views/controls/textfield/native_textfield_gtk.cc
@@ -376,6 +376,10 @@ void NativeTextfieldGtk::HandleFocus() {
void NativeTextfieldGtk::HandleBlur() {
}
+TextInputClient* NativeTextfieldGtk::GetTextInputClient() {
+ return NULL;
+}
+
// static
gboolean NativeTextfieldGtk::OnKeyPressEventHandler(
GtkWidget* widget,
@@ -405,18 +409,16 @@ gboolean NativeTextfieldGtk::OnActivate() {
if (!event || event->type != GDK_KEY_PRESS)
return false;
- GdkEventKey* key_event = reinterpret_cast<GdkEventKey*>(event);
+ KeyEvent views_key_event(event);
gboolean handled = false;
TextfieldController* controller = textfield_->GetController();
- if (controller) {
- KeyEvent views_key_event(event);
+ if (controller)
handled = controller->HandleKeyEvent(textfield_, views_key_event);
- }
WidgetGtk* widget = static_cast<WidgetGtk*>(GetWidget());
if (!handled && widget)
- handled = widget->HandleKeyboardEvent(key_event);
+ handled = widget->HandleKeyboardEvent(views_key_event);
return handled;
}
@@ -430,9 +432,6 @@ gboolean NativeTextfieldGtk::OnChangedHandler(
gboolean NativeTextfieldGtk::OnChanged() {
textfield_->SyncText();
- TextfieldController* controller = textfield_->GetController();
- if (controller)
- controller->ContentsChanged(textfield_, GetText());
textfield_->GetWidget()->NotifyAccessibilityEvent(
textfield_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true);
return false;
diff --git a/views/controls/textfield/native_textfield_gtk.h b/views/controls/textfield/native_textfield_gtk.h
index ee728e5..b28dd2e 100644
--- a/views/controls/textfield/native_textfield_gtk.h
+++ b/views/controls/textfield/native_textfield_gtk.h
@@ -55,6 +55,7 @@ class NativeTextfieldGtk : public NativeControlGtk,
virtual bool HandleKeyReleased(const views::KeyEvent& e) OVERRIDE;
virtual void HandleFocus() OVERRIDE;
virtual void HandleBlur() OVERRIDE;
+ virtual TextInputClient* GetTextInputClient() OVERRIDE;
// Overridden from NativeControlGtk:
virtual void CreateNativeControl() OVERRIDE;
diff --git a/views/controls/textfield/native_textfield_views.cc b/views/controls/textfield/native_textfield_views.cc
index e1b8ccc..73b95f3 100644
--- a/views/controls/textfield/native_textfield_views.cc
+++ b/views/controls/textfield/native_textfield_views.cc
@@ -23,6 +23,7 @@
#include "views/controls/textfield/textfield_controller.h"
#include "views/controls/textfield/textfield_views_model.h"
#include "views/events/event.h"
+#include "views/ime/input_method.h"
#include "views/metrics.h"
#include "views/views_delegate.h"
@@ -61,11 +62,12 @@ const char NativeTextfieldViews::kViewClassName[] =
NativeTextfieldViews::NativeTextfieldViews(Textfield* parent)
: textfield_(parent),
- model_(new TextfieldViewsModel()),
+ ALLOW_THIS_IN_INITIALIZER_LIST(model_(new TextfieldViewsModel(this))),
text_border_(new TextfieldBorder()),
text_offset_(0),
insert_(true),
is_cursor_visible_(false),
+ skip_input_method_cancel_composition_(false),
ALLOW_THIS_IN_INITIALIZER_LIST(cursor_timer_(this)),
last_mouse_press_time_(base::Time::FromInternalValue(0)),
click_state_(NONE) {
@@ -160,14 +162,9 @@ string16 NativeTextfieldViews::GetText() const {
}
void NativeTextfieldViews::UpdateText() {
- bool changed = model_->SetText(textfield_->text());
+ model_->SetText(textfield_->text());
UpdateCursorBoundsAndTextOffset();
SchedulePaint();
- if (changed) {
- TextfieldController* controller = textfield_->GetController();
- if (controller)
- controller->ContentsChanged(textfield_, GetText());
- }
}
void NativeTextfieldViews::AppendText(const string16& text) {
@@ -176,10 +173,6 @@ void NativeTextfieldViews::AppendText(const string16& text) {
model_->Append(text);
UpdateCursorBoundsAndTextOffset();
SchedulePaint();
-
- TextfieldController* controller = textfield_->GetController();
- if (controller)
- controller->ContentsChanged(textfield_, GetText());
}
string16 NativeTextfieldViews::GetSelectedText() const {
@@ -220,6 +213,7 @@ void NativeTextfieldViews::UpdateBackgroundColor() {
void NativeTextfieldViews::UpdateReadOnly() {
SchedulePaint();
+ OnTextInputTypeChanged();
}
void NativeTextfieldViews::UpdateFont() {
@@ -230,11 +224,13 @@ void NativeTextfieldViews::UpdateIsPassword() {
model_->set_is_password(textfield_->IsPassword());
UpdateCursorBoundsAndTextOffset();
SchedulePaint();
+ OnTextInputTypeChanged();
}
void NativeTextfieldViews::UpdateEnabled() {
SetEnabled(textfield_->IsEnabled());
SchedulePaint();
+ OnTextInputTypeChanged();
}
gfx::Insets NativeTextfieldViews::CalculateInsets() {
@@ -275,7 +271,7 @@ gfx::NativeView NativeTextfieldViews::GetTestingHandle() const {
}
bool NativeTextfieldViews::IsIMEComposing() const {
- return false;
+ return model_->HasCompositionText();
}
void NativeTextfieldViews::GetSelectedRange(ui::Range* range) const {
@@ -307,6 +303,7 @@ bool NativeTextfieldViews::HandleKeyReleased(const views::KeyEvent& e) {
void NativeTextfieldViews::HandleFocus() {
is_cursor_visible_ = true;
SchedulePaint();
+ OnCaretBoundsChanged();
// Start blinking cursor.
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
@@ -323,6 +320,10 @@ void NativeTextfieldViews::HandleBlur() {
}
}
+TextInputClient* NativeTextfieldViews::GetTextInputClient() {
+ return textfield_->read_only() ? NULL : this;
+}
+
/////////////////////////////////////////////////////////////////
// NativeTextfieldViews, ui::SimpleMenuModel::Delegate overrides:
@@ -412,7 +413,174 @@ void NativeTextfieldViews::OnBoundsChanged(const gfx::Rect& previous_bounds) {
///////////////////////////////////////////////////////////////////////////////
-// NativeTextfieldViews private:
+// NativeTextfieldViews, TextInputClient implementation, private:
+
+void NativeTextfieldViews::SetCompositionText(
+ const ui::CompositionText& composition) {
+ if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE)
+ return;
+
+ OnBeforeUserAction();
+ skip_input_method_cancel_composition_ = true;
+ model_->SetCompositionText(composition);
+ skip_input_method_cancel_composition_ = false;
+ UpdateAfterChange(true, true);
+ OnAfterUserAction();
+}
+
+void NativeTextfieldViews::ConfirmCompositionText() {
+ if (!model_->HasCompositionText())
+ return;
+
+ OnBeforeUserAction();
+ skip_input_method_cancel_composition_ = true;
+ model_->ConfirmCompositionText();
+ skip_input_method_cancel_composition_ = false;
+ UpdateAfterChange(true, true);
+ OnAfterUserAction();
+}
+
+void NativeTextfieldViews::ClearCompositionText() {
+ if (!model_->HasCompositionText())
+ return;
+
+ OnBeforeUserAction();
+ skip_input_method_cancel_composition_ = true;
+ model_->ClearCompositionText();
+ skip_input_method_cancel_composition_ = false;
+ UpdateAfterChange(true, true);
+ OnAfterUserAction();
+}
+
+void NativeTextfieldViews::InsertText(const string16& text) {
+ // TODO(suzhe): Filter invalid characters.
+ if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE || text.empty())
+ return;
+
+ OnBeforeUserAction();
+ skip_input_method_cancel_composition_ = true;
+ if (insert_)
+ model_->InsertText(text);
+ else
+ model_->ReplaceText(text);
+ skip_input_method_cancel_composition_ = false;
+ UpdateAfterChange(true, true);
+ OnAfterUserAction();
+}
+
+void NativeTextfieldViews::InsertChar(char16 ch, int flags) {
+ if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE ||
+ !ShouldInsertChar(ch, flags)) {
+ return;
+ }
+
+ OnBeforeUserAction();
+ skip_input_method_cancel_composition_ = true;
+ if (insert_)
+ model_->InsertChar(ch);
+ else
+ model_->ReplaceChar(ch);
+ skip_input_method_cancel_composition_ = false;
+ UpdateAfterChange(true, true);
+ OnAfterUserAction();
+}
+
+ui::TextInputType NativeTextfieldViews::GetTextInputType() {
+ if (textfield_->read_only() || !textfield_->IsEnabled())
+ return ui::TEXT_INPUT_TYPE_NONE;
+ else if (textfield_->IsPassword())
+ return ui::TEXT_INPUT_TYPE_PASSWORD;
+ return ui::TEXT_INPUT_TYPE_TEXT;
+}
+
+gfx::Rect NativeTextfieldViews::GetCaretBounds() {
+ return cursor_bounds_;
+}
+
+bool NativeTextfieldViews::HasCompositionText() {
+ return model_->HasCompositionText();
+}
+
+bool NativeTextfieldViews::GetTextRange(ui::Range* range) {
+ // We don't allow the input method to retrieve or delete content from a
+ // password box.
+ if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT)
+ return false;
+
+ model_->GetTextRange(range);
+ return true;
+}
+
+bool NativeTextfieldViews::GetCompositionTextRange(ui::Range* range) {
+ if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT)
+ return false;
+
+ model_->GetCompositionTextRange(range);
+ return true;
+}
+
+bool NativeTextfieldViews::GetSelectionRange(ui::Range* range) {
+ if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT)
+ return false;
+
+ model_->GetSelectedRange(range);
+ return true;
+}
+
+bool NativeTextfieldViews::SetSelectionRange(const ui::Range& range) {
+ if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT || !range.IsValid())
+ return false;
+
+ OnBeforeUserAction();
+ SelectRange(range);
+ OnAfterUserAction();
+ return true;
+}
+
+bool NativeTextfieldViews::DeleteRange(const ui::Range& range) {
+ if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT || range.is_empty())
+ return false;
+
+ OnBeforeUserAction();
+ model_->SelectRange(range);
+ if (model_->HasSelection()) {
+ model_->DeleteSelection();
+ UpdateAfterChange(true, true);
+ }
+ OnAfterUserAction();
+ return true;
+}
+
+bool NativeTextfieldViews::GetTextFromRange(
+ const ui::Range& range,
+ const base::Callback<void(const string16&)>& callback) {
+ if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT || range.is_empty())
+ return false;
+
+ callback.Run(model_->GetTextFromRange(range));
+ return true;
+}
+
+void NativeTextfieldViews::OnInputMethodChanged() {
+ NOTIMPLEMENTED();
+}
+
+bool NativeTextfieldViews::ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection direction) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+View* NativeTextfieldViews::GetOwnerViewOfTextInputClient() {
+ return textfield_;
+}
+
+void NativeTextfieldViews::OnCompositionTextConfirmedOrCleared() {
+ if (skip_input_method_cancel_composition_)
+ return;
+ DCHECK(textfield_->GetInputMethod());
+ textfield_->GetInputMethod()->CancelComposition(textfield_);
+}
const gfx::Font& NativeTextfieldViews::GetFont() const {
return textfield_->font();
@@ -475,6 +643,8 @@ void NativeTextfieldViews::UpdateCursorBoundsAndTextOffset() {
}
// shift cursor bounds to fit insets.
cursor_bounds_.set_x(cursor_bounds_.x() + text_offset_ + insets.left());
+
+ OnCaretBoundsChanged();
}
void NativeTextfieldViews::PaintTextAndCursor(gfx::Canvas* canvas) {
@@ -501,17 +671,22 @@ void NativeTextfieldViews::PaintTextAndCursor(gfx::Canvas* canvas) {
fragments.begin();
iter != fragments.end();
iter++) {
- string16 text = model_->GetVisibleText((*iter).begin, (*iter).end);
+ string16 text = model_->GetVisibleText(iter->start, iter->end);
+
+ gfx::Font font = GetFont();
+ if (iter->underline)
+ font = font.DeriveFont(0, font.GetStyle() | gfx::Font::UNDERLINED);
+
// TODO(oshima): This does not give the accurate position due to
// kerning. Figure out how webkit does this with skia.
- int width = GetFont().GetStringWidth(text);
+ int width = font.GetStringWidth(text);
- if ((*iter).selected) {
+ if (iter->selected) {
canvas->FillRectInt(selection_color, x_offset, y, width, text_height);
- canvas->DrawStringInt(text, GetFont(), kSelectedTextColor,
+ canvas->DrawStringInt(text, font, kSelectedTextColor,
x_offset, y, width, text_height);
} else {
- canvas->DrawStringInt(text, GetFont(), text_color,
+ canvas->DrawStringInt(text, font, text_color,
x_offset, y, width, text_height);
}
x_offset += width;
@@ -577,7 +752,7 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) {
cursor_changed = true;
break;
case ui::VKEY_HOME:
- model_->MoveCursorToStart(selection);
+ model_->MoveCursorToHome(selection);
cursor_changed = true;
break;
case ui::VKEY_BACK:
@@ -590,7 +765,7 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) {
#if defined(OS_WIN)
break;
#else
- model_->MoveCursorToStart(true);
+ model_->MoveCursorToHome(true);
#endif
} else if (control) {
// If only control is pressed, then erase the previous word.
@@ -626,14 +801,9 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) {
default:
break;
}
- char16 ch = key_event.GetCharacter();
- if (editable && ShouldInsertChar(ch, key_event.flags())) {
- if (insert_)
- model_->Insert(ch);
- else
- model_->Replace(ch);
- text_changed = true;
- }
+
+ // We must have input method in order to support text input.
+ DCHECK(textfield_->GetInputMethod());
UpdateAfterChange(text_changed, cursor_changed);
OnAfterUserAction();
@@ -717,9 +887,6 @@ void NativeTextfieldViews::SetCursorForMouseClick(const views::MouseEvent& e) {
void NativeTextfieldViews::PropagateTextChange() {
textfield_->SyncText();
- TextfieldController* controller = textfield_->GetController();
- if (controller)
- controller->ContentsChanged(textfield_, GetText());
}
void NativeTextfieldViews::UpdateAfterChange(bool text_changed,
@@ -750,6 +917,16 @@ void NativeTextfieldViews::InitContextMenuIfRequired() {
context_menu_menu_.reset(new Menu2(context_menu_contents_.get()));
}
+void NativeTextfieldViews::OnTextInputTypeChanged() {
+ DCHECK(textfield_->GetInputMethod());
+ textfield_->GetInputMethod()->OnTextInputTypeChanged(textfield_);
+}
+
+void NativeTextfieldViews::OnCaretBoundsChanged() {
+ DCHECK(textfield_->GetInputMethod());
+ textfield_->GetInputMethod()->OnCaretBoundsChanged(textfield_);
+}
+
void NativeTextfieldViews::OnBeforeUserAction() {
TextfieldController* controller = textfield_->GetController();
if (controller)
diff --git a/views/controls/textfield/native_textfield_views.h b/views/controls/textfield/native_textfield_views.h
index c91281b..68ffbb4 100644
--- a/views/controls/textfield/native_textfield_views.h
+++ b/views/controls/textfield/native_textfield_views.h
@@ -12,6 +12,8 @@
#include "ui/gfx/font.h"
#include "views/border.h"
#include "views/controls/textfield/native_textfield_wrapper.h"
+#include "views/controls/textfield/textfield_views_model.h"
+#include "views/ime/text_input_client.h"
#include "views/view.h"
namespace base {
@@ -26,7 +28,6 @@ namespace views {
class KeyEvent;
class Menu2;
-class TextfieldViewsModel;
// A views/skia only implementation of NativeTextfieldWrapper.
// No platform specific code is used.
@@ -41,7 +42,9 @@ class TextfieldViewsModel;
class NativeTextfieldViews : public views::View,
public views::ContextMenuController,
public NativeTextfieldWrapper,
- public ui::SimpleMenuModel::Delegate {
+ public ui::SimpleMenuModel::Delegate,
+ public TextInputClient,
+ public TextfieldViewsModel::Delegate {
public:
explicit NativeTextfieldViews(Textfield* parent);
~NativeTextfieldViews();
@@ -90,6 +93,7 @@ class NativeTextfieldViews : public views::View,
virtual bool HandleKeyReleased(const views::KeyEvent& e) OVERRIDE;
virtual void HandleFocus() OVERRIDE;
virtual void HandleBlur() OVERRIDE;
+ virtual TextInputClient* GetTextInputClient() OVERRIDE;
// ui::SimpleMenuModel::Delegate overrides
virtual bool IsCommandIdChecked(int command_id) const OVERRIDE;
@@ -147,6 +151,32 @@ class NativeTextfieldViews : public views::View,
DISALLOW_COPY_AND_ASSIGN(TextfieldBorder);
};
+ // Overridden from TextInputClient:
+ virtual void SetCompositionText(
+ const ui::CompositionText& composition) OVERRIDE;
+ virtual void ConfirmCompositionText() OVERRIDE;
+ virtual void ClearCompositionText() OVERRIDE;
+ virtual void InsertText(const string16& text) OVERRIDE;
+ virtual void InsertChar(char16 ch, int flags) OVERRIDE;
+ virtual ui::TextInputType GetTextInputType() OVERRIDE;
+ virtual gfx::Rect GetCaretBounds() OVERRIDE;
+ virtual bool HasCompositionText() OVERRIDE;
+ virtual bool GetTextRange(ui::Range* range) OVERRIDE;
+ virtual bool GetCompositionTextRange(ui::Range* range) OVERRIDE;
+ virtual bool GetSelectionRange(ui::Range* range) OVERRIDE;
+ virtual bool SetSelectionRange(const ui::Range& range) OVERRIDE;
+ virtual bool DeleteRange(const ui::Range& range) OVERRIDE;
+ virtual bool GetTextFromRange(
+ const ui::Range& range,
+ const base::Callback<void(const string16&)>& callback) OVERRIDE;
+ virtual void OnInputMethodChanged() OVERRIDE;
+ virtual bool ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection direction) OVERRIDE;
+ virtual View* GetOwnerViewOfTextInputClient() OVERRIDE;
+
+ // Overridden from TextfieldViewsModel::Delegate:
+ virtual void OnCompositionTextConfirmedOrCleared() OVERRIDE;
+
// Returns the Textfield's font.
const gfx::Font& GetFont() const;
@@ -188,6 +218,12 @@ class NativeTextfieldViews : public views::View,
// Utility function to create the context menu if one does not already exist.
void InitContextMenuIfRequired();
+ // Convenience method to call InputMethod::OnTextInputTypeChanged();
+ void OnTextInputTypeChanged();
+
+ // Convenience method to call InputMethod::OnCaretBoundsChanged();
+ void OnCaretBoundsChanged();
+
// Convenience method to call TextfieldController::OnBeforeUserAction();
void OnBeforeUserAction();
@@ -219,6 +255,9 @@ class NativeTextfieldViews : public views::View,
// The drawing state of cursor. True to draw.
bool is_cursor_visible_;
+ // True if InputMethod::CancelComposition() should not be called.
+ bool skip_input_method_cancel_composition_;
+
// A runnable method factory for callback to update the cursor.
ScopedRunnableMethodFactory<NativeTextfieldViews> cursor_timer_;
diff --git a/views/controls/textfield/native_textfield_views_unittest.cc b/views/controls/textfield/native_textfield_views_unittest.cc
index 7e2bab4..92f0e25 100644
--- a/views/controls/textfield/native_textfield_views_unittest.cc
+++ b/views/controls/textfield/native_textfield_views_unittest.cc
@@ -3,6 +3,9 @@
// found in the LICENSE file.
#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
#include "base/message_loop.h"
#include "base/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -16,11 +19,74 @@
#include "views/controls/textfield/textfield_views_model.h"
#include "views/events/event.h"
#include "views/focus/focus_manager.h"
+#include "views/ime/mock_input_method.h"
+#include "views/ime/text_input_client.h"
#include "views/test/test_views_delegate.h"
#include "views/test/views_test_base.h"
#include "views/views_delegate.h"
+#include "views/widget/native_widget.h"
#include "views/widget/widget.h"
+namespace {
+
+// A wrapper of Textfield to intercept the result of OnKeyPressed() and
+// OnKeyReleased() methods.
+class TestTextfield : public views::Textfield {
+ public:
+ TestTextfield()
+ : key_handled_(false),
+ key_received_(false) {
+ }
+
+ explicit TestTextfield(StyleFlags style)
+ : Textfield(style),
+ key_handled_(false),
+ key_received_(false) {
+ }
+
+ virtual bool OnKeyPressed(const views::KeyEvent& e) OVERRIDE {
+ key_received_ = true;
+ key_handled_ = views::Textfield::OnKeyPressed(e);
+ return key_handled_;
+ }
+
+ virtual bool OnKeyReleased(const views::KeyEvent& e) OVERRIDE {
+ key_received_ = true;
+ key_handled_ = views::Textfield::OnKeyReleased(e);
+ return key_handled_;
+ }
+
+ bool key_handled() const { return key_handled_; }
+ bool key_received() const { return key_received_; }
+
+ void clear() {
+ key_received_ = key_handled_ = false;
+ }
+
+ private:
+ bool key_handled_;
+ bool key_received_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTextfield);
+};
+
+// A helper class for use with TextInputClient::GetTextFromRange().
+class GetTextHelper {
+ public:
+ GetTextHelper() {
+ }
+
+ void set_text(const string16& text) { text_ = text; }
+ const string16& text() const { return text_; }
+
+ private:
+ string16 text_;
+
+ DISALLOW_COPY_AND_ASSIGN(GetTextHelper);
+};
+
+} // namespace
+
namespace views {
// Convert to Wide so that the printed string will be readable when
@@ -40,7 +106,10 @@ class NativeTextfieldViewsTest : public ViewsTestBase,
: widget_(NULL),
textfield_(NULL),
textfield_view_(NULL),
- model_(NULL) {
+ model_(NULL),
+ input_method_(NULL),
+ on_before_user_action_(0),
+ on_after_user_action_(0) {
}
// ::testing::Test:
@@ -57,7 +126,8 @@ class NativeTextfieldViewsTest : public ViewsTestBase,
// TextfieldController:
virtual void ContentsChanged(Textfield* sender,
- const string16& new_contents){
+ const string16& new_contents) {
+ ASSERT_NE(last_contents_, new_contents);
last_contents_ = new_contents;
}
@@ -68,13 +138,21 @@ class NativeTextfieldViewsTest : public ViewsTestBase,
return false;
}
+ virtual void OnBeforeUserAction(Textfield* sender) {
+ ++on_before_user_action_;
+ }
+
+ virtual void OnAfterUserAction(Textfield* sender) {
+ ++on_after_user_action_;
+ }
+
void InitTextfield(Textfield::StyleFlags style) {
InitTextfields(style, 1);
}
void InitTextfields(Textfield::StyleFlags style, int count) {
ASSERT_FALSE(textfield_);
- textfield_ = new Textfield(style);
+ textfield_ = new TestTextfield(style);
textfield_->SetController(this);
Widget::CreateParams params(Widget::CreateParams::TYPE_POPUP);
params.mirror_origin_in_rtl = false;
@@ -96,6 +174,14 @@ class NativeTextfieldViewsTest : public ViewsTestBase,
DCHECK(textfield_view_);
model_ = textfield_view_->model_.get();
+
+ input_method_ = new MockInputMethod();
+ widget_->native_widget()->ReplaceInputMethod(input_method_);
+
+ // Assumes the Widget is always focused.
+ input_method_->OnFocus();
+
+ textfield_->RequestFocus();
}
views::Menu2* GetContextMenu() {
@@ -108,25 +194,23 @@ class NativeTextfieldViewsTest : public ViewsTestBase,
}
protected:
- bool SendKeyEventToTextfieldViews(ui::KeyboardCode key_code,
- bool shift,
- bool control,
- bool capslock) {
+ void SendKeyEvent(ui::KeyboardCode key_code,
+ bool shift,
+ bool control,
+ bool capslock) {
int flags = (shift ? ui::EF_SHIFT_DOWN : 0) |
(control ? ui::EF_CONTROL_DOWN : 0) |
(capslock ? ui::EF_CAPS_LOCK_DOWN : 0);
KeyEvent event(ui::ET_KEY_PRESSED, key_code, flags);
- return textfield_->OnKeyPressed(event);
+ input_method_->DispatchKeyEvent(event);
}
- bool SendKeyEventToTextfieldViews(ui::KeyboardCode key_code,
- bool shift,
- bool control) {
- return SendKeyEventToTextfieldViews(key_code, shift, control, false);
+ void SendKeyEvent(ui::KeyboardCode key_code, bool shift, bool control) {
+ SendKeyEvent(key_code, shift, control, false);
}
- bool SendKeyEventToTextfieldViews(ui::KeyboardCode key_code) {
- return SendKeyEventToTextfieldViews(key_code, false, false);
+ void SendKeyEvent(ui::KeyboardCode key_code) {
+ SendKeyEvent(key_code, false, false);
}
View* GetFocusedView() {
@@ -136,62 +220,66 @@ class NativeTextfieldViewsTest : public ViewsTestBase,
// We need widget to populate wrapper class.
Widget* widget_;
- Textfield* textfield_;
+ TestTextfield* textfield_;
NativeTextfieldViews* textfield_view_;
TextfieldViewsModel* model_;
// The string from Controller::ContentsChanged callback.
string16 last_contents_;
+ // For testing input method related behaviors.
+ MockInputMethod* input_method_;
+
+ // Indicates how many times OnBeforeUserAction() is called.
+ int on_before_user_action_;
+
+ // Indicates how many times OnAfterUserAction() is called.
+ int on_after_user_action_;
+
private:
DISALLOW_COPY_AND_ASSIGN(NativeTextfieldViewsTest);
};
-TEST_F(NativeTextfieldViewsTest, ModelChangesTeset) {
+TEST_F(NativeTextfieldViewsTest, ModelChangesTest) {
InitTextfield(Textfield::STYLE_DEFAULT);
+
+ // TextfieldController::ContentsChanged() shouldn't be called when changing
+ // text programmatically.
+ last_contents_.clear();
textfield_->SetText(ASCIIToUTF16("this is"));
EXPECT_STR_EQ("this is", model_->text());
- EXPECT_STR_EQ("this is", last_contents_);
- last_contents_.clear();
+ EXPECT_STR_EQ("this is", textfield_->text());
+ EXPECT_TRUE(last_contents_.empty());
textfield_->AppendText(ASCIIToUTF16(" a test"));
EXPECT_STR_EQ("this is a test", model_->text());
- EXPECT_STR_EQ("this is a test", last_contents_);
- last_contents_.clear();
-
- // Cases where the callback should not be called.
- textfield_->SetText(ASCIIToUTF16("this is a test"));
- EXPECT_STR_EQ("this is a test", model_->text());
- EXPECT_EQ(string16(), last_contents_);
-
- textfield_->AppendText(string16());
- EXPECT_STR_EQ("this is a test", model_->text());
- EXPECT_EQ(string16(), last_contents_);
+ EXPECT_STR_EQ("this is a test", textfield_->text());
+ EXPECT_TRUE(last_contents_.empty());
EXPECT_EQ(string16(), textfield_->GetSelectedText());
textfield_->SelectAll();
EXPECT_STR_EQ("this is a test", textfield_->GetSelectedText());
- EXPECT_EQ(string16(), last_contents_);
+ EXPECT_TRUE(last_contents_.empty());
}
TEST_F(NativeTextfieldViewsTest, KeyTest) {
InitTextfield(Textfield::STYLE_DEFAULT);
- SendKeyEventToTextfieldViews(ui::VKEY_C, true, false);
+ SendKeyEvent(ui::VKEY_C, true, false);
EXPECT_STR_EQ("C", textfield_->text());
EXPECT_STR_EQ("C", last_contents_);
last_contents_.clear();
- SendKeyEventToTextfieldViews(ui::VKEY_R, false, false);
+ SendKeyEvent(ui::VKEY_R, false, false);
EXPECT_STR_EQ("Cr", textfield_->text());
EXPECT_STR_EQ("Cr", last_contents_);
textfield_->SetText(ASCIIToUTF16(""));
- SendKeyEventToTextfieldViews(ui::VKEY_C, true, false, true);
- SendKeyEventToTextfieldViews(ui::VKEY_C, false, false, true);
- SendKeyEventToTextfieldViews(ui::VKEY_1, false, false, true);
- SendKeyEventToTextfieldViews(ui::VKEY_1, true, false, true);
- SendKeyEventToTextfieldViews(ui::VKEY_1, true, false, false);
+ SendKeyEvent(ui::VKEY_C, true, false, true);
+ SendKeyEvent(ui::VKEY_C, false, false, true);
+ SendKeyEvent(ui::VKEY_1, false, false, true);
+ SendKeyEvent(ui::VKEY_1, true, false, true);
+ SendKeyEvent(ui::VKEY_1, true, false, false);
EXPECT_STR_EQ("cC1!!", textfield_->text());
EXPECT_STR_EQ("cC1!!", last_contents_);
}
@@ -200,34 +288,34 @@ TEST_F(NativeTextfieldViewsTest, ControlAndSelectTest) {
// Insert a test string in a textfield.
InitTextfield(Textfield::STYLE_DEFAULT);
textfield_->SetText(ASCIIToUTF16("one two three"));
- SendKeyEventToTextfieldViews(ui::VKEY_RIGHT,
+ SendKeyEvent(ui::VKEY_RIGHT,
true /* shift */, false /* control */);
- SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, true, false);
- SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, true, false);
+ SendKeyEvent(ui::VKEY_RIGHT, true, false);
+ SendKeyEvent(ui::VKEY_RIGHT, true, false);
EXPECT_STR_EQ("one", textfield_->GetSelectedText());
// Test word select.
- SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, true, true);
+ SendKeyEvent(ui::VKEY_RIGHT, true, true);
EXPECT_STR_EQ("one two", textfield_->GetSelectedText());
- SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, true, true);
+ SendKeyEvent(ui::VKEY_RIGHT, true, true);
EXPECT_STR_EQ("one two three", textfield_->GetSelectedText());
- SendKeyEventToTextfieldViews(ui::VKEY_LEFT, true, true);
+ SendKeyEvent(ui::VKEY_LEFT, true, true);
EXPECT_STR_EQ("one two ", textfield_->GetSelectedText());
- SendKeyEventToTextfieldViews(ui::VKEY_LEFT, true, true);
+ SendKeyEvent(ui::VKEY_LEFT, true, true);
EXPECT_STR_EQ("one ", textfield_->GetSelectedText());
// Replace the selected text.
- SendKeyEventToTextfieldViews(ui::VKEY_Z, true, false);
- SendKeyEventToTextfieldViews(ui::VKEY_E, true, false);
- SendKeyEventToTextfieldViews(ui::VKEY_R, true, false);
- SendKeyEventToTextfieldViews(ui::VKEY_O, true, false);
- SendKeyEventToTextfieldViews(ui::VKEY_SPACE, false, false);
+ SendKeyEvent(ui::VKEY_Z, true, false);
+ SendKeyEvent(ui::VKEY_E, true, false);
+ SendKeyEvent(ui::VKEY_R, true, false);
+ SendKeyEvent(ui::VKEY_O, true, false);
+ SendKeyEvent(ui::VKEY_SPACE, false, false);
EXPECT_STR_EQ("ZERO two three", textfield_->text());
- SendKeyEventToTextfieldViews(ui::VKEY_END, true, false);
+ SendKeyEvent(ui::VKEY_END, true, false);
EXPECT_STR_EQ("two three", textfield_->GetSelectedText());
- SendKeyEventToTextfieldViews(ui::VKEY_HOME, true, false);
+ SendKeyEvent(ui::VKEY_HOME, true, false);
EXPECT_STR_EQ("ZERO ", textfield_->GetSelectedText());
}
@@ -242,41 +330,41 @@ TEST_F(NativeTextfieldViewsTest, InsertionDeletionTest) {
ui::KeyboardCode code =
c == ' ' ? ui::VKEY_SPACE :
static_cast<ui::KeyboardCode>(ui::VKEY_A + c - 'a');
- SendKeyEventToTextfieldViews(code);
+ SendKeyEvent(code);
}
EXPECT_STR_EQ(test_str, textfield_->text());
// Move the cursor around.
for (int i = 0; i < 6; i++) {
- SendKeyEventToTextfieldViews(ui::VKEY_LEFT);
+ SendKeyEvent(ui::VKEY_LEFT);
}
- SendKeyEventToTextfieldViews(ui::VKEY_RIGHT);
+ SendKeyEvent(ui::VKEY_RIGHT);
// Delete using backspace and check resulting string.
- SendKeyEventToTextfieldViews(ui::VKEY_BACK);
+ SendKeyEvent(ui::VKEY_BACK);
EXPECT_STR_EQ("this is test", textfield_->text());
// Delete using delete key and check resulting string.
for (int i = 0; i < 5; i++) {
- SendKeyEventToTextfieldViews(ui::VKEY_DELETE);
+ SendKeyEvent(ui::VKEY_DELETE);
}
EXPECT_STR_EQ("this is ", textfield_->text());
// Select all and replace with "k".
textfield_->SelectAll();
- SendKeyEventToTextfieldViews(ui::VKEY_K);
+ SendKeyEvent(ui::VKEY_K);
EXPECT_STR_EQ("k", textfield_->text());
// Delete the previous word from cursor.
textfield_->SetText(ASCIIToUTF16("one two three four"));
- SendKeyEventToTextfieldViews(ui::VKEY_END);
- SendKeyEventToTextfieldViews(ui::VKEY_BACK, false, true, false);
+ SendKeyEvent(ui::VKEY_END);
+ SendKeyEvent(ui::VKEY_BACK, false, true, false);
EXPECT_STR_EQ("one two three ", textfield_->text());
// Delete upto the beginning of the buffer from cursor in chromeos, do nothing
// in windows.
- SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, true, false);
- SendKeyEventToTextfieldViews(ui::VKEY_BACK, true, true, false);
+ SendKeyEvent(ui::VKEY_LEFT, false, true, false);
+ SendKeyEvent(ui::VKEY_BACK, true, true, false);
#if defined(OS_WIN)
EXPECT_STR_EQ("one two three ", textfield_->text());
#else
@@ -285,14 +373,14 @@ TEST_F(NativeTextfieldViewsTest, InsertionDeletionTest) {
// Delete the next word from cursor.
textfield_->SetText(ASCIIToUTF16("one two three four"));
- SendKeyEventToTextfieldViews(ui::VKEY_HOME);
- SendKeyEventToTextfieldViews(ui::VKEY_DELETE, false, true, false);
+ SendKeyEvent(ui::VKEY_HOME);
+ SendKeyEvent(ui::VKEY_DELETE, false, true, false);
EXPECT_STR_EQ(" two three four", textfield_->text());
// Delete upto the end of the buffer from cursor in chromeos, do nothing
// in windows.
- SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, false, true, false);
- SendKeyEventToTextfieldViews(ui::VKEY_DELETE, true, true, false);
+ SendKeyEvent(ui::VKEY_RIGHT, false, true, false);
+ SendKeyEvent(ui::VKEY_DELETE, true, true, false);
#if defined(OS_WIN)
EXPECT_STR_EQ(" two three four", textfield_->text());
#else
@@ -302,20 +390,44 @@ TEST_F(NativeTextfieldViewsTest, InsertionDeletionTest) {
TEST_F(NativeTextfieldViewsTest, PasswordTest) {
InitTextfield(Textfield::STYLE_PASSWORD);
+
+ last_contents_.clear();
textfield_->SetText(ASCIIToUTF16("my password"));
// Just to make sure the text() and callback returns
// the actual text instead of "*".
EXPECT_STR_EQ("my password", textfield_->text());
- EXPECT_STR_EQ("my password", last_contents_);
+ EXPECT_TRUE(last_contents_.empty());
}
TEST_F(NativeTextfieldViewsTest, OnKeyPressReturnValueTest) {
InitTextfield(Textfield::STYLE_DEFAULT);
- EXPECT_TRUE(SendKeyEventToTextfieldViews(ui::VKEY_A));
+
+ // Character keys will be handled by input method.
+ SendKeyEvent(ui::VKEY_A);
+ EXPECT_TRUE(textfield_->key_received());
+ EXPECT_FALSE(textfield_->key_handled());
+ textfield_->clear();
+
+ // Home will be handled.
+ SendKeyEvent(ui::VKEY_HOME);
+ EXPECT_TRUE(textfield_->key_received());
+ EXPECT_TRUE(textfield_->key_handled());
+ textfield_->clear();
+
// F24, up/down key won't be handled.
- EXPECT_FALSE(SendKeyEventToTextfieldViews(ui::VKEY_F24));
- EXPECT_FALSE(SendKeyEventToTextfieldViews(ui::VKEY_UP));
- EXPECT_FALSE(SendKeyEventToTextfieldViews(ui::VKEY_DOWN));
+ SendKeyEvent(ui::VKEY_F24);
+ EXPECT_TRUE(textfield_->key_received());
+ EXPECT_FALSE(textfield_->key_handled());
+ textfield_->clear();
+
+ SendKeyEvent(ui::VKEY_UP);
+ EXPECT_TRUE(textfield_->key_received());
+ EXPECT_FALSE(textfield_->key_handled());
+ textfield_->clear();
+
+ SendKeyEvent(ui::VKEY_DOWN);
+ EXPECT_TRUE(textfield_->key_received());
+ EXPECT_FALSE(textfield_->key_handled());
}
TEST_F(NativeTextfieldViewsTest, CursorMovement) {
@@ -325,23 +437,23 @@ TEST_F(NativeTextfieldViewsTest, CursorMovement) {
textfield_->SetText(ASCIIToUTF16("one two hre "));
// Send the cursor at the end.
- SendKeyEventToTextfieldViews(ui::VKEY_END);
+ SendKeyEvent(ui::VKEY_END);
// Ctrl+Left should move the cursor just before the last word.
- SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, true);
- SendKeyEventToTextfieldViews(ui::VKEY_T);
+ SendKeyEvent(ui::VKEY_LEFT, false, true);
+ SendKeyEvent(ui::VKEY_T);
EXPECT_STR_EQ("one two thre ", textfield_->text());
EXPECT_STR_EQ("one two thre ", last_contents_);
// Ctrl+Right should move the cursor to the end of the last word.
- SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, false, true);
- SendKeyEventToTextfieldViews(ui::VKEY_E);
+ SendKeyEvent(ui::VKEY_RIGHT, false, true);
+ SendKeyEvent(ui::VKEY_E);
EXPECT_STR_EQ("one two three ", textfield_->text());
EXPECT_STR_EQ("one two three ", last_contents_);
// Ctrl+Right again should move the cursor to the end.
- SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, false, true);
- SendKeyEventToTextfieldViews(ui::VKEY_BACK);
+ SendKeyEvent(ui::VKEY_RIGHT, false, true);
+ SendKeyEvent(ui::VKEY_BACK);
EXPECT_STR_EQ("one two three", textfield_->text());
EXPECT_STR_EQ("one two three", last_contents_);
@@ -349,34 +461,26 @@ TEST_F(NativeTextfieldViewsTest, CursorMovement) {
textfield_->SetText(ASCIIToUTF16(" ne two"));
// Send the cursor at the beginning.
- SendKeyEventToTextfieldViews(ui::VKEY_HOME);
+ SendKeyEvent(ui::VKEY_HOME);
// Ctrl+Right, then Ctrl+Left should move the cursor to the beginning of the
// first word.
- SendKeyEventToTextfieldViews(ui::VKEY_RIGHT, false, true);
- SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, true);
- SendKeyEventToTextfieldViews(ui::VKEY_O);
+ SendKeyEvent(ui::VKEY_RIGHT, false, true);
+ SendKeyEvent(ui::VKEY_LEFT, false, true);
+ SendKeyEvent(ui::VKEY_O);
EXPECT_STR_EQ(" one two", textfield_->text());
EXPECT_STR_EQ(" one two", last_contents_);
// Ctrl+Left to move the cursor to the beginning of the first word.
- SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, true);
+ SendKeyEvent(ui::VKEY_LEFT, false, true);
// Ctrl+Left again should move the cursor back to the very beginning.
- SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, true);
- SendKeyEventToTextfieldViews(ui::VKEY_DELETE);
+ SendKeyEvent(ui::VKEY_LEFT, false, true);
+ SendKeyEvent(ui::VKEY_DELETE);
EXPECT_STR_EQ("one two", textfield_->text());
EXPECT_STR_EQ("one two", last_contents_);
}
-#if defined(OS_WIN)
-// TODO(oshima): Windows' FocusManager::ClearNativeFocus() resets the
-// focused view to NULL, which causes crash in this test. Figure out
-// why and fix this.
-#define MAYBE_FocusTraversalTest DISABLED_FocusTraversalTest
-#else
-#define MAYBE_FocusTraversalTest FocusTraversalTest
-#endif
-TEST_F(NativeTextfieldViewsTest, MAYBE_FocusTraversalTest) {
+TEST_F(NativeTextfieldViewsTest, FocusTraversalTest) {
InitTextfields(Textfield::STYLE_DEFAULT, 3);
textfield_->RequestFocus();
@@ -464,19 +568,19 @@ TEST_F(NativeTextfieldViewsTest, ReadOnlyTest) {
InitTextfield(Textfield::STYLE_DEFAULT);
textfield_->SetText(ASCIIToUTF16(" one two three "));
textfield_->SetReadOnly(true);
- SendKeyEventToTextfieldViews(ui::VKEY_HOME);
+ SendKeyEvent(ui::VKEY_HOME);
EXPECT_EQ(0U, textfield_->GetCursorPosition());
- SendKeyEventToTextfieldViews(ui::VKEY_END);
+ SendKeyEvent(ui::VKEY_END);
EXPECT_EQ(15U, textfield_->GetCursorPosition());
- SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, false);
+ SendKeyEvent(ui::VKEY_LEFT, false, false);
EXPECT_EQ(14U, textfield_->GetCursorPosition());
- SendKeyEventToTextfieldViews(ui::VKEY_LEFT, false, true);
+ SendKeyEvent(ui::VKEY_LEFT, false, true);
EXPECT_EQ(9U, textfield_->GetCursorPosition());
- SendKeyEventToTextfieldViews(ui::VKEY_LEFT, true, true);
+ SendKeyEvent(ui::VKEY_LEFT, true, true);
EXPECT_EQ(5U, textfield_->GetCursorPosition());
EXPECT_STR_EQ("two ", textfield_->GetSelectedText());
@@ -484,14 +588,14 @@ TEST_F(NativeTextfieldViewsTest, ReadOnlyTest) {
EXPECT_STR_EQ(" one two three ", textfield_->GetSelectedText());
// CUT&PASTE does not work, but COPY works
- SendKeyEventToTextfieldViews(ui::VKEY_X, false, true);
+ SendKeyEvent(ui::VKEY_X, false, true);
EXPECT_STR_EQ(" one two three ", textfield_->GetSelectedText());
string16 str;
views::ViewsDelegate::views_delegate->GetClipboard()->
ReadText(ui::Clipboard::BUFFER_STANDARD, &str);
EXPECT_STR_NE(" one two three ", str);
- SendKeyEventToTextfieldViews(ui::VKEY_C, false, true);
+ SendKeyEvent(ui::VKEY_C, false, true);
views::ViewsDelegate::views_delegate->GetClipboard()->
ReadText(ui::Clipboard::BUFFER_STANDARD, &str);
EXPECT_STR_EQ(" one two three ", str);
@@ -501,7 +605,7 @@ TEST_F(NativeTextfieldViewsTest, ReadOnlyTest) {
EXPECT_STR_EQ(" four five six ", textfield_->text());
// Paste shouldn't work.
- SendKeyEventToTextfieldViews(ui::VKEY_V, false, true);
+ SendKeyEvent(ui::VKEY_V, false, true);
EXPECT_STR_EQ(" four five six ", textfield_->text());
EXPECT_TRUE(textfield_->GetSelectedText().empty());
@@ -509,12 +613,103 @@ TEST_F(NativeTextfieldViewsTest, ReadOnlyTest) {
EXPECT_STR_EQ(" four five six ", textfield_->GetSelectedText());
// Text field is unmodifiable and selection shouldn't change.
- SendKeyEventToTextfieldViews(ui::VKEY_DELETE);
+ SendKeyEvent(ui::VKEY_DELETE);
EXPECT_STR_EQ(" four five six ", textfield_->GetSelectedText());
- SendKeyEventToTextfieldViews(ui::VKEY_BACK);
+ SendKeyEvent(ui::VKEY_BACK);
EXPECT_STR_EQ(" four five six ", textfield_->GetSelectedText());
- SendKeyEventToTextfieldViews(ui::VKEY_T);
+ SendKeyEvent(ui::VKEY_T);
EXPECT_STR_EQ(" four five six ", textfield_->GetSelectedText());
}
+TEST_F(NativeTextfieldViewsTest, TextInputClientTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ TextInputClient* client = textfield_->GetTextInputClient();
+ EXPECT_TRUE(client);
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, client->GetTextInputType());
+
+ textfield_->SetText(ASCIIToUTF16("0123456789"));
+ ui::Range range;
+ EXPECT_TRUE(client->GetTextRange(&range));
+ EXPECT_EQ(0U, range.start());
+ EXPECT_EQ(10U, range.end());
+
+ EXPECT_TRUE(client->SetSelectionRange(ui::Range(1, 4)));
+ EXPECT_TRUE(client->GetSelectionRange(&range));
+ EXPECT_EQ(ui::Range(1,4), range);
+
+ // This code can't be compiled because of a bug in base::Callback.
+#if 0
+ GetTextHelper helper;
+ base::Callback<void(string16)> callback =
+ base::Bind(&GetTextHelper::set_text, base::Unretained(&helper));
+
+ EXPECT_TRUE(client->GetTextFromRange(range, callback));
+ EXPECT_STR_EQ("123", helper.text());
+#endif
+
+ EXPECT_TRUE(client->DeleteRange(range));
+ EXPECT_STR_EQ("0456789", textfield_->text());
+
+ ui::CompositionText composition;
+ composition.text = UTF8ToUTF16("321");
+ // Set composition through input method.
+ input_method_->Clear();
+ input_method_->SetCompositionTextForNextKey(composition);
+ textfield_->clear();
+
+ on_before_user_action_ = on_after_user_action_ = 0;
+ SendKeyEvent(ui::VKEY_A);
+ EXPECT_TRUE(textfield_->key_received());
+ EXPECT_FALSE(textfield_->key_handled());
+ EXPECT_TRUE(client->HasCompositionText());
+ EXPECT_TRUE(client->GetCompositionTextRange(&range));
+ EXPECT_STR_EQ("0321456789", textfield_->text());
+ EXPECT_EQ(ui::Range(1,4), range);
+ EXPECT_EQ(2, on_before_user_action_);
+ EXPECT_EQ(2, on_after_user_action_);
+
+ input_method_->SetResultTextForNextKey(UTF8ToUTF16("123"));
+ on_before_user_action_ = on_after_user_action_ = 0;
+ textfield_->clear();
+ SendKeyEvent(ui::VKEY_A);
+ EXPECT_TRUE(textfield_->key_received());
+ EXPECT_FALSE(textfield_->key_handled());
+ EXPECT_FALSE(client->HasCompositionText());
+ EXPECT_FALSE(input_method_->cancel_composition_called());
+ EXPECT_STR_EQ("0123456789", textfield_->text());
+ EXPECT_EQ(2, on_before_user_action_);
+ EXPECT_EQ(2, on_after_user_action_);
+
+ input_method_->Clear();
+ input_method_->SetCompositionTextForNextKey(composition);
+ textfield_->clear();
+ SendKeyEvent(ui::VKEY_A);
+ EXPECT_TRUE(client->HasCompositionText());
+ EXPECT_STR_EQ("0123321456789", textfield_->text());
+
+ on_before_user_action_ = on_after_user_action_ = 0;
+ textfield_->clear();
+ SendKeyEvent(ui::VKEY_RIGHT);
+ EXPECT_FALSE(client->HasCompositionText());
+ EXPECT_TRUE(input_method_->cancel_composition_called());
+ EXPECT_TRUE(textfield_->key_received());
+ EXPECT_TRUE(textfield_->key_handled());
+ EXPECT_STR_EQ("0123321456789", textfield_->text());
+ EXPECT_EQ(8U, textfield_->GetCursorPosition());
+ EXPECT_EQ(1, on_before_user_action_);
+ EXPECT_EQ(1, on_after_user_action_);
+
+ input_method_->Clear();
+ textfield_->SetReadOnly(true);
+ EXPECT_TRUE(input_method_->text_input_type_changed());
+ EXPECT_FALSE(textfield_->GetTextInputClient());
+
+ textfield_->SetReadOnly(false);
+ input_method_->Clear();
+ textfield_->SetPassword(true);
+ EXPECT_TRUE(input_method_->text_input_type_changed());
+ EXPECT_TRUE(textfield_->GetTextInputClient());
+}
+
+
} // namespace views
diff --git a/views/controls/textfield/native_textfield_win.cc b/views/controls/textfield/native_textfield_win.cc
index a147987..a7e7443 100644
--- a/views/controls/textfield/native_textfield_win.cc
+++ b/views/controls/textfield/native_textfield_win.cc
@@ -366,6 +366,10 @@ void NativeTextfieldWin::HandleFocus() {
void NativeTextfieldWin::HandleBlur() {
}
+TextInputClient* NativeTextfieldWin::GetTextInputClient() {
+ return NULL;
+}
+
////////////////////////////////////////////////////////////////////////////////
// NativeTextfieldWin, ui::SimpleMenuModel::Delegate implementation:
@@ -602,8 +606,6 @@ LRESULT NativeTextfieldWin::OnImeEndComposition(UINT message,
// this case, we need to update the find results when a composition is
// finished or canceled.
textfield_->SyncText();
- if (textfield_->GetController())
- textfield_->GetController()->ContentsChanged(textfield_, GetText());
return DefWindowProc(message, wparam, lparam);
}
@@ -1016,8 +1018,6 @@ void NativeTextfieldWin::OnAfterPossibleChange(bool should_redraw_text) {
}
textfield_->SyncText();
UpdateAccessibleValue(textfield_->text());
- if (textfield_->GetController())
- textfield_->GetController()->ContentsChanged(textfield_, new_text);
if (should_redraw_text) {
CHARRANGE original_sel;
diff --git a/views/controls/textfield/native_textfield_win.h b/views/controls/textfield/native_textfield_win.h
index 9ec30d2..69a7103 100644
--- a/views/controls/textfield/native_textfield_win.h
+++ b/views/controls/textfield/native_textfield_win.h
@@ -85,6 +85,7 @@ class NativeTextfieldWin
virtual bool HandleKeyReleased(const views::KeyEvent& event) OVERRIDE;
virtual void HandleFocus() OVERRIDE;
virtual void HandleBlur() OVERRIDE;
+ virtual TextInputClient* GetTextInputClient() OVERRIDE;
// Overridden from ui::SimpleMenuModel::Delegate:
virtual bool IsCommandIdChecked(int command_id) const OVERRIDE;
diff --git a/views/controls/textfield/native_textfield_wrapper.h b/views/controls/textfield/native_textfield_wrapper.h
index 2ad5b6c..b3a6edb 100644
--- a/views/controls/textfield/native_textfield_wrapper.h
+++ b/views/controls/textfield/native_textfield_wrapper.h
@@ -21,6 +21,7 @@ namespace views {
class KeyEvent;
class Textfield;
+class TextInputClient;
class View;
// An interface implemented by an object that provides a platform-native
@@ -120,6 +121,10 @@ class NativeTextfieldWrapper {
virtual void HandleFocus() = 0;
virtual void HandleBlur() = 0;
+ // Returns the View's TextInputClient instance or NULL if the View doesn't
+ // support text input.
+ virtual TextInputClient* GetTextInputClient() = 0;
+
// Creates an appropriate NativeTextfieldWrapper for the platform.
static NativeTextfieldWrapper* CreateWrapper(Textfield* field);
};
diff --git a/views/controls/textfield/textfield.cc b/views/controls/textfield/textfield.cc
index 7dfa4da..1d6b078 100644
--- a/views/controls/textfield/textfield.cc
+++ b/views/controls/textfield/textfield.cc
@@ -244,8 +244,14 @@ void Textfield::UpdateAllProperties() {
}
void Textfield::SyncText() {
- if (native_wrapper_)
- text_ = native_wrapper_->GetText();
+ if (native_wrapper_) {
+ string16 new_text = native_wrapper_->GetText();
+ if (new_text != text_) {
+ text_ = new_text;
+ if (controller_)
+ controller_->ContentsChanged(this, text_);
+ }
+ }
}
bool Textfield::IsIMEComposing() const {
@@ -367,6 +373,10 @@ void Textfield::GetAccessibleState(ui::AccessibleViewState* state) {
state->selection_end = range.end();
}
+TextInputClient* Textfield::GetTextInputClient() {
+ return native_wrapper_ ? native_wrapper_->GetTextInputClient() : NULL;
+}
+
void Textfield::SetEnabled(bool enabled) {
View::SetEnabled(enabled);
if (native_wrapper_)
diff --git a/views/controls/textfield/textfield.h b/views/controls/textfield/textfield.h
index bbea47d..9ef8711 100644
--- a/views/controls/textfield/textfield.h
+++ b/views/controls/textfield/textfield.h
@@ -215,6 +215,7 @@ class Textfield : public View {
virtual void OnFocus() OVERRIDE;
virtual void OnBlur() OVERRIDE;
virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+ virtual TextInputClient* GetTextInputClient() OVERRIDE;
protected:
virtual void ViewHierarchyChanged(bool is_add, View* parent,
diff --git a/views/controls/textfield/textfield_controller.h b/views/controls/textfield/textfield_controller.h
index ee8df1b..a50f2e9 100644
--- a/views/controls/textfield/textfield_controller.h
+++ b/views/controls/textfield/textfield_controller.h
@@ -17,7 +17,9 @@ class Textfield;
// in the state of a text field.
class TextfieldController {
public:
- // This method is called whenever the text in the field changes.
+ // This method is called whenever the text in the field is changed by the
+ // user. It won't be called if the text is changed by calling
+ // Textfield::SetText() or Textfield::AppendText().
virtual void ContentsChanged(Textfield* sender,
const string16& new_contents) = 0;
diff --git a/views/controls/textfield/textfield_views_model.cc b/views/controls/textfield/textfield_views_model.cc
index ef12404..6e3ebf7 100644
--- a/views/controls/textfield/textfield_views_model.cc
+++ b/views/controls/textfield/textfield_views_model.cc
@@ -18,9 +18,15 @@
namespace views {
-TextfieldViewsModel::TextfieldViewsModel()
- : cursor_pos_(0),
- selection_begin_(0),
+TextfieldViewsModel::Delegate::~Delegate() {
+}
+
+TextfieldViewsModel::TextfieldViewsModel(Delegate* delegate)
+ : delegate_(delegate),
+ cursor_pos_(0),
+ selection_start_(0),
+ composition_start_(0),
+ composition_end_(0),
is_password_(false) {
}
@@ -30,57 +36,159 @@ TextfieldViewsModel::~TextfieldViewsModel() {
void TextfieldViewsModel::GetFragments(TextFragments* fragments) const {
DCHECK(fragments);
fragments->clear();
- if (HasSelection()) {
- int begin = std::min(selection_begin_, cursor_pos_);
- int end = std::max(selection_begin_, cursor_pos_);
- if (begin != 0) {
- fragments->push_back(TextFragment(0, begin, false));
+ if (HasCompositionText()) {
+ if (composition_start_)
+ fragments->push_back(TextFragment(0, composition_start_, false, false));
+
+ size_t selection_start = std::min(selection_start_, cursor_pos_);
+ size_t selection_end = std::max(selection_start_, cursor_pos_);
+ size_t last_end = composition_start_;
+ for (ui::CompositionUnderlines::const_iterator i =
+ composition_underlines_.begin();
+ i != composition_underlines_.end(); ++i) {
+ size_t fragment_start =
+ std::min(i->start_offset, i->end_offset) + composition_start_;
+ size_t fragment_end =
+ std::max(i->start_offset, i->end_offset) + composition_start_;
+
+ fragment_start = std::max(last_end, fragment_start);
+ fragment_end = std::min(fragment_end, composition_end_);
+
+ if (fragment_start >= fragment_end)
+ break;
+
+ // If there is no selection, then just add a text fragment with underline.
+ if (selection_start == selection_end) {
+ if (last_end < fragment_start) {
+ fragments->push_back(
+ TextFragment(last_end, fragment_start, false, false));
+ }
+ fragments->push_back(
+ TextFragment(fragment_start, fragment_end, false, true));
+ last_end = fragment_end;
+ continue;
+ }
+
+ size_t end = std::min(fragment_start, selection_start);
+ if (last_end < end)
+ fragments->push_back(TextFragment(last_end, end, false, false));
+
+ last_end = fragment_end;
+
+ if (selection_start < fragment_start) {
+ end = std::min(selection_end, fragment_start);
+ fragments->push_back(TextFragment(selection_start, end, true, false));
+ selection_start = end;
+ } else if (selection_start > fragment_start) {
+ end = std::min(selection_start, fragment_end);
+ fragments->push_back(TextFragment(fragment_start, end, false, true));
+ fragment_start = end;
+ if (fragment_start == fragment_end)
+ continue;
+ }
+
+ if (fragment_start < selection_end) {
+ DCHECK_EQ(selection_start, fragment_start);
+ end = std::min(fragment_end, selection_end);
+ fragments->push_back(TextFragment(fragment_start, end, true, true));
+ fragment_start = end;
+ selection_start = end;
+ if (fragment_start == fragment_end)
+ continue;
+ }
+
+ DCHECK_LT(fragment_start, fragment_end);
+ fragments->push_back(
+ TextFragment(fragment_start, fragment_end, false, true));
}
- fragments->push_back(TextFragment(begin, end, true));
- int len = text_.length();
- if (end != len) {
- fragments->push_back(TextFragment(end, len, false));
+
+ if (last_end < composition_end_) {
+ if (selection_start < selection_end) {
+ DCHECK_LE(last_end, selection_start);
+ if (last_end < selection_start) {
+ fragments->push_back(
+ TextFragment(last_end, selection_start, false, false));
+ }
+ fragments->push_back(
+ TextFragment(selection_start, selection_end, true, false));
+ if (selection_end < composition_end_) {
+ fragments->push_back(
+ TextFragment(selection_end, composition_end_, false, false));
+ }
+ } else {
+ fragments->push_back(
+ TextFragment(last_end, composition_end_, false, false));
+ }
}
+
+ size_t len = text_.length();
+ if (composition_end_ < len)
+ fragments->push_back(TextFragment(composition_end_, len, false, false));
+ } else if (HasSelection()) {
+ size_t start = std::min(selection_start_, cursor_pos_);
+ size_t end = std::max(selection_start_, cursor_pos_);
+ if (start)
+ fragments->push_back(TextFragment(0, start, false, false));
+ fragments->push_back(TextFragment(start, end, true, false));
+ size_t len = text_.length();
+ if (end != len)
+ fragments->push_back(TextFragment(end, len, false, false));
} else {
- fragments->push_back(TextFragment(0, text_.length(), false));
+ fragments->push_back(TextFragment(0, text_.length(), false, false));
}
}
bool TextfieldViewsModel::SetText(const string16& text) {
- bool changed = text_ != text;
- if (changed) {
+ bool changed = false;
+ if (HasCompositionText()) {
+ ConfirmCompositionText();
+ changed = true;
+ }
+ if (text_ != text) {
text_ = text;
if (cursor_pos_ > text.length()) {
cursor_pos_ = text.length();
}
+ changed = true;
}
ClearSelection();
return changed;
}
-void TextfieldViewsModel::Insert(char16 c) {
- if (HasSelection())
+void TextfieldViewsModel::InsertText(const string16& text) {
+ if (HasCompositionText())
+ ClearCompositionText();
+ else if (HasSelection())
DeleteSelection();
- text_.insert(cursor_pos_, 1, c);
- cursor_pos_++;
+ text_.insert(cursor_pos_, text);
+ cursor_pos_ += text.size();
ClearSelection();
}
-void TextfieldViewsModel::Replace(char16 c) {
- if (!HasSelection())
- Delete();
- Insert(c);
+void TextfieldViewsModel::ReplaceText(const string16& text) {
+ if (HasCompositionText())
+ ClearCompositionText();
+ else if (!HasSelection())
+ SelectRange(ui::Range(cursor_pos_, cursor_pos_ + text.length()));
+ InsertText(text);
}
void TextfieldViewsModel::Append(const string16& text) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
text_ += text;
}
bool TextfieldViewsModel::Delete() {
+ if (HasCompositionText()) {
+ ClearCompositionText();
+ return true;
+ }
if (HasSelection()) {
DeleteSelection();
return true;
- } else if (text_.length() > cursor_pos_) {
+ }
+ if (text_.length() > cursor_pos_) {
text_.erase(cursor_pos_, 1);
return true;
}
@@ -88,10 +196,15 @@ bool TextfieldViewsModel::Delete() {
}
bool TextfieldViewsModel::Backspace() {
+ if (HasCompositionText()) {
+ ClearCompositionText();
+ return true;
+ }
if (HasSelection()) {
DeleteSelection();
return true;
- } else if (cursor_pos_ > 0) {
+ }
+ if (cursor_pos_ > 0) {
cursor_pos_--;
text_.erase(cursor_pos_, 1);
ClearSelection();
@@ -101,13 +214,15 @@ bool TextfieldViewsModel::Backspace() {
}
void TextfieldViewsModel::MoveCursorLeft(bool select) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
// TODO(oshima): support BIDI
if (select) {
if (cursor_pos_ > 0)
cursor_pos_--;
} else {
if (HasSelection())
- cursor_pos_ = std::min(cursor_pos_, selection_begin_);
+ cursor_pos_ = std::min(cursor_pos_, selection_start_);
else if (cursor_pos_ > 0)
cursor_pos_--;
ClearSelection();
@@ -115,12 +230,14 @@ void TextfieldViewsModel::MoveCursorLeft(bool select) {
}
void TextfieldViewsModel::MoveCursorRight(bool select) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
// TODO(oshima): support BIDI
if (select) {
cursor_pos_ = std::min(text_.length(), cursor_pos_ + 1);
} else {
if (HasSelection())
- cursor_pos_ = std::max(cursor_pos_, selection_begin_);
+ cursor_pos_ = std::max(cursor_pos_, selection_start_);
else
cursor_pos_ = std::min(text_.length(), cursor_pos_ + 1);
ClearSelection();
@@ -128,6 +245,8 @@ void TextfieldViewsModel::MoveCursorRight(bool select) {
}
void TextfieldViewsModel::MoveCursorToPreviousWord(bool select) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
// Notes: We always iterate words from the begining.
// This is probably fast enough for our usage, but we may
// want to modify WordIterator so that it can start from the
@@ -162,6 +281,8 @@ void TextfieldViewsModel::MoveCursorToPreviousWord(bool select) {
}
void TextfieldViewsModel::MoveCursorToNextWord(bool select) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
base::BreakIterator iter(&text_, base::BreakIterator::BREAK_WORD);
bool success = iter.Init();
DCHECK(success);
@@ -179,19 +300,25 @@ void TextfieldViewsModel::MoveCursorToNextWord(bool select) {
ClearSelection();
}
-void TextfieldViewsModel::MoveCursorToStart(bool select) {
+void TextfieldViewsModel::MoveCursorToHome(bool select) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
cursor_pos_ = 0;
if (!select)
ClearSelection();
}
void TextfieldViewsModel::MoveCursorToEnd(bool select) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
cursor_pos_ = text_.length();
if (!select)
ClearSelection();
}
bool TextfieldViewsModel::MoveCursorTo(size_t pos, bool select) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
bool cursor_changed = false;
if (cursor_pos_ != pos) {
cursor_pos_ = pos;
@@ -217,48 +344,54 @@ gfx::Rect TextfieldViewsModel::GetCursorBounds(const gfx::Font& font) const {
string16 TextfieldViewsModel::GetSelectedText() const {
return text_.substr(
- std::min(cursor_pos_, selection_begin_),
- std::abs(static_cast<long>(cursor_pos_ - selection_begin_)));
+ std::min(cursor_pos_, selection_start_),
+ std::abs(static_cast<long>(cursor_pos_ - selection_start_)));
}
void TextfieldViewsModel::GetSelectedRange(ui::Range* range) const {
- *range = ui::Range(selection_begin_, cursor_pos_);
+ *range = ui::Range(selection_start_, cursor_pos_);
}
void TextfieldViewsModel::SelectRange(const ui::Range& range) {
- selection_begin_ = GetSafePosition(range.start());
+ if (HasCompositionText())
+ ConfirmCompositionText();
+ selection_start_ = GetSafePosition(range.start());
cursor_pos_ = GetSafePosition(range.end());
}
void TextfieldViewsModel::SelectAll() {
+ if (HasCompositionText())
+ ConfirmCompositionText();
// SelectAll selects towards the end.
cursor_pos_ = text_.length();
- selection_begin_ = 0;
+ selection_start_ = 0;
}
void TextfieldViewsModel::SelectWord() {
- // First we setup selection_begin_ and cursor_pos_. There are so many cases
+ if (HasCompositionText())
+ ConfirmCompositionText();
+ // First we setup selection_start_ and cursor_pos_. There are so many cases
// because we try to emulate what select-word looks like in a gtk textfield.
// See associated testcase for different cases.
if (cursor_pos_ > 0 && cursor_pos_ < text_.length()) {
if (isalnum(text_[cursor_pos_])) {
- selection_begin_ = cursor_pos_;
+ selection_start_ = cursor_pos_;
cursor_pos_++;
} else
- selection_begin_ = cursor_pos_ - 1;
+ selection_start_ = cursor_pos_ - 1;
} else if (cursor_pos_ == 0) {
- selection_begin_ = cursor_pos_;
+ selection_start_ = cursor_pos_;
if (text_.length() > 0)
cursor_pos_++;
} else {
- selection_begin_ = cursor_pos_ - 1;
+ selection_start_ = cursor_pos_ - 1;
}
- // Now we move selection_begin_ to beginning of selection. Selection boundary
+ // Now we move selection_start_ to beginning of selection. Selection boundary
// is defined as the position where we have alpha-num character on one side
// and non-alpha-num char on the other side.
- for (; selection_begin_ > 0; selection_begin_--) {
- if (IsPositionAtWordSelectionBoundary(selection_begin_))
+ for (; selection_start_ > 0; selection_start_--) {
+ if (IsPositionAtWordSelectionBoundary(selection_start_))
break;
}
@@ -272,11 +405,13 @@ void TextfieldViewsModel::SelectWord() {
}
void TextfieldViewsModel::ClearSelection() {
- selection_begin_ = cursor_pos_;
+ if (HasCompositionText())
+ ConfirmCompositionText();
+ selection_start_ = cursor_pos_;
}
bool TextfieldViewsModel::Cut() {
- if (HasSelection()) {
+ if (!HasCompositionText() && HasSelection()) {
ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate
->GetClipboard()).WriteText(GetSelectedText());
DeleteSelection();
@@ -286,7 +421,7 @@ bool TextfieldViewsModel::Cut() {
}
void TextfieldViewsModel::Copy() {
- if (HasSelection()) {
+ if (!HasCompositionText() && HasSelection()) {
ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate
->GetClipboard()).WriteText(GetSelectedText());
}
@@ -297,7 +432,9 @@ bool TextfieldViewsModel::Paste() {
views::ViewsDelegate::views_delegate->GetClipboard()
->ReadText(ui::Clipboard::BUFFER_STANDARD, &result);
if (!result.empty()) {
- if (HasSelection())
+ if (HasCompositionText())
+ ConfirmCompositionText();
+ else if (HasSelection())
DeleteSelection();
text_.insert(cursor_pos_, result);
cursor_pos_ += result.length();
@@ -308,23 +445,94 @@ bool TextfieldViewsModel::Paste() {
}
bool TextfieldViewsModel::HasSelection() const {
- return selection_begin_ != cursor_pos_;
+ return selection_start_ != cursor_pos_;
}
void TextfieldViewsModel::DeleteSelection() {
+ DCHECK(!HasCompositionText());
DCHECK(HasSelection());
- size_t n = std::abs(static_cast<long>(cursor_pos_ - selection_begin_));
- size_t begin = std::min(cursor_pos_, selection_begin_);
+ size_t n = std::abs(static_cast<long>(cursor_pos_ - selection_start_));
+ size_t begin = std::min(cursor_pos_, selection_start_);
text_.erase(begin, n);
cursor_pos_ = begin;
ClearSelection();
}
+string16 TextfieldViewsModel::GetTextFromRange(const ui::Range& range) const {
+ if (range.IsValid() && range.GetMin() < text_.length())
+ return text_.substr(range.GetMin(), range.length());
+ return string16();
+}
+
+void TextfieldViewsModel::GetTextRange(ui::Range* range) const {
+ *range = ui::Range(0, text_.length());
+}
+
+void TextfieldViewsModel::SetCompositionText(
+ const ui::CompositionText& composition) {
+ if (HasCompositionText())
+ ClearCompositionText();
+ else if (HasSelection())
+ DeleteSelection();
+
+ if (composition.text.empty())
+ return;
+
+ size_t length = composition.text.length();
+ text_.insert(cursor_pos_, composition.text);
+ composition_start_ = cursor_pos_;
+ composition_end_ = composition_start_ + length;
+ composition_underlines_ = composition.underlines;
+
+ if (composition.selection.IsValid()) {
+ selection_start_ =
+ std::min(composition_start_ + composition.selection.start(),
+ composition_end_);
+ cursor_pos_ =
+ std::min(composition_start_ + composition.selection.end(),
+ composition_end_);
+ } else {
+ cursor_pos_ = composition_end_;
+ ClearSelection();
+ }
+}
+
+void TextfieldViewsModel::ConfirmCompositionText() {
+ DCHECK(HasCompositionText());
+ cursor_pos_ = composition_end_;
+ composition_start_ = composition_end_ = string16::npos;
+ composition_underlines_.clear();
+ ClearSelection();
+ if (delegate_)
+ delegate_->OnCompositionTextConfirmedOrCleared();
+}
+
+void TextfieldViewsModel::ClearCompositionText() {
+ DCHECK(HasCompositionText());
+ text_.erase(composition_start_, composition_end_ - composition_start_);
+ cursor_pos_ = composition_start_;
+ composition_start_ = composition_end_ = string16::npos;
+ composition_underlines_.clear();
+ ClearSelection();
+ if (delegate_)
+ delegate_->OnCompositionTextConfirmedOrCleared();
+}
+
+void TextfieldViewsModel::GetCompositionTextRange(ui::Range* range) const {
+ if (HasCompositionText())
+ *range = ui::Range(composition_start_, composition_end_);
+ else
+ *range = ui::Range::InvalidRange();
+}
+
+bool TextfieldViewsModel::HasCompositionText() const {
+ return composition_start_ != composition_end_;
+}
+
string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const {
DCHECK(end >= begin);
- if (is_password_) {
+ if (is_password_)
return string16(end - begin, '*');
- }
return text_.substr(begin, end - begin);
}
diff --git a/views/controls/textfield/textfield_views_model.h b/views/controls/textfield/textfield_views_model.h
index c7e7e9a..260d493 100644
--- a/views/controls/textfield/textfield_views_model.h
+++ b/views/controls/textfield/textfield_views_model.h
@@ -10,6 +10,7 @@
#include "base/string16.h"
#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/ime/composition_text.h"
#include "ui/gfx/rect.h"
namespace gfx {
@@ -26,7 +27,19 @@ namespace views {
// It supports editing, selection and cursor manipulation.
class TextfieldViewsModel {
public:
- TextfieldViewsModel();
+
+ // Delegate interface implemented by the textfield view class to provided
+ // additional functionalities required by the model.
+ class Delegate {
+ public:
+ // Called when the current composition text is confirmed or cleared.
+ virtual void OnCompositionTextConfirmedOrCleared() = 0;
+
+ protected:
+ virtual ~Delegate();
+ };
+
+ explicit TextfieldViewsModel(Delegate* delegate);
virtual ~TextfieldViewsModel();
// Text fragment info. Used to draw selected text.
@@ -34,13 +47,16 @@ class TextfieldViewsModel {
// in the future to support multi-color text
// for omnibox.
struct TextFragment {
- TextFragment(size_t b, size_t e, bool s)
- : begin(b), end(e), selected(s) {
+ TextFragment(size_t s, size_t e, bool sel, bool u)
+ : start(s), end(e), selected(sel), underline(u) {
}
- // The begin and end position of text fragment.
- size_t begin, end;
+ // The start and end position of text fragment.
+ size_t start, end;
// True if the text is selected.
bool selected;
+ // True if the text has underline.
+ // TODO(suzhe): support underline color and thick style.
+ bool underline;
};
typedef std::vector<TextFragment> TextFragments;
@@ -55,25 +71,41 @@ class TextfieldViewsModel {
// Edit related methods.
// Sest the text. Returns true if the text has been modified.
+ // The current composition text will be confirmed first.
bool SetText(const string16& text);
+ // Inserts given |text| at the current cursor position.
+ // The current composition text will be cleared.
+ void InsertText(const string16& text);
+
// Inserts a character at the current cursor position.
- void Insert(char16 c);
+ void InsertChar(char16 c) {
+ InsertText(string16(&c, 1));
+ }
+
+ // Replaces characters at the current position with characters in given text.
+ // The current composition text will be cleared.
+ void ReplaceText(const string16& text);
// Replaces the char at the current position with given character.
- void Replace(char16 c);
+ void ReplaceChar(char16 c) {
+ ReplaceText(string16(&c, 1));
+ }
// Appends the text.
+ // The current composition text will be confirmed.
void Append(const string16& text);
// Deletes the first character after the current cursor position (as if, the
// the user has pressed delete key in the textfield). Returns true if
// the deletion is successful.
+ // If there is composition text, it'll be deleted instead.
bool Delete();
// Deletes the first character before the current cursor position (as if, the
// the user has pressed backspace key in the textfield). Returns true if
// the removal is successful.
+ // If there is composition text, it'll be deleted instead.
bool Backspace();
// Cursor related methods.
@@ -83,31 +115,38 @@ class TextfieldViewsModel {
// Moves the cursor left by one position (as if, the user has pressed the left
// arrow key). If |select| is true, it updates the selection accordingly.
+ // The current composition text will be confirmed.
void MoveCursorLeft(bool select);
// Moves the cursor right by one position (as if, the user has pressed the
// right arrow key). If |select| is true, it updates the selection
// accordingly.
+ // The current composition text will be confirmed.
void MoveCursorRight(bool select);
// Moves the cursor left by one word (word boundry is defined by space).
// If |select| is true, it updates the selection accordingly.
+ // The current composition text will be confirmed.
void MoveCursorToPreviousWord(bool select);
// Moves the cursor right by one word (word boundry is defined by space).
// If |select| is true, it updates the selection accordingly.
+ // The current composition text will be confirmed.
void MoveCursorToNextWord(bool select);
// Moves the cursor to start of the textfield contents.
// If |select| is true, it updates the selection accordingly.
- void MoveCursorToStart(bool select);
+ // The current composition text will be confirmed.
+ void MoveCursorToHome(bool select);
// Moves the cursor to end of the textfield contents.
// If |select| is true, it updates the selection accordingly.
+ // The current composition text will be confirmed.
void MoveCursorToEnd(bool select);
// Moves the cursor to the specified |position|.
// If |select| is true, it updates the selection accordingly.
+ // The current composition text will be confirmed.
bool MoveCursorTo(size_t position, bool select);
// Returns the bounds of character at the current cursor.
@@ -120,15 +159,19 @@ class TextfieldViewsModel {
void GetSelectedRange(ui::Range* range) const;
+ // The current composition text will be confirmed.
void SelectRange(const ui::Range& range);
// Selects all text.
+ // The current composition text will be confirmed.
void SelectAll();
// Selects the word at which the cursor is currently positioned.
+ // The current composition text will be confirmed.
void SelectWord();
// Clears selection.
+ // The current composition text will be confirmed.
void ClearSelection();
// Returns visible text. If the field is password, it returns the
@@ -148,15 +191,43 @@ class TextfieldViewsModel {
// if text has changed after pasting.
bool Paste();
- // Tells if any text is selected.
+ // Tells if any text is selected, even if the selection is in composition
+ // text.
bool HasSelection() const;
+ // Deletes the selected text. This method shouldn't be called with
+ // composition text.
+ void DeleteSelection();
+
+ // Retrieves the text content in a given range.
+ string16 GetTextFromRange(const ui::Range& range) const;
+
+ // Retrieves the range containing all text in the model.
+ void GetTextRange(ui::Range* range) const;
+
+ // Sets composition text and attributes. If there is composition text already,
+ // it’ll be replaced by the new one. Otherwise, current selection will be
+ // replaced. If there is no selection, the composition text will be inserted
+ // at the insertion point.
+ // Any changes to the model except text insertion will confirm the current
+ // composition text.
+ void SetCompositionText(const ui::CompositionText& composition);
+
+ // Converts current composition text into final content.
+ void ConfirmCompositionText();
+
+ // Removes current composition text.
+ void ClearCompositionText();
+
+ // Retrieves the range of current composition text.
+ void GetCompositionTextRange(ui::Range* range) const;
+
+ // Returns true if there is composition text.
+ bool HasCompositionText() const;
+
private:
friend class NativeTextfieldViews;
- // Deletes the selected text.
- void DeleteSelection();
-
// Returns the visible text given |start| and |end|.
string16 GetVisibleText(size_t start, size_t end) const;
@@ -167,6 +238,10 @@ class TextfieldViewsModel {
// text length.
size_t GetSafePosition(size_t position) const;
+ // Pointer to a TextfieldViewsModel::Delegate instance, should be provided by
+ // the View object.
+ Delegate* delegate_;
+
// The text in utf16 format.
string16 text_;
@@ -174,7 +249,14 @@ class TextfieldViewsModel {
size_t cursor_pos_;
// Selection range.
- size_t selection_begin_;
+ size_t selection_start_;
+
+ // Composition text range.
+ size_t composition_start_;
+ size_t composition_end_;
+
+ // Underline information of the composition text.
+ ui::CompositionUnderlines composition_underlines_;
// True if the text is the password.
bool is_password_;
diff --git a/views/controls/textfield/textfield_views_model_unittest.cc b/views/controls/textfield/textfield_views_model_unittest.cc
index 4339dac..c735332 100644
--- a/views/controls/textfield/textfield_views_model_unittest.cc
+++ b/views/controls/textfield/textfield_views_model_unittest.cc
@@ -20,9 +20,20 @@ namespace views {
#include "views/test/views_test_base.h"
-class TextfieldViewsModelTest : public ViewsTestBase {
+class TextfieldViewsModelTest : public ViewsTestBase,
+ public TextfieldViewsModel::Delegate {
public:
- TextfieldViewsModelTest() : ViewsTestBase() {}
+ TextfieldViewsModelTest()
+ : ViewsTestBase(),
+ composition_text_confirmed_or_cleared_(false) {
+ }
+
+ virtual void OnCompositionTextConfirmedOrCleared() {
+ composition_text_confirmed_or_cleared_ = true;
+ }
+
+ protected:
+ bool composition_text_confirmed_or_cleared_;
private:
DISALLOW_COPY_AND_ASSIGN(TextfieldViewsModelTest);
@@ -32,7 +43,7 @@ class TextfieldViewsModelTest : public ViewsTestBase {
EXPECT_EQ(ASCIIToWide(ascii), UTF16ToWide(utf16))
TEST_F(TextfieldViewsModelTest, EditString) {
- TextfieldViewsModel model;
+ TextfieldViewsModel model(NULL);
// append two strings
model.Append(ASCIIToUTF16("HILL"));
EXPECT_STR_EQ("HILL", model.text());
@@ -41,13 +52,13 @@ TEST_F(TextfieldViewsModelTest, EditString) {
// Insert "E" to make hello
model.MoveCursorRight(false);
- model.Insert('E');
+ model.InsertChar('E');
EXPECT_STR_EQ("HEILLWORLD", model.text());
// Replace "I" with "L"
- model.Replace('L');
+ model.ReplaceChar('L');
EXPECT_STR_EQ("HELLLWORLD", model.text());
- model.Replace('L');
- model.Replace('O');
+ model.ReplaceChar('L');
+ model.ReplaceChar('O');
EXPECT_STR_EQ("HELLOWORLD", model.text());
// Delete 6th char "W", then delete 5th char O"
@@ -59,7 +70,7 @@ TEST_F(TextfieldViewsModelTest, EditString) {
EXPECT_STR_EQ("HELLORLD", model.text());
// Move the cursor to start. backspace should fail.
- model.MoveCursorToStart(false);
+ model.MoveCursorToHome(false);
EXPECT_FALSE(model.Backspace());
EXPECT_STR_EQ("HELLORLD", model.text());
// Move the cursor to the end. delete should fail.
@@ -72,7 +83,7 @@ TEST_F(TextfieldViewsModelTest, EditString) {
}
TEST_F(TextfieldViewsModelTest, EmptyString) {
- TextfieldViewsModel model;
+ TextfieldViewsModel model(NULL);
EXPECT_EQ(string16(), model.text());
EXPECT_EQ(string16(), model.GetSelectedText());
EXPECT_EQ(string16(), model.GetVisibleText());
@@ -89,7 +100,7 @@ TEST_F(TextfieldViewsModelTest, EmptyString) {
}
TEST_F(TextfieldViewsModelTest, Selection) {
- TextfieldViewsModel model;
+ TextfieldViewsModel model(NULL);
model.Append(ASCIIToUTF16("HELLO"));
model.MoveCursorRight(false);
model.MoveCursorRight(true);
@@ -97,7 +108,7 @@ TEST_F(TextfieldViewsModelTest, Selection) {
model.MoveCursorRight(true);
EXPECT_STR_EQ("EL", model.GetSelectedText());
- model.MoveCursorToStart(true);
+ model.MoveCursorToHome(true);
EXPECT_STR_EQ("H", model.GetSelectedText());
model.MoveCursorToEnd(true);
EXPECT_STR_EQ("ELLO", model.GetSelectedText());
@@ -132,7 +143,7 @@ TEST_F(TextfieldViewsModelTest, Selection) {
}
TEST_F(TextfieldViewsModelTest, SelectionAndEdit) {
- TextfieldViewsModel model;
+ TextfieldViewsModel model(NULL);
model.Append(ASCIIToUTF16("HELLO"));
model.MoveCursorRight(false);
model.MoveCursorRight(true);
@@ -147,21 +158,21 @@ TEST_F(TextfieldViewsModelTest, SelectionAndEdit) {
EXPECT_STR_EQ("HILL", model.text());
EXPECT_EQ(1U, model.cursor_pos());
model.MoveCursorRight(true); // select "I"
- model.Insert('E');
+ model.InsertChar('E');
EXPECT_STR_EQ("HELL", model.text());
- model.MoveCursorToStart(false);
+ model.MoveCursorToHome(false);
model.MoveCursorRight(true); // select "H"
- model.Replace('B');
+ model.ReplaceChar('B');
EXPECT_STR_EQ("BELL", model.text());
model.MoveCursorToEnd(false);
model.MoveCursorLeft(true);
model.MoveCursorLeft(true); // select ">LL"
- model.Replace('E');
+ model.ReplaceChar('E');
EXPECT_STR_EQ("BEE", model.text());
}
TEST_F(TextfieldViewsModelTest, Password) {
- TextfieldViewsModel model;
+ TextfieldViewsModel model(NULL);
model.set_is_password(true);
model.Append(ASCIIToUTF16("HELLO"));
EXPECT_STR_EQ("*****", model.GetVisibleText());
@@ -176,13 +187,13 @@ TEST_F(TextfieldViewsModelTest, Password) {
EXPECT_STR_EQ("ELLO", model.GetSelectedText());
EXPECT_EQ(4U, model.cursor_pos());
- model.Insert('X');
+ model.InsertChar('X');
EXPECT_STR_EQ("*", model.GetVisibleText());
EXPECT_STR_EQ("X", model.text());
}
TEST_F(TextfieldViewsModelTest, Word) {
- TextfieldViewsModel model;
+ TextfieldViewsModel model(NULL);
model.Append(
ASCIIToUTF16("The answer to Life, the Universe, and Everything"));
model.MoveCursorToNextWord(false);
@@ -206,7 +217,7 @@ TEST_F(TextfieldViewsModelTest, Word) {
// Should be safe to go next word at the end.
model.MoveCursorToNextWord(true);
EXPECT_STR_EQ(", the Universe, and Everything", model.GetSelectedText());
- model.Insert('2');
+ model.InsertChar('2');
EXPECT_EQ(19U, model.cursor_pos());
// Now backwards.
@@ -223,18 +234,18 @@ TEST_F(TextfieldViewsModelTest, Word) {
// Should be safe to go pervious word at the begining.
model.MoveCursorToPreviousWord(true);
EXPECT_STR_EQ("The answer to Life", model.GetSelectedText());
- model.Replace('4');
+ model.ReplaceChar('4');
EXPECT_EQ(string16(), model.GetSelectedText());
EXPECT_STR_EQ("42", model.GetVisibleText());
}
TEST_F(TextfieldViewsModelTest, TextFragment) {
- TextfieldViewsModel model;
+ TextfieldViewsModel model(NULL);
TextfieldViewsModel::TextFragments fragments;
// Empty string
model.GetFragments(&fragments);
EXPECT_EQ(1U, fragments.size());
- EXPECT_EQ(0U, fragments[0].begin);
+ EXPECT_EQ(0U, fragments[0].start);
EXPECT_EQ(0U, fragments[0].end);
EXPECT_FALSE(fragments[0].selected);
@@ -242,7 +253,7 @@ TEST_F(TextfieldViewsModelTest, TextFragment) {
model.Append(ASCIIToUTF16("Hello world"));
model.GetFragments(&fragments);
EXPECT_EQ(1U, fragments.size());
- EXPECT_EQ(0U, fragments[0].begin);
+ EXPECT_EQ(0U, fragments[0].start);
EXPECT_EQ(11U, fragments[0].end);
EXPECT_FALSE(fragments[0].selected);
@@ -250,10 +261,10 @@ TEST_F(TextfieldViewsModelTest, TextFragment) {
model.MoveCursorToNextWord(true);
model.GetFragments(&fragments);
EXPECT_EQ(2U, fragments.size());
- EXPECT_EQ(0U, fragments[0].begin);
+ EXPECT_EQ(0U, fragments[0].start);
EXPECT_EQ(5U, fragments[0].end);
EXPECT_TRUE(fragments[0].selected);
- EXPECT_EQ(5U, fragments[1].begin);
+ EXPECT_EQ(5U, fragments[1].start);
EXPECT_EQ(11U, fragments[1].end);
EXPECT_FALSE(fragments[1].selected);
@@ -262,14 +273,14 @@ TEST_F(TextfieldViewsModelTest, TextFragment) {
model.MoveCursorRight(true);
model.GetFragments(&fragments);
EXPECT_EQ(3U, fragments.size());
- EXPECT_EQ(0U, fragments[0].begin);
+ EXPECT_EQ(0U, fragments[0].start);
EXPECT_EQ(5U, fragments[0].end);
EXPECT_FALSE(fragments[0].selected);
- EXPECT_EQ(5U, fragments[1].begin);
+ EXPECT_EQ(5U, fragments[1].start);
EXPECT_EQ(6U, fragments[1].end);
EXPECT_TRUE(fragments[1].selected);
- EXPECT_EQ(6U, fragments[2].begin);
+ EXPECT_EQ(6U, fragments[2].start);
EXPECT_EQ(11U, fragments[2].end);
EXPECT_FALSE(fragments[2].selected);
@@ -277,16 +288,16 @@ TEST_F(TextfieldViewsModelTest, TextFragment) {
model.MoveCursorToEnd(true);
model.GetFragments(&fragments);
EXPECT_EQ(2U, fragments.size());
- EXPECT_EQ(0U, fragments[0].begin);
+ EXPECT_EQ(0U, fragments[0].start);
EXPECT_EQ(5U, fragments[0].end);
EXPECT_FALSE(fragments[0].selected);
- EXPECT_EQ(5U, fragments[1].begin);
+ EXPECT_EQ(5U, fragments[1].start);
EXPECT_EQ(11U, fragments[1].end);
EXPECT_TRUE(fragments[1].selected);
}
TEST_F(TextfieldViewsModelTest, SetText) {
- TextfieldViewsModel model;
+ TextfieldViewsModel model(NULL);
model.Append(ASCIIToUTF16("HELLO"));
model.MoveCursorToEnd(false);
model.SetText(ASCIIToUTF16("GOODBYE"));
@@ -296,7 +307,7 @@ TEST_F(TextfieldViewsModelTest, SetText) {
EXPECT_STR_EQ("GOODBYE", model.GetSelectedText());
// Selection move the current pos to the end.
EXPECT_EQ(7U, model.cursor_pos());
- model.MoveCursorToStart(false);
+ model.MoveCursorToHome(false);
EXPECT_EQ(0U, model.cursor_pos());
model.MoveCursorToEnd(false);
@@ -316,7 +327,7 @@ TEST_F(TextfieldViewsModelTest, Clipboard) {
string16 initial_clipboard_text;
clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &initial_clipboard_text);
string16 clipboard_text;
- TextfieldViewsModel model;
+ TextfieldViewsModel model(NULL);
model.Append(ASCIIToUTF16("HELLO WORLD"));
model.MoveCursorToEnd(false);
@@ -369,11 +380,11 @@ void SelectWordTestVerifier(TextfieldViewsModel &model,
}
TEST_F(TextfieldViewsModelTest, SelectWordTest) {
- TextfieldViewsModel model;
+ TextfieldViewsModel model(NULL);
model.Append(ASCIIToUTF16(" HELLO !! WO RLD "));
// Test when cursor is at the beginning.
- model.MoveCursorToStart(false);
+ model.MoveCursorToHome(false);
model.SelectWord();
SelectWordTestVerifier(model, " ", 2U);
@@ -406,9 +417,9 @@ TEST_F(TextfieldViewsModelTest, SelectWordTest) {
}
TEST_F(TextfieldViewsModelTest, RangeTest) {
- TextfieldViewsModel model;
+ TextfieldViewsModel model(NULL);
model.Append(ASCIIToUTF16("HELLO WORLD"));
- model.MoveCursorToStart(false);
+ model.MoveCursorToHome(false);
ui::Range range;
model.GetSelectedRange(&range);
EXPECT_TRUE(range.is_empty());
@@ -462,7 +473,7 @@ TEST_F(TextfieldViewsModelTest, RangeTest) {
EXPECT_EQ(11U, range.end());
// Select All
- model.MoveCursorToStart(true);
+ model.MoveCursorToHome(true);
model.GetSelectedRange(&range);
EXPECT_FALSE(range.is_empty());
EXPECT_TRUE(range.is_reversed());
@@ -471,7 +482,7 @@ TEST_F(TextfieldViewsModelTest, RangeTest) {
}
TEST_F(TextfieldViewsModelTest, SelectRangeTest) {
- TextfieldViewsModel model;
+ TextfieldViewsModel model(NULL);
model.Append(ASCIIToUTF16("HELLO WORLD"));
ui::Range range(0, 6);
EXPECT_FALSE(range.is_reversed());
@@ -514,4 +525,187 @@ TEST_F(TextfieldViewsModelTest, SelectRangeTest) {
EXPECT_TRUE(model.GetSelectedText().empty());
}
+TEST_F(TextfieldViewsModelTest, CompositionTextTest) {
+ TextfieldViewsModel model(this);
+ model.Append(ASCIIToUTF16("1234590"));
+ model.SelectRange(ui::Range(5, 5));
+ EXPECT_FALSE(model.HasSelection());
+ EXPECT_EQ(5U, model.cursor_pos());
+
+ ui::Range range;
+ model.GetTextRange(&range);
+ EXPECT_EQ(0U, range.start());
+ EXPECT_EQ(7U, range.end());
+
+ ui::CompositionText composition;
+ composition.text = ASCIIToUTF16("678");
+ composition.underlines.push_back(ui::CompositionUnderline(0, 3, 0, false));
+ composition.selection = ui::Range(2, 3);
+ model.SetCompositionText(composition);
+ EXPECT_TRUE(model.HasCompositionText());
+ EXPECT_TRUE(model.HasSelection());
+
+ model.GetTextRange(&range);
+ EXPECT_EQ(10U, range.end());
+
+ model.GetCompositionTextRange(&range);
+ EXPECT_EQ(5U, range.start());
+ EXPECT_EQ(8U, range.end());
+
+ model.GetSelectedRange(&range);
+ EXPECT_EQ(7U, range.start());
+ EXPECT_EQ(8U, range.end());
+
+ EXPECT_STR_EQ("1234567890", model.text());
+ EXPECT_STR_EQ("8", model.GetSelectedText());
+ EXPECT_STR_EQ("456", model.GetTextFromRange(ui::Range(3, 6)));
+
+ TextfieldViewsModel::TextFragments fragments;
+ model.GetFragments(&fragments);
+ EXPECT_EQ(4U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].start);
+ EXPECT_EQ(5U, fragments[0].end);
+ EXPECT_FALSE(fragments[0].selected);
+ EXPECT_FALSE(fragments[0].underline);
+ EXPECT_EQ(5U, fragments[1].start);
+ EXPECT_EQ(7U, fragments[1].end);
+ EXPECT_FALSE(fragments[1].selected);
+ EXPECT_TRUE(fragments[1].underline);
+ EXPECT_EQ(7U, fragments[2].start);
+ EXPECT_EQ(8U, fragments[2].end);
+ EXPECT_TRUE(fragments[2].selected);
+ EXPECT_TRUE(fragments[2].underline);
+ EXPECT_EQ(8U, fragments[3].start);
+ EXPECT_EQ(10U, fragments[3].end);
+ EXPECT_FALSE(fragments[3].selected);
+ EXPECT_FALSE(fragments[3].underline);
+
+ EXPECT_FALSE(composition_text_confirmed_or_cleared_);
+ model.ClearCompositionText();
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_FALSE(model.HasCompositionText());
+ EXPECT_FALSE(model.HasSelection());
+ EXPECT_EQ(5U, model.cursor_pos());
+
+ model.SetCompositionText(composition);
+ EXPECT_STR_EQ("1234567890", model.text());
+ EXPECT_TRUE(model.SetText(ASCIIToUTF16("1234567890")));
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ model.MoveCursorToEnd(false);
+
+ model.SetCompositionText(composition);
+ EXPECT_STR_EQ("1234567890678", model.text());
+
+ model.InsertText(UTF8ToUTF16("-"));
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("1234567890-", model.text());
+ EXPECT_FALSE(model.HasCompositionText());
+ EXPECT_FALSE(model.HasSelection());
+
+ model.MoveCursorLeft(true);
+ EXPECT_STR_EQ("-", model.GetSelectedText());
+ model.SetCompositionText(composition);
+ EXPECT_STR_EQ("1234567890678", model.text());
+
+ model.ReplaceText(UTF8ToUTF16("-"));
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("1234567890-", model.text());
+ EXPECT_FALSE(model.HasCompositionText());
+ EXPECT_FALSE(model.HasSelection());
+
+ model.SetCompositionText(composition);
+ model.Append(UTF8ToUTF16("-"));
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("1234567890-678-", model.text());
+
+ model.SetCompositionText(composition);
+ model.Delete();
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("1234567890-678-", model.text());
+
+ model.SetCompositionText(composition);
+ model.Backspace();
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("1234567890-678-", model.text());
+
+ model.SetText(string16());
+ model.SetCompositionText(composition);
+ model.MoveCursorLeft(false);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("678", model.text());
+ EXPECT_EQ(2U, model.cursor_pos());
+
+ model.SetCompositionText(composition);
+ model.MoveCursorRight(false);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("676788", model.text());
+ EXPECT_EQ(6U, model.cursor_pos());
+
+ model.SetCompositionText(composition);
+ model.MoveCursorToPreviousWord(false);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("676788678", model.text());
+
+ model.SetText(string16());
+ model.SetCompositionText(composition);
+ model.MoveCursorToNextWord(false);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+
+ model.SetCompositionText(composition);
+ model.MoveCursorToHome(true);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("678678", model.text());
+
+ model.SetCompositionText(composition);
+ model.MoveCursorToEnd(false);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("678", model.text());
+
+ model.SetCompositionText(composition);
+ model.MoveCursorTo(0, true);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("678678", model.text());
+
+ model.SetCompositionText(composition);
+ model.SelectRange(ui::Range(0, 3));
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("678", model.text());
+
+ model.SetCompositionText(composition);
+ model.SelectAll();
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("678", model.text());
+
+ model.SetCompositionText(composition);
+ model.SelectWord();
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("678", model.text());
+
+ model.SetCompositionText(composition);
+ model.ClearSelection();
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+
+ model.SetCompositionText(composition);
+ EXPECT_FALSE(model.Cut());
+ EXPECT_FALSE(composition_text_confirmed_or_cleared_);
+}
+
} // namespace views
diff --git a/views/focus/accelerator_handler_touch.cc b/views/focus/accelerator_handler_touch.cc
index 3755e76..04dbec8 100644
--- a/views/focus/accelerator_handler_touch.cc
+++ b/views/focus/accelerator_handler_touch.cc
@@ -15,6 +15,7 @@
#include "views/accelerator.h"
#include "views/events/event.h"
#include "views/focus/focus_manager.h"
+#include "views/ime/input_method.h"
#include "views/touchui/touch_factory.h"
#include "views/widget/root_view.h"
#include "views/widget/widget_gtk.h"
@@ -23,7 +24,7 @@ namespace views {
namespace {
-RootView* FindRootViewForGdkWindow(GdkWindow* gdk_window) {
+Widget* FindWidgetForGdkWindow(GdkWindow* gdk_window) {
gpointer data = NULL;
gdk_window_get_user_data(gdk_window, &data);
GtkWidget* gtk_widget = reinterpret_cast<GtkWidget*>(data);
@@ -37,7 +38,7 @@ RootView* FindRootViewForGdkWindow(GdkWindow* gdk_window) {
DLOG(WARNING) << "no WidgetGtk found for that GtkWidget";
return NULL;
}
- return widget->GetWidget()->GetRootView();
+ return widget->GetWidget();
}
#if defined(HAVE_XINPUT2)
@@ -162,13 +163,21 @@ bool DispatchXEvent(XEvent* xev) {
#endif
GdkWindow* gwind = gdk_window_lookup_for_display(gdisp, xwindow);
-
- if (RootView* root = FindRootViewForGdkWindow(gwind)) {
+ Widget* widget = FindWidgetForGdkWindow(gwind);
+ if (widget) {
+ RootView* root = widget->GetRootView();
switch (xev->type) {
case KeyPress:
case KeyRelease: {
Event::FromNativeEvent2 from_native;
KeyEvent keyev(xev, from_native);
+ InputMethod* ime = widget->GetInputMethod();
+ // Always dispatch key events to the input method first, to make sure
+ // that the input method's hotkeys work all time.
+ if (ime) {
+ ime->DispatchKeyEvent(keyev);
+ return true;
+ }
return root->ProcessKeyEvent(keyev);
}
diff --git a/views/focus/focus_manager.cc b/views/focus/focus_manager.cc
index 5712b2e..e26cfd2 100644
--- a/views/focus/focus_manager.cc
+++ b/views/focus/focus_manager.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -368,10 +368,6 @@ void FocusManager::RestoreFocusedView() {
focus_change_reason_ = kReasonFocusRestore;
}
}
- } else {
- // Clearing the focus will focus the root window, so we still get key
- // events.
- ClearFocus();
}
}
diff --git a/views/view.cc b/views/view.cc
index e2acab9..5494a4e 100644
--- a/views/view.cc
+++ b/views/view.cc
@@ -846,8 +846,8 @@ TextInputClient* View::GetTextInputClient() {
}
InputMethod* View::GetInputMethod() {
- NOTIMPLEMENTED();
- return NULL;
+ Widget* widget = GetWidget();
+ return widget ? widget->GetInputMethod() : NULL;
}
// Accelerators ----------------------------------------------------------------
diff --git a/views/widget/native_widget.h b/views/widget/native_widget.h
index 9b28ac7..d9a1184 100644
--- a/views/widget/native_widget.h
+++ b/views/widget/native_widget.h
@@ -20,6 +20,7 @@ class OSExchangeData;
namespace views {
+class InputMethod;
class TooltipManager;
class Widget;
@@ -81,6 +82,17 @@ class NativeWidget {
// Returns true if this native widget is capturing all events.
virtual bool HasMouseCapture() const = 0;
+ // Returns the InputMethod for this native widget.
+ // Note that all widgets in a widget hierarchy share the same input method.
+ // TODO(suzhe): rename to GetInputMethod() when NativeWidget implementation
+ // class doesn't inherit Widget anymore.
+ virtual InputMethod* GetInputMethodNative() = 0;
+
+ // Sets a different InputMethod instance to this native widget. The instance
+ // must not be initialized, the ownership will be assumed by the native
+ // widget. It's only for testing purpose.
+ virtual void ReplaceInputMethod(InputMethod* input_method) = 0;
+
protected:
friend class Widget;
diff --git a/views/widget/widget.cc b/views/widget/widget.cc
index 2a31aa1..320f69f 100644
--- a/views/widget/widget.cc
+++ b/views/widget/widget.cc
@@ -8,6 +8,7 @@
#include "base/message_loop.h"
#include "ui/gfx/compositor/compositor.h"
#include "views/focus/view_storage.h"
+#include "views/ime/input_method.h"
#include "views/widget/default_theme_provider.h"
#include "views/widget/root_view.h"
#include "views/widget/native_widget.h"
@@ -219,6 +220,12 @@ FocusManager* Widget::GetFocusManager() {
return focus_manager_.get();
}
+InputMethod* Widget::GetInputMethod() {
+ Widget* toplevel_widget = GetTopLevelWidget();
+ return toplevel_widget ?
+ toplevel_widget->native_widget()->GetInputMethodNative() : NULL;
+}
+
bool Widget::ContainsNativeView(gfx::NativeView native_view) {
if (native_widget_->ContainsNativeView(native_view))
return true;
diff --git a/views/widget/widget.h b/views/widget/widget.h
index c57375d..8bf3ec0 100644
--- a/views/widget/widget.h
+++ b/views/widget/widget.h
@@ -33,6 +33,7 @@ using ui::ThemeProvider;
namespace views {
class DefaultThemeProvider;
+class InputMethod;
class NativeWidget;
class RootView;
class TooltipManager;
@@ -224,6 +225,10 @@ class Widget : public internal::NativeWidgetDelegate,
// TODO(beng): remove virtual.
virtual FocusManager* GetFocusManager();
+ // Returns the InputMethod for this widget.
+ // Note that all widgets in a widget hierarchy share the same input method.
+ InputMethod* GetInputMethod();
+
// Returns true if the native view |native_view| is contained in the
// views::View hierarchy rooted at this widget.
// TODO(beng): const.
diff --git a/views/widget/widget_gtk.cc b/views/widget/widget_gtk.cc
index 9f53543..00824f5 100644
--- a/views/widget/widget_gtk.cc
+++ b/views/widget/widget_gtk.cc
@@ -25,6 +25,7 @@
#include "ui/gfx/canvas_skia_paint.h"
#include "ui/gfx/path.h"
#include "views/views_delegate.h"
+#include "views/controls/textfield/native_textfield_views.h"
#include "views/focus/view_storage.h"
#include "views/widget/drop_target_gtk.h"
#include "views/widget/gtk_views_fixed.h"
@@ -294,6 +295,9 @@ WidgetGtk::WidgetGtk(Type type)
}
WidgetGtk::~WidgetGtk() {
+ // We need to delete the input method before calling DestroyRootView(),
+ // because it'll set focus_manager_ to NULL.
+ input_method_.reset();
DestroyRootView();
DCHECK(delete_on_destroy_ || widget_ == NULL);
if (type_ != TYPE_CHILD)
@@ -516,6 +520,20 @@ void WidgetGtk::Init(GtkWidget* parent,
CreateGtkWidget(parent, bounds);
delegate_->OnNativeWidgetCreated();
+ // Creates input method for toplevel widget after calling
+ // delegate_->OnNativeWidgetCreated(), to make sure that focus manager is
+ // already created at this point.
+ // TODO(suzhe): Always enable input method when we start to use
+ // RenderWidgetHostViewViews in normal ChromeOS.
+#if !defined(TOUCH_UI)
+ if (type_ != TYPE_CHILD && NativeTextfieldViews::IsTextfieldViewsEnabled()) {
+#else
+ if (type_ != TYPE_CHILD) {
+#endif
+ input_method_.reset(new InputMethodGtk(this));
+ input_method_->Init(GetWidget());
+ }
+
if (opacity_ != 255)
SetOpacity(opacity_);
@@ -665,22 +683,21 @@ void WidgetGtk::ClearNativeFocus() {
gtk_window_set_focus(GTK_WINDOW(GetNativeView()), NULL);
}
-bool WidgetGtk::HandleKeyboardEvent(GdkEventKey* event) {
+bool WidgetGtk::HandleKeyboardEvent(const KeyEvent& key) {
if (!GetFocusManager())
return false;
- KeyEvent key(reinterpret_cast<NativeEvent>(event));
- int key_code = key.key_code();
+ const int key_code = key.key_code();
bool handled = false;
// Always reset |should_handle_menu_key_release_| unless we are handling a
// VKEY_MENU key release event. It ensures that VKEY_MENU accelerator can only
// be activated when handling a VKEY_MENU key release event which is preceded
// by an un-handled VKEY_MENU key press event.
- if (key_code != ui::VKEY_MENU || event->type != GDK_KEY_RELEASE)
+ if (key_code != ui::VKEY_MENU || key.type() != ui::ET_KEY_RELEASED)
should_handle_menu_key_release_ = false;
- if (event->type == GDK_KEY_PRESS) {
+ if (key.type() == ui::ET_KEY_PRESSED) {
// VKEY_MENU is triggered by key release event.
// FocusManager::OnKeyEvent() returns false when the key has been consumed.
if (key_code != ui::VKEY_MENU)
@@ -768,6 +785,18 @@ bool WidgetGtk::HasMouseCapture() const {
return GTK_WIDGET_HAS_GRAB(window_contents_);
}
+InputMethod* WidgetGtk::GetInputMethodNative() {
+ return input_method_.get();
+}
+
+void WidgetGtk::ReplaceInputMethod(InputMethod* input_method) {
+ input_method_.reset(input_method);
+ if (input_method) {
+ input_method->set_delegate(this);
+ input_method->Init(GetWidget());
+ }
+}
+
gfx::Rect WidgetGtk::GetWindowScreenBounds() const {
// Client == Window bounds on Gtk.
return GetClientAreaScreenBounds();
@@ -858,6 +887,7 @@ void WidgetGtk::Close() {
void WidgetGtk::CloseNow() {
if (widget_) {
+ input_method_.reset();
gtk_widget_destroy(widget_); // Triggers OnDestroy().
}
}
@@ -1195,6 +1225,10 @@ gboolean WidgetGtk::OnFocusIn(GtkWidget* widget, GdkEventFocus* event) {
if (type_ == TYPE_CHILD)
return false;
+ // Only top-level Widget should have an InputMethod instance.
+ if (input_method_.get())
+ input_method_->OnFocus();
+
// See description of got_initial_focus_in_ for details on this.
if (!got_initial_focus_in_) {
got_initial_focus_in_ = true;
@@ -1213,6 +1247,10 @@ gboolean WidgetGtk::OnFocusOut(GtkWidget* widget, GdkEventFocus* event) {
if (type_ == TYPE_CHILD)
return false;
+ // Only top-level Widget should have an InputMethod instance.
+ if (input_method_.get())
+ input_method_->OnBlur();
+
// The top-level window lost focus, store the focused view.
GetFocusManager()->StoreFocusedView();
return false;
@@ -1220,43 +1258,13 @@ gboolean WidgetGtk::OnFocusOut(GtkWidget* widget, GdkEventFocus* event) {
gboolean WidgetGtk::OnKeyEvent(GtkWidget* widget, GdkEventKey* event) {
KeyEvent key(reinterpret_cast<NativeEvent>(event));
+ if (input_method_.get())
+ input_method_->DispatchKeyEvent(key);
+ else
+ DispatchKeyEventPostIME(key);
- // Always reset |should_handle_menu_key_release_| unless we are handling a
- // VKEY_MENU key release event. It ensures that VKEY_MENU accelerator can only
- // be activated when handling a VKEY_MENU key release event which is preceded
- // by an unhandled VKEY_MENU key press event. See also HandleKeyboardEvent().
- if (key.key_code() != ui::VKEY_MENU || event->type != GDK_KEY_RELEASE)
- should_handle_menu_key_release_ = false;
-
- bool handled = false;
-
- // Dispatch the key event to View hierarchy first.
- handled = GetRootView()->ProcessKeyEvent(key);
-
- // Dispatch the key event to native GtkWidget hierarchy.
- // To prevent GtkWindow from handling the key event as a keybinding, we need
- // to bypass GtkWindow's default key event handler and dispatch the event
- // here.
- if (!handled && GTK_IS_WINDOW(widget))
- handled = gtk_window_propagate_key_event(GTK_WINDOW(widget), event);
-
- // On Linux, in order to handle VKEY_MENU (Alt) accelerator key correctly and
- // avoid issues like: http://crbug.com/40966 and http://crbug.com/49701, we
- // should only send the key event to the focus manager if it's not handled by
- // any View or native GtkWidget.
- // The flow is different when the focus is in a RenderWidgetHostViewGtk, which
- // always consumes the key event and send it back to us later by calling
- // HandleKeyboardEvent() directly, if it's not handled by webkit.
- if (!handled)
- handled = HandleKeyboardEvent(event);
-
- // Dispatch the key event for bindings processing.
- if (!handled && GTK_IS_WINDOW(widget))
- handled = gtk_bindings_activate_event(GTK_OBJECT(widget), event);
-
- // Always return true for toplevel window to prevents GtkWindow's default key
- // event handler.
- return GTK_IS_WINDOW(widget) ? true : handled;
+ // Returns true to prevent GtkWindow's default key event handler.
+ return true;
}
gboolean WidgetGtk::OnQueryTooltip(GtkWidget* widget,
@@ -1334,6 +1342,45 @@ gfx::AcceleratedWidget WidgetGtk::GetAcceleratedWidget() {
return GDK_WINDOW_XID(window_contents_->window);
}
+void WidgetGtk::DispatchKeyEventPostIME(const KeyEvent& key) {
+ // Always reset |should_handle_menu_key_release_| unless we are handling a
+ // VKEY_MENU key release event. It ensures that VKEY_MENU accelerator can only
+ // be activated when handling a VKEY_MENU key release event which is preceded
+ // by an unhandled VKEY_MENU key press event. See also HandleKeyboardEvent().
+ if (key.key_code() != ui::VKEY_MENU || key.type() != ui::ET_KEY_RELEASED)
+ should_handle_menu_key_release_ = false;
+
+ bool handled = false;
+
+ // Dispatch the key event to View hierarchy first.
+ handled = GetRootView()->ProcessKeyEvent(key);
+
+ if (key.key_code() == ui::VKEY_PROCESSKEY || handled)
+ return;
+
+ // Dispatch the key event to native GtkWidget hierarchy.
+ // To prevent GtkWindow from handling the key event as a keybinding, we need
+ // to bypass GtkWindow's default key event handler and dispatch the event
+ // here.
+ GdkEventKey* event = reinterpret_cast<GdkEventKey*>(key.native_event());
+ if (!handled && event && GTK_IS_WINDOW(widget_))
+ handled = gtk_window_propagate_key_event(GTK_WINDOW(widget_), event);
+
+ // On Linux, in order to handle VKEY_MENU (Alt) accelerator key correctly and
+ // avoid issues like: http://crbug.com/40966 and http://crbug.com/49701, we
+ // should only send the key event to the focus manager if it's not handled by
+ // any View or native GtkWidget.
+ // The flow is different when the focus is in a RenderWidgetHostViewGtk, which
+ // always consumes the key event and send it back to us later by calling
+ // HandleKeyboardEvent() directly, if it's not handled by webkit.
+ if (!handled)
+ handled = HandleKeyboardEvent(key);
+
+ // Dispatch the key event for bindings processing.
+ if (!handled && event && GTK_IS_WINDOW(widget_))
+ gtk_bindings_activate_event(GTK_OBJECT(widget_), event);
+}
+
gboolean WidgetGtk::OnWindowPaint(GtkWidget* widget, GdkEventExpose* event) {
// Clear the background to be totally transparent. We don't need to
// paint the root view here as that is done by OnPaint.
diff --git a/views/widget/widget_gtk.h b/views/widget/widget_gtk.h
index 9ddc0a4..323f886 100644
--- a/views/widget/widget_gtk.h
+++ b/views/widget/widget_gtk.h
@@ -13,6 +13,8 @@
#include "ui/base/x/active_window_watcher_x.h"
#include "ui/gfx/size.h"
#include "views/focus/focus_manager.h"
+#include "views/ime/input_method_delegate.h"
+#include "views/ime/input_method_gtk.h"
#include "views/widget/native_widget.h"
#include "views/widget/widget.h"
@@ -41,7 +43,8 @@ class NativeWidgetDelegate;
// Widget implementation for GTK.
class WidgetGtk : public Widget,
public NativeWidget,
- public ui::ActiveWindowWatcherX::Observer {
+ public ui::ActiveWindowWatcherX::Observer,
+ public internal::InputMethodDelegate {
public:
// Type of widget.
enum Type {
@@ -171,7 +174,7 @@ class WidgetGtk : public Widget,
// Handles a keyboard event by sending it to our focus manager.
// Returns true if it's handled by the focus manager.
- bool HandleKeyboardEvent(GdkEventKey* event);
+ bool HandleKeyboardEvent(const KeyEvent& key);
// Enables debug painting. See |debug_paint_enabled_| for details.
static void EnableDebugPaint();
@@ -196,6 +199,8 @@ class WidgetGtk : public Widget,
virtual void SetMouseCapture() OVERRIDE;
virtual void ReleaseMouseCapture() OVERRIDE;
virtual bool HasMouseCapture() const OVERRIDE;
+ virtual InputMethod* GetInputMethodNative() OVERRIDE;
+ virtual void ReplaceInputMethod(InputMethod* input_method) OVERRIDE;
virtual gfx::Rect GetWindowScreenBounds() const OVERRIDE;
virtual gfx::Rect GetClientAreaScreenBounds() const OVERRIDE;
virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE;
@@ -297,6 +302,9 @@ class WidgetGtk : public Widget,
// Overridden from NativeWidget
virtual gfx::AcceleratedWidget GetAcceleratedWidget() OVERRIDE;
+ // Overridden from internal::InputMethodDelegate
+ virtual void DispatchKeyEventPostIME(const KeyEvent& key) OVERRIDE;
+
CHROMEGTK_CALLBACK_1(WidgetGtk, gboolean, OnWindowPaint, GdkEventExpose*);
// Returns the first ancestor of |widget| that is a window.
@@ -414,6 +422,8 @@ class WidgetGtk : public Widget,
// that window manager shows the window only after the window is painted.
bool painted_;
+ scoped_ptr<InputMethod> input_method_;
+
DISALLOW_COPY_AND_ASSIGN(WidgetGtk);
};
diff --git a/views/widget/widget_win.cc b/views/widget/widget_win.cc
index 0d6f47c..1cadfb1 100644
--- a/views/widget/widget_win.cc
+++ b/views/widget/widget_win.cc
@@ -23,9 +23,11 @@
#include "ui/gfx/path.h"
#include "views/accessibility/native_view_accessibility_win.h"
#include "views/controls/native_control_win.h"
+#include "views/controls/textfield/native_textfield_views.h"
#include "views/focus/accelerator_handler.h"
#include "views/focus/focus_util_win.h"
#include "views/focus/view_storage.h"
+#include "views/ime/input_method_win.h"
#include "views/views_delegate.h"
#include "views/widget/aero_tooltip_manager.h"
#include "views/widget/child_window_message_processor.h"
@@ -143,11 +145,15 @@ WidgetWin::WidgetWin()
restore_focus_when_enabled_(false),
accessibility_view_events_index_(-1),
accessibility_view_events_(kMaxAccessibilityViewEvents),
- previous_cursor_(NULL) {
+ previous_cursor_(NULL),
+ is_input_method_win_(false) {
set_native_widget(this);
}
WidgetWin::~WidgetWin() {
+ // We need to delete the input method before calling DestroyRootView(),
+ // because it'll set focus_manager_ to NULL.
+ input_method_.reset();
DestroyRootView();
}
@@ -333,6 +339,19 @@ bool WidgetWin::HasMouseCapture() const {
return GetCapture() == hwnd();
}
+InputMethod* WidgetWin::GetInputMethodNative() {
+ return input_method_.get();
+}
+
+void WidgetWin::ReplaceInputMethod(InputMethod* input_method) {
+ input_method_.reset(input_method);
+ if (input_method) {
+ input_method->set_delegate(this);
+ input_method->Init(GetWidget());
+ }
+ is_input_method_win_ = false;
+}
+
gfx::Rect WidgetWin::GetWindowScreenBounds() const {
RECT r;
GetWindowRect(&r);
@@ -383,6 +402,11 @@ void WidgetWin::Close() {
}
void WidgetWin::CloseNow() {
+ // Destroys the input method before closing the window so that it can be
+ // detached from the widget correctly.
+ input_method_.reset();
+ is_input_method_win_ = false;
+
// We may already have been destroyed if the selection resulted in a tab
// switch which will have reactivated the browser window and closed us, so
// we need to check to see if we're still a window before trying to destroy
@@ -620,6 +644,15 @@ LRESULT WidgetWin::OnCreate(CREATESTRUCT* create_struct) {
ClientAreaSizeChanged();
delegate_->OnNativeWidgetCreated();
+
+ // delegate_->OnNativeWidgetCreated() creates the focus manager for top-level
+ // widget. Only top-level widget should have an input method.
+ if (delegate_->HasFocusManager() &&
+ NativeTextfieldViews::IsTextfieldViewsEnabled()) {
+ input_method_.reset(new InputMethodWin(this));
+ input_method_->Init(GetWidget());
+ is_input_method_win_ = true;
+ }
return 0;
}
@@ -699,6 +732,45 @@ void WidgetWin::OnHScroll(int scroll_type, short position, HWND scrollbar) {
SetMsgHandled(FALSE);
}
+LRESULT WidgetWin::OnImeMessages(UINT message, WPARAM w_param, LPARAM l_param) {
+ if (!is_input_method_win_) {
+ SetMsgHandled(FALSE);
+ return 0;
+ }
+
+ InputMethodWin* ime = static_cast<InputMethodWin*>(input_method_.get());
+ BOOL handled = FALSE;
+ LRESULT result = 0;
+ switch (message) {
+ case WM_IME_SETCONTEXT:
+ result = ime->OnImeSetContext(message, w_param, l_param, &handled);
+ break;
+ case WM_IME_STARTCOMPOSITION:
+ result = ime->OnImeStartComposition(message, w_param, l_param, &handled);
+ break;
+ case WM_IME_COMPOSITION:
+ result = ime->OnImeComposition(message, w_param, l_param, &handled);
+ break;
+ case WM_IME_ENDCOMPOSITION:
+ result = ime->OnImeEndComposition(message, w_param, l_param, &handled);
+ break;
+ case WM_CHAR:
+ case WM_SYSCHAR:
+ result = ime->OnChar(message, w_param, l_param, &handled);
+ break;
+ case WM_DEADCHAR:
+ case WM_SYSDEADCHAR:
+ result = ime->OnDeadChar(message, w_param, l_param, &handled);
+ break;
+ default:
+ NOTREACHED() << "Unknown IME message:" << message;
+ break;
+ }
+
+ SetMsgHandled(handled);
+ return result;
+}
+
void WidgetWin::OnInitMenu(HMENU menu) {
SetMsgHandled(FALSE);
}
@@ -709,28 +781,37 @@ void WidgetWin::OnInitMenuPopup(HMENU menu,
SetMsgHandled(FALSE);
}
-LRESULT WidgetWin::OnKeyDown(UINT message, WPARAM w_param, LPARAM l_param) {
- RootView* root_view = GetFocusedViewRootView();
- if (!root_view)
- root_view = GetRootView();
+void WidgetWin::OnInputLangChange(DWORD character_set, HKL input_language_id) {
+ if (is_input_method_win_) {
+ static_cast<InputMethodWin*>(input_method_.get())->OnInputLangChange(
+ character_set, input_language_id);
+ }
+}
+LRESULT WidgetWin::OnKeyDown(UINT message, WPARAM w_param, LPARAM l_param) {
MSG msg = { hwnd(), message, w_param, l_param };
- SetMsgHandled(root_view->ProcessKeyEvent(KeyEvent(msg)));
+ KeyEvent key(msg);
+ if (input_method_.get())
+ input_method_->DispatchKeyEvent(key);
+ else
+ DispatchKeyEventPostIME(key);
return 0;
}
LRESULT WidgetWin::OnKeyUp(UINT message, WPARAM w_param, LPARAM l_param) {
- RootView* root_view = GetFocusedViewRootView();
- if (!root_view)
- root_view = GetRootView();
-
MSG msg = { hwnd(), message, w_param, l_param };
- SetMsgHandled(root_view->ProcessKeyEvent(KeyEvent(msg)));
+ KeyEvent key(msg);
+ if (input_method_.get())
+ input_method_->DispatchKeyEvent(key);
+ else
+ DispatchKeyEventPostIME(key);
return 0;
}
void WidgetWin::OnKillFocus(HWND focused_window) {
delegate_->OnNativeBlur(focused_window);
+ if (input_method_.get())
+ input_method_->OnBlur();
SetMsgHandled(FALSE);
}
@@ -863,6 +944,8 @@ LRESULT WidgetWin::OnReflectedMessage(UINT msg,
void WidgetWin::OnSetFocus(HWND focused_window) {
delegate_->OnNativeFocus(focused_window);
+ if (input_method_.get())
+ input_method_->OnFocus();
SetMsgHandled(FALSE);
}
@@ -1057,6 +1140,14 @@ gfx::AcceleratedWidget WidgetWin::GetAcceleratedWidget() {
return gfx::kNullAcceleratedWidget;
}
+void WidgetWin::DispatchKeyEventPostIME(const KeyEvent& key) {
+ RootView* root_view = GetFocusedViewRootView();
+ if (!root_view)
+ root_view = GetRootView();
+
+ SetMsgHandled(root_view->ProcessKeyEvent(key));
+}
+
////////////////////////////////////////////////////////////////////////////////
// Widget, public:
diff --git a/views/widget/widget_win.h b/views/widget/widget_win.h
index 53e5f50..6c72a22 100644
--- a/views/widget/widget_win.h
+++ b/views/widget/widget_win.h
@@ -20,6 +20,7 @@
#include "base/win/scoped_comptr.h"
#include "ui/base/win/window_impl.h"
#include "views/focus/focus_manager.h"
+#include "views/ime/input_method_delegate.h"
#include "views/layout/layout_manager.h"
#include "views/widget/native_widget.h"
#include "views/widget/widget.h"
@@ -81,7 +82,8 @@ const int WM_NCUAHDRAWFRAME = 0xAF;
class WidgetWin : public ui::WindowImpl,
public Widget,
public NativeWidget,
- public MessageLoopForUI::Observer {
+ public MessageLoopForUI::Observer,
+ public internal::InputMethodDelegate {
public:
WidgetWin();
virtual ~WidgetWin();
@@ -209,6 +211,8 @@ class WidgetWin : public ui::WindowImpl,
virtual void SetMouseCapture() OVERRIDE;
virtual void ReleaseMouseCapture() OVERRIDE;
virtual bool HasMouseCapture() const OVERRIDE;
+ virtual InputMethod* GetInputMethodNative() OVERRIDE;
+ virtual void ReplaceInputMethod(InputMethod* input_method) OVERRIDE;
virtual gfx::Rect GetWindowScreenBounds() const OVERRIDE;
virtual gfx::Rect GetClientAreaScreenBounds() const OVERRIDE;
virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE;
@@ -270,8 +274,18 @@ class WidgetWin : public ui::WindowImpl,
// Key events.
MESSAGE_HANDLER_EX(WM_KEYDOWN, OnKeyDown)
MESSAGE_HANDLER_EX(WM_KEYUP, OnKeyUp)
- MESSAGE_HANDLER_EX(WM_SYSKEYDOWN, OnKeyDown);
- MESSAGE_HANDLER_EX(WM_SYSKEYUP, OnKeyUp);
+ MESSAGE_HANDLER_EX(WM_SYSKEYDOWN, OnKeyDown)
+ MESSAGE_HANDLER_EX(WM_SYSKEYUP, OnKeyUp)
+
+ // IME Events.
+ MESSAGE_HANDLER_EX(WM_IME_SETCONTEXT, OnImeMessages)
+ MESSAGE_HANDLER_EX(WM_IME_STARTCOMPOSITION, OnImeMessages)
+ MESSAGE_HANDLER_EX(WM_IME_COMPOSITION, OnImeMessages)
+ MESSAGE_HANDLER_EX(WM_IME_ENDCOMPOSITION, OnImeMessages)
+ MESSAGE_HANDLER_EX(WM_CHAR, OnImeMessages)
+ MESSAGE_HANDLER_EX(WM_SYSCHAR, OnImeMessages)
+ MESSAGE_HANDLER_EX(WM_DEADCHAR, OnImeMessages)
+ MESSAGE_HANDLER_EX(WM_SYSDEADCHAR, OnImeMessages)
// This list is in _ALPHABETICAL_ order! OR I WILL HURT YOU.
MSG_WM_ACTIVATE(OnActivate)
@@ -293,6 +307,7 @@ class WidgetWin : public ui::WindowImpl,
MSG_WM_HSCROLL(OnHScroll)
MSG_WM_INITMENU(OnInitMenu)
MSG_WM_INITMENUPOPUP(OnInitMenuPopup)
+ MSG_WM_INPUTLANGCHANGE(OnInputLangChange)
MSG_WM_KILLFOCUS(OnKillFocus)
MSG_WM_MOVE(OnMove)
MSG_WM_MOVING(OnMoving)
@@ -347,8 +362,10 @@ class WidgetWin : public ui::WindowImpl,
virtual LRESULT OnGetObject(UINT uMsg, WPARAM w_param, LPARAM l_param);
virtual void OnGetMinMaxInfo(MINMAXINFO* minmax_info);
virtual void OnHScroll(int scroll_type, short position, HWND scrollbar);
+ virtual LRESULT OnImeMessages(UINT message, WPARAM w_param, LPARAM l_param);
virtual void OnInitMenu(HMENU menu);
virtual void OnInitMenuPopup(HMENU menu, UINT position, BOOL is_system_menu);
+ virtual void OnInputLangChange(DWORD character_set, HKL input_language_id);
virtual LRESULT OnKeyDown(UINT message, WPARAM w_param, LPARAM l_param);
virtual LRESULT OnKeyUp(UINT message, WPARAM w_param, LPARAM l_param);
virtual void OnKillFocus(HWND focused_window);
@@ -431,6 +448,9 @@ class WidgetWin : public ui::WindowImpl,
// Overridden from NativeWidget.
virtual gfx::AcceleratedWidget GetAcceleratedWidget() OVERRIDE;
+ // Overridden from internal::InputMethodDelegate
+ virtual void DispatchKeyEventPostIME(const KeyEvent& key) OVERRIDE;
+
// A delegate implementation that handles events received here.
internal::NativeWidgetDelegate* delegate_;
@@ -505,6 +525,11 @@ class WidgetWin : public ui::WindowImpl,
ViewProps props_;
+ scoped_ptr<InputMethod> input_method_;
+
+ // Indicates if the |input_method_| is an InputMethodWin instance.
+ bool is_input_method_win_;
+
DISALLOW_COPY_AND_ASSIGN(WidgetWin);
};