summaryrefslogtreecommitdiffstats
path: root/ui/views
diff options
context:
space:
mode:
Diffstat (limited to 'ui/views')
-rw-r--r--ui/views/controls/textfield/gtk_views_entry.cc74
-rw-r--r--ui/views/controls/textfield/gtk_views_entry.h53
-rw-r--r--ui/views/controls/textfield/gtk_views_textview.cc101
-rw-r--r--ui/views/controls/textfield/gtk_views_textview.h54
-rw-r--r--ui/views/controls/textfield/native_textfield_gtk.cc438
-rw-r--r--ui/views/controls/textfield/native_textfield_gtk.h105
-rw-r--r--ui/views/controls/textfield/native_textfield_views.cc1085
-rw-r--r--ui/views/controls/textfield/native_textfield_views.h267
-rw-r--r--ui/views/controls/textfield/native_textfield_views_unittest.cc1573
-rw-r--r--ui/views/controls/textfield/native_textfield_wayland.cc20
-rw-r--r--ui/views/controls/textfield/native_textfield_win.cc1168
-rw-r--r--ui/views/controls/textfield/native_textfield_win.h294
-rw-r--r--ui/views/controls/textfield/native_textfield_wrapper.h153
-rw-r--r--ui/views/controls/textfield/textfield.cc453
-rw-r--r--ui/views/controls/textfield/textfield.h315
-rw-r--r--ui/views/controls/textfield/textfield_controller.h46
-rw-r--r--ui/views/controls/textfield/textfield_views_model.cc795
-rw-r--r--ui/views/controls/textfield/textfield_views_model.h329
-rw-r--r--ui/views/controls/textfield/textfield_views_model_unittest.cc1535
-rw-r--r--ui/views/examples/textfield_example.cc2
-rw-r--r--ui/views/examples/textfield_example.h2
-rw-r--r--ui/views/focus/focus_manager_unittest.cc2
-rw-r--r--ui/views/focus/focus_traversal_unittest.cc2
-rw-r--r--ui/views/touchui/touch_selection_controller_impl_unittest.cc4
-rw-r--r--ui/views/widget/native_widget_gtk.cc4
-rw-r--r--ui/views/widget/native_widget_win.cc2
26 files changed, 8867 insertions, 9 deletions
diff --git a/ui/views/controls/textfield/gtk_views_entry.cc b/ui/views/controls/textfield/gtk_views_entry.cc
new file mode 100644
index 0000000..d26e370
--- /dev/null
+++ b/ui/views/controls/textfield/gtk_views_entry.cc
@@ -0,0 +1,74 @@
+// 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.
+
+#include "ui/views/controls/textfield/gtk_views_entry.h"
+
+#include "base/utf_string_conversions.h"
+#include "ui/gfx/canvas_skia_paint.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/skia_utils_gtk.h"
+#include "ui/views/controls/textfield/native_textfield_gtk.h"
+#include "ui/views/controls/textfield/textfield.h"
+
+G_BEGIN_DECLS
+
+G_DEFINE_TYPE(GtkViewsEntry, gtk_views_entry, GTK_TYPE_ENTRY)
+
+static gint gtk_views_entry_expose_event(GtkWidget *widget,
+ GdkEventExpose *event) {
+ views::NativeTextfieldGtk* host = GTK_VIEWS_ENTRY(widget)->host;
+#if defined(OS_CHROMEOS)
+ // Draw textfield background over the default white rectangle.
+ if (event->window == widget->window) {
+ gfx::CanvasSkiaPaint canvas(event);
+ if (!canvas.is_empty() && host) {
+ host->textfield()->OnPaintBackground(&canvas);
+ }
+ }
+#endif
+
+ gint result = GTK_WIDGET_CLASS(gtk_views_entry_parent_class)->expose_event(
+ widget, event);
+
+ GtkEntry* entry = GTK_ENTRY(widget);
+
+ // Internally GtkEntry creates an additional window (text_area) that the
+ // text is drawn to. We only need paint after that window has painted.
+ if (host && event->window == entry->text_area &&
+ !host->textfield()->text_to_display_when_empty().empty() &&
+ g_utf8_strlen(gtk_entry_get_text(entry), -1) == 0) {
+ gfx::CanvasSkiaPaint canvas(event);
+ if (!canvas.is_empty()) {
+ gfx::Insets insets =
+ views::NativeTextfieldGtk::GetEntryInnerBorder(entry);
+ gfx::Font font = host->textfield()->font();
+ const string16 text = host->textfield()->text_to_display_when_empty();
+ canvas.DrawStringInt(
+ text, font,
+ gfx::GdkColorToSkColor(widget->style->text[GTK_STATE_INSENSITIVE]),
+ insets.left(), insets.top(),
+ widget->allocation.width - insets.width(), font.GetHeight());
+ }
+ }
+
+ return result;
+}
+
+static void gtk_views_entry_class_init(GtkViewsEntryClass* views_entry_class) {
+ GtkWidgetClass* widget_class =
+ reinterpret_cast<GtkWidgetClass*>(views_entry_class);
+ widget_class->expose_event = gtk_views_entry_expose_event;
+}
+
+static void gtk_views_entry_init(GtkViewsEntry* entry) {
+ entry->host = NULL;
+}
+
+GtkWidget* gtk_views_entry_new(views::NativeTextfieldGtk* host) {
+ gpointer entry = g_object_new(GTK_TYPE_VIEWS_ENTRY, NULL);
+ GTK_VIEWS_ENTRY(entry)->host = host;
+ return GTK_WIDGET(entry);
+}
+
+G_END_DECLS
diff --git a/ui/views/controls/textfield/gtk_views_entry.h b/ui/views/controls/textfield/gtk_views_entry.h
new file mode 100644
index 0000000..1584165
--- /dev/null
+++ b/ui/views/controls/textfield/gtk_views_entry.h
@@ -0,0 +1,53 @@
+// 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.
+
+#ifndef UI_VIEWS_CONTROLS_TEXTFIELD_GTK_VIEWS_ENTRY_H_
+#define UI_VIEWS_CONTROLS_TEXTFIELD_GTK_VIEWS_ENTRY_H_
+#pragma once
+
+#include <gdk/gdk.h>
+#include <gtk/gtkentry.h>
+
+namespace views {
+class NativeTextfieldGtk;
+}
+
+// GtkViewsEntry is a subclass of GtkEntry that can draw text when the text in
+// the entry is empty. For example, this could show the text 'password' in a
+// password field when the text is empty. GtkViewsEntry is used internally by
+// NativeTextfieldGtk.
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_VIEWS_ENTRY (gtk_views_entry_get_type ())
+#define GTK_VIEWS_ENTRY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_VIEWS_ENTRY, GtkViewsEntry))
+#define GTK_VIEWS_ENTRY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_VIEWS_ENTRY, GtkViewsEntryClass))
+#define GTK_IS_VIEWS_ENTRY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_VIEWS_ENTRY))
+#define GTK_IS_VIEWS_ENTRY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_VIEWS_ENTRY))
+#define GTK_VIEWS_ENTRY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_VIEWS_ENTRY, GtkViewsEntry))
+
+typedef struct _GtkViewsEntry GtkViewsEntry;
+typedef struct _GtkViewsEntryClass GtkViewsEntryClass;
+
+struct _GtkViewsEntry {
+ GtkEntry entry;
+ views::NativeTextfieldGtk* host;
+};
+
+struct _GtkViewsEntryClass {
+ GtkEntryClass parent_class;
+};
+
+GtkWidget* gtk_views_entry_new(views::NativeTextfieldGtk* host);
+
+GType gtk_views_entry_get_type();
+
+G_END_DECLS
+
+#endif // UI_VIEWS_CONTROLS_TEXTFIELD_GTK_VIEWS_ENTRY_H_
diff --git a/ui/views/controls/textfield/gtk_views_textview.cc b/ui/views/controls/textfield/gtk_views_textview.cc
new file mode 100644
index 0000000..8e4ca38
--- /dev/null
+++ b/ui/views/controls/textfield/gtk_views_textview.cc
@@ -0,0 +1,101 @@
+// 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.
+
+#include "ui/views/controls/textfield/gtk_views_textview.h"
+
+#include "base/utf_string_conversions.h"
+#include "ui/gfx/canvas_skia_paint.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/skia_utils_gtk.h"
+#include "ui/views/controls/textfield/native_textfield_gtk.h"
+#include "ui/views/controls/textfield/textfield.h"
+
+G_BEGIN_DECLS
+
+G_DEFINE_TYPE(GtkViewsTextView, gtk_views_textview, GTK_TYPE_TEXT_VIEW)
+
+static gint gtk_views_textview_expose_event(GtkWidget *widget,
+ GdkEventExpose *event) {
+ GtkTextView* text_view = GTK_TEXT_VIEW(widget);
+ GdkWindow* text_window = gtk_text_view_get_window(text_view,
+ GTK_TEXT_WINDOW_TEXT);
+ if (event->window == text_window) {
+ gint width, height;
+ gdk_drawable_get_size (text_window, &width, &height);
+ gtk_paint_flat_box(widget->style, text_window,
+ static_cast<GtkStateType>(GTK_WIDGET_STATE(widget)), GTK_SHADOW_NONE,
+ &event->area, widget, "textview",
+ 0, 0, width, height);
+ }
+
+ gint result = GTK_WIDGET_CLASS(gtk_views_textview_parent_class)->expose_event(
+ widget, event);
+
+ GtkTextBuffer* text_buffer = gtk_text_view_get_buffer(text_view);
+ views::NativeTextfieldGtk* host = GTK_VIEWS_TEXTVIEW(widget)->host;
+
+ GtkTextIter start;
+ GtkTextIter end;
+ gtk_text_buffer_get_bounds(text_buffer, &start, &end);
+
+ // Paint the info text to text window if GtkTextView contains no text.
+ if (host && event->window == text_window &&
+ !host->textfield()->text_to_display_when_empty().empty() &&
+ gtk_text_iter_equal(&start, &end)) {
+ gfx::CanvasSkiaPaint canvas(event);
+ if (!canvas.is_empty()) {
+ gfx::Insets insets =
+ views::NativeTextfieldGtk::GetTextViewInnerBorder(text_view);
+ gfx::Font font = host->textfield()->font();
+ const string16 text = host->textfield()->text_to_display_when_empty();
+ canvas.DrawStringInt(
+ text, font,
+ gfx::GdkColorToSkColor(widget->style->text[GTK_STATE_INSENSITIVE]),
+ insets.left(), insets.top(),
+ widget->allocation.width - insets.width(), font.GetHeight());
+ }
+ }
+
+ // Draw border and focus.
+ if (event->window == widget->window) {
+ gint width;
+ gint height;
+ gdk_drawable_get_size (widget->window, &width, &height);
+
+ bool has_focus = GTK_WIDGET_HAS_FOCUS(widget);
+
+ gtk_paint_shadow(widget->style, widget->window,
+ has_focus ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT,
+ &event->area, widget, "textview",
+ 0, 0, width, height);
+ if (has_focus) {
+ gtk_paint_focus(widget->style, widget->window,
+ static_cast<GtkStateType>(GTK_WIDGET_STATE(widget)),
+ &event->area, widget, "textview",
+ 0, 0, width, height);
+ }
+ }
+
+ return result;
+}
+
+static void gtk_views_textview_class_init(
+ GtkViewsTextViewClass* views_textview_class) {
+ GtkWidgetClass* widget_class =
+ reinterpret_cast<GtkWidgetClass*>(views_textview_class);
+ widget_class->expose_event = gtk_views_textview_expose_event;
+}
+
+static void gtk_views_textview_init(GtkViewsTextView* text_view) {
+ text_view->host = NULL;
+}
+
+GtkWidget* gtk_views_textview_new(views::NativeTextfieldGtk* host) {
+ gpointer text_view = g_object_new(GTK_TYPE_VIEWS_TEXTVIEW, NULL);
+ GTK_VIEWS_TEXTVIEW(text_view)->host = host;
+ return GTK_WIDGET(text_view);
+}
+
+G_END_DECLS
diff --git a/ui/views/controls/textfield/gtk_views_textview.h b/ui/views/controls/textfield/gtk_views_textview.h
new file mode 100644
index 0000000..9c2e4e6
--- /dev/null
+++ b/ui/views/controls/textfield/gtk_views_textview.h
@@ -0,0 +1,54 @@
+// 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.
+
+#ifndef UI_VIEWS_CONTROLS_TEXTFIELD_GTK_VIEWS_TEXTVIEW_H_
+#define UI_VIEWS_CONTROLS_TEXTFIELD_GTK_VIEWS_TEXTVIEW_H_
+#pragma once
+
+#include <gdk/gdk.h>
+#include <gtk/gtktextview.h>
+
+namespace views {
+class NativeTextfieldGtk;
+}
+
+// Similar to GtkViewsEntry, GtkViewsTextView is a subclass of GtkTextView
+// with a border and ability to show a custom info when text view has no text.
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_VIEWS_TEXTVIEW (gtk_views_textview_get_type())
+#define GTK_VIEWS_TEXTVIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_VIEWS_TEXTVIEW, \
+ GtkViewsTextView))
+#define GTK_VIEWS_TEXTVIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_VIEWS_TEXTVIEW, \
+ GtkViewsTextViewClass))
+#define GTK_IS_VIEWS_TEXTVIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_VIEWS_TEXTVIEW))
+#define GTK_IS_VIEWS_TEXTVIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_VIEWS_TEXTVIEW))
+#define GTK_VIEWS_TEXTVIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_VIEWS_TEXTVIEW, \
+ GtkViewsTextView))
+
+typedef struct _GtkViewsTextView GtkViewsTextView;
+typedef struct _GtkViewsTextViewClass GtkViewsTextViewClass;
+
+struct _GtkViewsTextView {
+ GtkTextView text_view;
+ views::NativeTextfieldGtk* host;
+};
+
+struct _GtkViewsTextViewClass {
+ GtkTextViewClass parent_class;
+};
+
+GtkWidget* gtk_views_textview_new(views::NativeTextfieldGtk* host);
+
+GType gtk_views_textview_get_type();
+
+G_END_DECLS
+
+#endif // UI_VIEWS_CONTROLS_TEXTFIELD_GTK_VIEWS_TEXTVIEW_H_
diff --git a/ui/views/controls/textfield/native_textfield_gtk.cc b/ui/views/controls/textfield/native_textfield_gtk.cc
new file mode 100644
index 0000000..3bab2a1
--- /dev/null
+++ b/ui/views/controls/textfield/native_textfield_gtk.cc
@@ -0,0 +1,438 @@
+// 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.
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include "ui/views/controls/textfield/native_textfield_gtk.h"
+
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "ui/base/range/range.h"
+#include "ui/gfx/gtk_util.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/selection_model.h"
+#include "ui/gfx/skia_utils_gtk.h"
+#include "ui/views/controls/textfield/gtk_views_entry.h"
+#include "ui/views/controls/textfield/gtk_views_textview.h"
+#include "ui/views/controls/textfield/native_textfield_views.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/widget/native_widget_gtk.h"
+
+namespace views {
+
+// A character used to hide a text in password mode.
+static const char kPasswordChar = '*';
+
+// Border width for GtkTextView.
+const int kTextViewBorderWidth = 4;
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldGtk, public:
+
+NativeTextfieldGtk::NativeTextfieldGtk(Textfield* textfield)
+ : textfield_(textfield),
+ paste_clipboard_requested_(false) {
+ // Make |textfield| the focused view, so that when we get focused the focus
+ // manager sees |textfield| as the focused view (since we are just a wrapper
+ // view).
+ set_focus_view(textfield);
+}
+
+NativeTextfieldGtk::~NativeTextfieldGtk() {
+}
+
+// Returns the inner border of an entry.
+// static
+gfx::Insets NativeTextfieldGtk::GetEntryInnerBorder(GtkEntry* entry) {
+ const GtkBorder* inner_border = gtk_entry_get_inner_border(entry);
+ if (inner_border)
+ return gfx::Insets(*inner_border);
+
+ // No explicit border set, try the style.
+ GtkBorder* style_border;
+ gtk_widget_style_get(GTK_WIDGET(entry), "inner-border", &style_border, NULL);
+ if (style_border) {
+ gfx::Insets insets = gfx::Insets(*style_border);
+ gtk_border_free(style_border);
+ return insets;
+ }
+
+ // If border is null, Gtk uses 2 on all sides.
+ return gfx::Insets(2, 2, 2, 2);
+}
+
+gfx::Insets NativeTextfieldGtk::GetTextViewInnerBorder(GtkTextView* text_view) {
+ return gfx::Insets(kTextViewBorderWidth / 2, kTextViewBorderWidth / 2,
+ kTextViewBorderWidth / 2, kTextViewBorderWidth / 2);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldGtk, NativeTextfieldWrapper implementation:
+
+string16 NativeTextfieldGtk::GetText() const {
+ return UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(native_view())));
+}
+
+void NativeTextfieldGtk::UpdateText() {
+ if (!native_view())
+ return;
+ gtk_entry_set_text(GTK_ENTRY(native_view()),
+ UTF16ToUTF8(textfield_->text()).c_str());
+}
+
+void NativeTextfieldGtk::AppendText(const string16& text) {
+ if (!native_view())
+ return;
+ gtk_entry_append_text(GTK_ENTRY(native_view()), UTF16ToUTF8(text).c_str());
+}
+
+string16 NativeTextfieldGtk::GetSelectedText() const {
+ if (!native_view())
+ return string16();
+
+ string16 result;
+
+ gint start_pos;
+ gint end_pos;
+ if (!gtk_editable_get_selection_bounds(GTK_EDITABLE(native_view()),
+ &start_pos, &end_pos))
+ return result; // No selection.
+
+ UTF8ToUTF16(gtk_editable_get_chars(GTK_EDITABLE(native_view()),
+ start_pos, end_pos),
+ end_pos - start_pos, &result);
+
+ return result;
+}
+
+void NativeTextfieldGtk::SelectAll() {
+ if (!native_view())
+ return;
+ // -1 as the end position selects to the end of the text.
+ gtk_editable_select_region(GTK_EDITABLE(native_view()), 0, -1);
+}
+
+void NativeTextfieldGtk::ClearSelection() {
+ if (!native_view())
+ return;
+ gtk_editable_select_region(GTK_EDITABLE(native_view()), 0, 0);
+}
+
+void NativeTextfieldGtk::UpdateBorder() {
+ if (!native_view())
+ return;
+
+ if (!textfield_->draw_border())
+ gtk_entry_set_has_frame(GTK_ENTRY(native_view()), false);
+}
+
+void NativeTextfieldGtk::UpdateTextColor() {
+ if (textfield_->use_default_text_color()) {
+ // Passing NULL as the color undoes the effect of previous calls to
+ // gtk_widget_modify_text.
+ gtk_widget_modify_text(native_view(), GTK_STATE_NORMAL, NULL);
+ return;
+ }
+ GdkColor gdk_color = gfx::SkColorToGdkColor(textfield_->text_color());
+ gtk_widget_modify_text(native_view(), GTK_STATE_NORMAL, &gdk_color);
+}
+
+void NativeTextfieldGtk::UpdateBackgroundColor() {
+ if (textfield_->use_default_background_color()) {
+ // Passing NULL as the color undoes the effect of previous calls to
+ // gtk_widget_modify_base.
+ gtk_widget_modify_base(native_view(), GTK_STATE_NORMAL, NULL);
+ return;
+ }
+ GdkColor gdk_color = gfx::SkColorToGdkColor(textfield_->background_color());
+ gtk_widget_modify_base(native_view(), GTK_STATE_NORMAL, &gdk_color);
+}
+
+void NativeTextfieldGtk::UpdateReadOnly() {
+ if (!native_view())
+ return;
+ gtk_editable_set_editable(GTK_EDITABLE(native_view()),
+ !textfield_->read_only());
+}
+
+void NativeTextfieldGtk::UpdateFont() {
+ if (!native_view())
+ return;
+ PangoFontDescription* pfd = textfield_->font().GetNativeFont();
+ gtk_widget_modify_font(native_view(), pfd);
+ pango_font_description_free(pfd);
+}
+
+void NativeTextfieldGtk::UpdateIsPassword() {
+ if (!native_view())
+ return;
+ gtk_entry_set_visibility(GTK_ENTRY(native_view()), !textfield_->IsPassword());
+}
+
+void NativeTextfieldGtk::UpdateEnabled() {
+ if (!native_view())
+ return;
+ SetEnabled(textfield_->IsEnabled());
+}
+
+gfx::Insets NativeTextfieldGtk::CalculateInsets() {
+ if (!native_view())
+ return gfx::Insets();
+
+ GtkWidget* widget = native_view();
+ gfx::Insets insets;
+
+ GtkEntry* entry = GTK_ENTRY(widget);
+ insets += GetEntryInnerBorder(entry);
+ if (entry->has_frame) {
+ insets += gfx::Insets(widget->style->ythickness,
+ widget->style->xthickness,
+ widget->style->ythickness,
+ widget->style->xthickness);
+ }
+
+ gboolean interior_focus;
+ gint focus_width;
+ gtk_widget_style_get(widget,
+ "focus-line-width", &focus_width,
+ "interior-focus", &interior_focus,
+ NULL);
+ if (!interior_focus)
+ insets += gfx::Insets(focus_width, focus_width, focus_width, focus_width);
+
+ return insets;
+}
+
+void NativeTextfieldGtk::UpdateHorizontalMargins() {
+ if (!native_view())
+ return;
+
+ int left, right;
+ if (!textfield_->GetHorizontalMargins(&left, &right))
+ return;
+
+ gfx::Insets insets = GetEntryInnerBorder(GTK_ENTRY(native_view()));
+ GtkBorder border = {left, right, insets.top(), insets.bottom()};
+ gtk_entry_set_inner_border(GTK_ENTRY(native_view()), &border);
+}
+
+void NativeTextfieldGtk::UpdateVerticalMargins() {
+ if (!native_view())
+ return;
+
+ int top, bottom;
+ if (!textfield_->GetVerticalMargins(&top, &bottom))
+ return;
+
+ gfx::Insets insets = GetEntryInnerBorder(GTK_ENTRY(native_view()));
+ GtkBorder border = {insets.left(), insets.right(), top, bottom};
+ gtk_entry_set_inner_border(GTK_ENTRY(native_view()), &border);
+}
+
+bool NativeTextfieldGtk::SetFocus() {
+ OnFocus();
+ return true;
+}
+
+View* NativeTextfieldGtk::GetView() {
+ return this;
+}
+
+gfx::NativeView NativeTextfieldGtk::GetTestingHandle() const {
+ return native_view();
+}
+
+bool NativeTextfieldGtk::IsIMEComposing() const {
+ return false;
+}
+
+void NativeTextfieldGtk::GetSelectedRange(ui::Range* range) const {
+ gint start_pos;
+ gint end_pos;
+ gtk_editable_get_selection_bounds(
+ GTK_EDITABLE(native_view()), &start_pos, &end_pos);
+ *range = ui::Range(start_pos, end_pos);
+}
+
+void NativeTextfieldGtk::SelectRange(const ui::Range& range) {
+ NOTREACHED();
+}
+
+void NativeTextfieldGtk::GetSelectionModel(gfx::SelectionModel* sel) const {
+ NOTREACHED();
+}
+
+void NativeTextfieldGtk::SelectSelectionModel(const gfx::SelectionModel& sel) {
+ NOTREACHED();
+}
+
+size_t NativeTextfieldGtk::GetCursorPosition() const {
+ NOTREACHED();
+ return 0U;
+}
+
+bool NativeTextfieldGtk::HandleKeyPressed(const views::KeyEvent& e) {
+ return false;
+}
+
+bool NativeTextfieldGtk::HandleKeyReleased(const views::KeyEvent& e) {
+ return false;
+}
+
+void NativeTextfieldGtk::HandleFocus() {
+}
+
+void NativeTextfieldGtk::HandleBlur() {
+}
+
+ui::TextInputClient* NativeTextfieldGtk::GetTextInputClient() {
+ return NULL;
+}
+
+void NativeTextfieldGtk::ApplyStyleRange(const gfx::StyleRange& style) {
+ NOTREACHED();
+}
+
+void NativeTextfieldGtk::ApplyDefaultStyle() {
+ NOTREACHED();
+}
+
+void NativeTextfieldGtk::ClearEditHistory() {
+ NOTREACHED();
+}
+
+void NativeTextfieldGtk::OnActivate(GtkWidget* native_widget) {
+ GdkEvent* event = gtk_get_current_event();
+ if (!event || event->type != GDK_KEY_PRESS)
+ return;
+
+ KeyEvent views_key_event(event);
+ gboolean handled = false;
+
+ TextfieldController* controller = textfield_->GetController();
+ if (controller)
+ handled = controller->HandleKeyEvent(textfield_, views_key_event);
+
+ Widget* widget = GetWidget();
+ if (!handled && widget) {
+ NativeWidgetGtk* native_widget =
+ static_cast<NativeWidgetGtk*>(widget->native_widget());
+ handled = native_widget->HandleKeyboardEvent(views_key_event);
+ }
+
+ // Stop signal emission if the key event is handled by us.
+ if (handled) {
+ // Only GtkEntry has "activate" signal.
+ static guint signal_id = g_signal_lookup("activate", GTK_TYPE_ENTRY);
+ g_signal_stop_emission(native_widget, signal_id, 0);
+ }
+}
+
+void NativeTextfieldGtk::OnChanged(GObject* object) {
+ // We need to call TextfieldController::ContentsChanged() explicitly if the
+ // paste action didn't change the content at all. See http://crbug.com/79002
+ const bool call_contents_changed =
+ paste_clipboard_requested_ && GetText() == textfield_->text();
+ textfield_->SyncText();
+ textfield_->GetWidget()->NotifyAccessibilityEvent(
+ textfield_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true);
+ if (call_contents_changed) {
+ TextfieldController* controller = textfield_->GetController();
+ if (controller)
+ controller->ContentsChanged(textfield_, textfield_->text());
+ }
+ paste_clipboard_requested_ = false;
+}
+
+gboolean NativeTextfieldGtk::OnButtonPressEvent(GtkWidget* widget,
+ GdkEventButton* event) {
+ paste_clipboard_requested_ = false;
+ return false;
+}
+
+gboolean NativeTextfieldGtk::OnButtonReleaseEventAfter(GtkWidget* widget,
+ GdkEventButton* event) {
+ textfield_->GetWidget()->NotifyAccessibilityEvent(
+ textfield_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true);
+ return false;
+}
+
+gboolean NativeTextfieldGtk::OnKeyPressEvent(GtkWidget* widget,
+ GdkEventKey* event) {
+ paste_clipboard_requested_ = false;
+ return false;
+}
+
+gboolean NativeTextfieldGtk::OnKeyPressEventAfter(GtkWidget* widget,
+ GdkEventKey* event) {
+ TextfieldController* controller = textfield_->GetController();
+ if (controller) {
+ KeyEvent key_event(reinterpret_cast<GdkEvent*>(event));
+ return controller->HandleKeyEvent(textfield_, key_event);
+ }
+ return false;
+}
+
+void NativeTextfieldGtk::OnMoveCursor(GtkWidget* widget,
+ GtkMovementStep step,
+ gint count,
+ gboolean extend_selection) {
+ textfield_->GetWidget()->NotifyAccessibilityEvent(
+ textfield_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true);
+}
+
+void NativeTextfieldGtk::OnPasteClipboard(GtkWidget* widget) {
+ if (!textfield_->read_only())
+ paste_clipboard_requested_ = true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldGtk, NativeControlGtk overrides:
+
+void NativeTextfieldGtk::CreateNativeControl() {
+ NativeControlCreated(gtk_views_entry_new(this));
+ gtk_entry_set_invisible_char(GTK_ENTRY(native_view()),
+ static_cast<gunichar>(kPasswordChar));
+ textfield_->UpdateAllProperties();
+}
+
+void NativeTextfieldGtk::NativeControlCreated(GtkWidget* widget) {
+ NativeControlGtk::NativeControlCreated(widget);
+
+ g_signal_connect(widget, "changed", G_CALLBACK(OnChangedThunk), this);
+ // In order to properly trigger Accelerators bound to VKEY_RETURN, we need
+ // to send an event when the widget gets the activate signal.
+ g_signal_connect(widget, "activate", G_CALLBACK(OnActivateThunk), this);
+ g_signal_connect(widget, "move-cursor", G_CALLBACK(OnMoveCursorThunk), this);
+ g_signal_connect(widget, "button-press-event",
+ G_CALLBACK(OnButtonPressEventThunk), this);
+ g_signal_connect(widget, "key-press-event",
+ G_CALLBACK(OnKeyPressEventThunk), this);
+ g_signal_connect(widget, "paste-clipboard",
+ G_CALLBACK(OnPasteClipboardThunk), this);
+
+ g_signal_connect_after(widget, "button-release-event",
+ G_CALLBACK(OnButtonReleaseEventAfterThunk), this);
+ g_signal_connect_after(widget, "key-press-event",
+ G_CALLBACK(OnKeyPressEventAfterThunk), this);
+}
+
+bool NativeTextfieldGtk::IsPassword() {
+ return textfield_->IsPassword();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldWrapper:
+
+// static
+NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper(
+ Textfield* field) {
+ if (Widget::IsPureViews())
+ return new NativeTextfieldViews(field);
+ return new NativeTextfieldGtk(field);
+}
+
+} // namespace views
diff --git a/ui/views/controls/textfield/native_textfield_gtk.h b/ui/views/controls/textfield/native_textfield_gtk.h
new file mode 100644
index 0000000..ea3151c
--- /dev/null
+++ b/ui/views/controls/textfield/native_textfield_gtk.h
@@ -0,0 +1,105 @@
+// 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.
+
+#ifndef UI_VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_GTK_H_
+#define UI_VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_GTK_H_
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "base/string16.h"
+#include "ui/base/gtk/gtk_signal.h"
+#include "ui/views/controls/textfield/native_textfield_wrapper.h"
+#include "views/controls/native_control_gtk.h"
+
+namespace gfx {
+class SelectionModel;
+}
+
+namespace views {
+
+class NativeTextfieldGtk : public NativeControlGtk,
+ public NativeTextfieldWrapper {
+ public:
+ explicit NativeTextfieldGtk(Textfield* parent);
+ virtual ~NativeTextfieldGtk();
+
+ // Returns the textfield this NativeTextfieldGtk wraps.
+ Textfield* textfield() const { return textfield_; }
+
+ // Returns the inner border of the entry.
+ static gfx::Insets GetEntryInnerBorder(GtkEntry* entry);
+ static gfx::Insets GetTextViewInnerBorder(GtkTextView* text_view);
+
+ // Overridden from NativeTextfieldWrapper:
+ virtual string16 GetText() const OVERRIDE;
+ virtual void UpdateText() OVERRIDE;
+ virtual void AppendText(const string16& text) OVERRIDE;
+ virtual string16 GetSelectedText() const OVERRIDE;
+ virtual void SelectAll() OVERRIDE;
+ virtual void ClearSelection() OVERRIDE;
+ virtual void UpdateBorder() OVERRIDE;
+ virtual void UpdateTextColor() OVERRIDE;
+ virtual void UpdateBackgroundColor() OVERRIDE;
+ virtual void UpdateReadOnly() OVERRIDE;
+ virtual void UpdateFont() OVERRIDE;
+ virtual void UpdateIsPassword() OVERRIDE;
+ virtual void UpdateEnabled() OVERRIDE;
+ virtual gfx::Insets CalculateInsets() OVERRIDE;
+ virtual void UpdateHorizontalMargins() OVERRIDE;
+ virtual void UpdateVerticalMargins() OVERRIDE;
+ virtual bool SetFocus() OVERRIDE;
+ virtual View* GetView() OVERRIDE;
+ virtual gfx::NativeView GetTestingHandle() const OVERRIDE;
+ virtual bool IsIMEComposing() const OVERRIDE;
+ virtual void GetSelectedRange(ui::Range* range) const OVERRIDE;
+ virtual void SelectRange(const ui::Range& range) OVERRIDE;
+ virtual void GetSelectionModel(gfx::SelectionModel* sel) const OVERRIDE;
+ virtual void SelectSelectionModel(const gfx::SelectionModel& sel) OVERRIDE;
+ virtual size_t GetCursorPosition() const OVERRIDE;
+ virtual bool HandleKeyPressed(const views::KeyEvent& e) OVERRIDE;
+ virtual bool HandleKeyReleased(const views::KeyEvent& e) OVERRIDE;
+ virtual void HandleFocus() OVERRIDE;
+ virtual void HandleBlur() OVERRIDE;
+ virtual ui::TextInputClient* GetTextInputClient() OVERRIDE;
+ virtual void ApplyStyleRange(const gfx::StyleRange& style) OVERRIDE;
+ virtual void ApplyDefaultStyle() OVERRIDE;
+ virtual void ClearEditHistory() OVERRIDE;
+
+ // Overridden from NativeControlGtk:
+ virtual void CreateNativeControl() OVERRIDE;
+ virtual void NativeControlCreated(GtkWidget* widget) OVERRIDE;
+
+ // Returns true if the textfield is for password.
+ bool IsPassword();
+
+ private:
+ // Gtk signal callbacks.
+ CHROMEGTK_CALLBACK_0(NativeTextfieldGtk, void, OnActivate);
+ CHROMEGTK_CALLBACK_1(NativeTextfieldGtk, gboolean, OnButtonPressEvent,
+ GdkEventButton*);
+ CHROMEGTK_CALLBACK_1(NativeTextfieldGtk, gboolean, OnButtonReleaseEventAfter,
+ GdkEventButton*);
+ CHROMEG_CALLBACK_0(NativeTextfieldGtk, void, OnChanged, GObject*);
+ CHROMEGTK_CALLBACK_1(NativeTextfieldGtk, gboolean, OnKeyPressEvent,
+ GdkEventKey*);
+ CHROMEGTK_CALLBACK_1(NativeTextfieldGtk, gboolean, OnKeyPressEventAfter,
+ GdkEventKey*);
+ CHROMEGTK_CALLBACK_3(NativeTextfieldGtk, void, OnMoveCursor,
+ GtkMovementStep, gint, gboolean);
+ CHROMEGTK_CALLBACK_0(NativeTextfieldGtk, void, OnPasteClipboard);
+
+ Textfield* textfield_;
+
+ // Indicates that user requested to paste clipboard.
+ // The actual paste clipboard action might be performed later if the
+ // clipboard is not empty.
+ bool paste_clipboard_requested_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeTextfieldGtk);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_GTK_H_
diff --git a/ui/views/controls/textfield/native_textfield_views.cc b/ui/views/controls/textfield/native_textfield_views.cc
new file mode 100644
index 0000000..8cba8de
--- /dev/null
+++ b/ui/views/controls/textfield/native_textfield_views.cc
@@ -0,0 +1,1085 @@
+// 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.
+
+#include "ui/views/controls/textfield/native_textfield_views.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/utf_string_conversions.h"
+#include "grit/ui_strings.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/range/range.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/render_text.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/controls/textfield/textfield_views_model.h"
+#include "ui/views/events/event.h"
+#include "ui/views/ime/input_method.h"
+#include "ui/views/widget/widget.h"
+#include "views/background.h"
+#include "views/border.h"
+#include "views/controls/focusable_border.h"
+#include "views/controls/menu/menu_item_view.h"
+#include "views/controls/menu/menu_model_adapter.h"
+#include "views/controls/menu/menu_runner.h"
+#include "views/metrics.h"
+#include "views/views_delegate.h"
+
+#if defined(OS_LINUX)
+#include "ui/gfx/gtk_util.h"
+#endif
+
+#if defined(USE_AURA)
+#include "ui/aura/cursor.h"
+#endif
+
+namespace {
+
+// Text color for read only.
+const SkColor kReadonlyTextColor = SK_ColorDKGRAY;
+
+// Parameters to control cursor blinking.
+const int kCursorVisibleTimeMs = 800;
+const int kCursorInvisibleTimeMs = 500;
+
+} // namespace
+
+namespace views {
+
+const char NativeTextfieldViews::kViewClassName[] =
+ "views/NativeTextfieldViews";
+
+NativeTextfieldViews::NativeTextfieldViews(Textfield* parent)
+ : textfield_(parent),
+ ALLOW_THIS_IN_INITIALIZER_LIST(model_(new TextfieldViewsModel(this))),
+ text_border_(new FocusableBorder()),
+ is_cursor_visible_(false),
+ is_drop_cursor_visible_(false),
+ skip_input_method_cancel_composition_(false),
+ initiating_drag_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(cursor_timer_(this)),
+ aggregated_clicks_(0),
+ last_click_time_(),
+ last_click_location_(),
+ ALLOW_THIS_IN_INITIALIZER_LIST(touch_selection_controller_(
+ TouchSelectionController::create(this))) {
+ set_border(text_border_);
+
+ // Lowercase is not supported.
+ DCHECK_NE(parent->style(), Textfield::STYLE_LOWERCASE);
+
+ // Set the default text style.
+ gfx::StyleRange default_style;
+ default_style.font = textfield_->font();
+ default_style.foreground = textfield_->text_color();
+ GetRenderText()->set_default_style(default_style);
+ GetRenderText()->ApplyDefaultStyle();
+
+ set_context_menu_controller(this);
+ set_drag_controller(this);
+}
+
+NativeTextfieldViews::~NativeTextfieldViews() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldViews, View overrides:
+
+bool NativeTextfieldViews::OnMousePressed(const MouseEvent& event) {
+ OnBeforeUserAction();
+ TrackMouseClicks(event);
+
+ // Allow the textfield/omnibox to optionally handle the mouse pressed event.
+ // This should be removed once native textfield implementations are
+ // consolidated to textfield.
+ if (!textfield_->OnMousePressed(event))
+ HandleMousePressEvent(event);
+
+ OnAfterUserAction();
+ return true;
+}
+
+bool NativeTextfieldViews::OnMouseDragged(const MouseEvent& event) {
+ // Don't adjust the cursor on a potential drag and drop.
+ if (initiating_drag_)
+ return true;
+
+ OnBeforeUserAction();
+ if (MoveCursorTo(event.location(), true))
+ SchedulePaint();
+ OnAfterUserAction();
+ return true;
+}
+
+void NativeTextfieldViews::OnMouseReleased(const MouseEvent& event) {
+ OnBeforeUserAction();
+ // Cancel suspected drag initiations, the user was clicking in the selection.
+ if (initiating_drag_ && MoveCursorTo(event.location(), false))
+ SchedulePaint();
+ initiating_drag_ = false;
+ OnAfterUserAction();
+}
+
+bool NativeTextfieldViews::OnKeyPressed(const KeyEvent& event) {
+ // OnKeyPressed/OnKeyReleased/OnFocus/OnBlur will never be invoked on
+ // NativeTextfieldViews as it will never gain focus.
+ NOTREACHED();
+ return false;
+}
+
+bool NativeTextfieldViews::OnKeyReleased(const KeyEvent& event) {
+ NOTREACHED();
+ return false;
+}
+
+bool NativeTextfieldViews::GetDropFormats(
+ int* formats,
+ std::set<OSExchangeData::CustomFormat>* custom_formats) {
+ if (!textfield_->IsEnabled() || textfield_->read_only())
+ return false;
+ // TODO(msw): Can we support URL, FILENAME, etc.?
+ *formats = ui::OSExchangeData::STRING;
+ return true;
+}
+
+bool NativeTextfieldViews::CanDrop(const OSExchangeData& data) {
+ return textfield_->IsEnabled() && !textfield_->read_only() &&
+ data.HasString();
+}
+
+int NativeTextfieldViews::OnDragUpdated(const DropTargetEvent& event) {
+ DCHECK(CanDrop(event.data()));
+ bool in_selection = GetRenderText()->IsPointInSelection(event.location());
+ is_drop_cursor_visible_ = !in_selection;
+ // TODO(msw): Pan over text when the user drags to the visible text edge.
+ OnCaretBoundsChanged();
+ SchedulePaint();
+
+ if (initiating_drag_) {
+ if (in_selection)
+ return ui::DragDropTypes::DRAG_NONE;
+ return event.IsControlDown() ? ui::DragDropTypes::DRAG_COPY :
+ ui::DragDropTypes::DRAG_MOVE;
+ }
+ return ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE;
+}
+
+int NativeTextfieldViews::OnPerformDrop(const DropTargetEvent& event) {
+ DCHECK(CanDrop(event.data()));
+ DCHECK(!initiating_drag_ ||
+ !GetRenderText()->IsPointInSelection(event.location()));
+ OnBeforeUserAction();
+ skip_input_method_cancel_composition_ = true;
+
+ // TODO(msw): Remove final reference to FindCursorPosition.
+ gfx::SelectionModel drop_destination =
+ GetRenderText()->FindCursorPosition(event.location());
+ string16 text;
+ event.data().GetString(&text);
+
+ // We'll delete the current selection for a drag and drop within this view.
+ bool move = initiating_drag_ && !event.IsControlDown() &&
+ event.source_operations() & ui::DragDropTypes::DRAG_MOVE;
+ if (move) {
+ gfx::SelectionModel selected;
+ model_->GetSelectionModel(&selected);
+ // Adjust the drop destination if it is on or after the current selection.
+ size_t max_of_selected_range = std::max(selected.selection_start(),
+ selected.selection_end());
+ size_t min_of_selected_range = std::min(selected.selection_start(),
+ selected.selection_end());
+ size_t selected_range_length = max_of_selected_range -
+ min_of_selected_range;
+ size_t drop_destination_end = drop_destination.selection_end();
+ if (max_of_selected_range <= drop_destination_end)
+ drop_destination_end -= selected_range_length;
+ else if (min_of_selected_range <= drop_destination_end)
+ drop_destination_end = min_of_selected_range;
+ model_->DeleteSelectionAndInsertTextAt(text, drop_destination_end);
+ } else {
+ model_->MoveCursorTo(drop_destination);
+ // Drop always inserts text even if the textfield is not in insert mode.
+ model_->InsertText(text);
+ }
+ skip_input_method_cancel_composition_ = false;
+ UpdateAfterChange(true, true);
+ OnAfterUserAction();
+ return move ? ui::DragDropTypes::DRAG_MOVE : ui::DragDropTypes::DRAG_COPY;
+}
+
+void NativeTextfieldViews::OnDragDone() {
+ initiating_drag_ = false;
+ is_drop_cursor_visible_ = false;
+}
+
+void NativeTextfieldViews::OnPaint(gfx::Canvas* canvas) {
+ text_border_->set_has_focus(textfield_->HasFocus());
+ OnPaintBackground(canvas);
+ PaintTextAndCursor(canvas);
+ if (textfield_->draw_border())
+ OnPaintBorder(canvas);
+}
+
+void NativeTextfieldViews::OnFocus() {
+ NOTREACHED();
+}
+
+void NativeTextfieldViews::OnBlur() {
+ NOTREACHED();
+}
+
+void NativeTextfieldViews::SelectRect(const gfx::Point& start,
+ const gfx::Point& end) {
+ if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE)
+ return;
+
+ gfx::SelectionModel start_pos = GetRenderText()->FindCursorPosition(start);
+ gfx::SelectionModel end_pos = GetRenderText()->FindCursorPosition(end);
+
+ OnBeforeUserAction();
+ // Merge selection models of "start_pos" and "end_pos" so that
+ // selection start is the value from "start_pos", while selection end,
+ // caret position, and caret placement are values from "end_pos".
+ if (start_pos.selection_start() == end_pos.selection_end())
+ model_->SelectSelectionModel(end_pos);
+ else
+ model_->SelectRange(ui::Range(start_pos.selection_start(),
+ end_pos.selection_end()));
+
+ OnCaretBoundsChanged();
+ SchedulePaint();
+ OnAfterUserAction();
+}
+
+gfx::NativeCursor NativeTextfieldViews::GetCursor(const MouseEvent& event) {
+ bool in_selection = GetRenderText()->IsPointInSelection(event.location());
+ bool drag_event = event.type() == ui::ET_MOUSE_DRAGGED;
+ bool text_cursor = !initiating_drag_ && (drag_event || !in_selection);
+#if defined(USE_AURA)
+ return text_cursor ? aura::kCursorIBeam : aura::kCursorNull;
+#elif defined(OS_WIN)
+ static HCURSOR ibeam = LoadCursor(NULL, IDC_IBEAM);
+ static HCURSOR arrow = LoadCursor(NULL, IDC_ARROW);
+ return text_cursor ? ibeam : arrow;
+#else
+ return text_cursor ? gfx::GetCursor(GDK_XTERM) : NULL;
+#endif
+}
+
+/////////////////////////////////////////////////////////////////
+// NativeTextfieldViews, ContextMenuController overrides:
+void NativeTextfieldViews::ShowContextMenuForView(View* source,
+ const gfx::Point& p,
+ bool is_mouse_gesture) {
+ UpdateContextMenu();
+ if (context_menu_runner_->RunMenuAt(
+ GetWidget(), NULL, gfx::Rect(p, gfx::Size()),
+ views::MenuItemView::TOPLEFT, MenuRunner::HAS_MNEMONICS) ==
+ MenuRunner::MENU_DELETED)
+ return;
+}
+
+/////////////////////////////////////////////////////////////////
+// NativeTextfieldViews, views::DragController overrides:
+void NativeTextfieldViews::WriteDragDataForView(views::View* sender,
+ const gfx::Point& press_pt,
+ OSExchangeData* data) {
+ DCHECK_NE(ui::DragDropTypes::DRAG_NONE,
+ GetDragOperationsForView(sender, press_pt));
+ data->SetString(GetSelectedText());
+}
+
+int NativeTextfieldViews::GetDragOperationsForView(views::View* sender,
+ const gfx::Point& p) {
+ if (!textfield_->IsEnabled() || !GetRenderText()->IsPointInSelection(p))
+ return ui::DragDropTypes::DRAG_NONE;
+ if (sender == this && !textfield_->read_only())
+ return ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_COPY;
+ return ui::DragDropTypes::DRAG_COPY;
+}
+
+bool NativeTextfieldViews::CanStartDragForView(View* sender,
+ const gfx::Point& press_pt,
+ const gfx::Point& p) {
+ return GetRenderText()->IsPointInSelection(press_pt);
+}
+
+/////////////////////////////////////////////////////////////////
+// NativeTextfieldViews, NativeTextifieldWrapper overrides:
+
+string16 NativeTextfieldViews::GetText() const {
+ return model_->GetText();
+}
+
+void NativeTextfieldViews::UpdateText() {
+ model_->SetText(textfield_->text());
+ OnCaretBoundsChanged();
+ SchedulePaint();
+}
+
+void NativeTextfieldViews::AppendText(const string16& text) {
+ if (text.empty())
+ return;
+ model_->Append(text);
+ OnCaretBoundsChanged();
+ SchedulePaint();
+}
+
+string16 NativeTextfieldViews::GetSelectedText() const {
+ return model_->GetSelectedText();
+}
+
+void NativeTextfieldViews::SelectAll() {
+ OnBeforeUserAction();
+ model_->SelectAll();
+ OnCaretBoundsChanged();
+ SchedulePaint();
+ OnAfterUserAction();
+}
+
+void NativeTextfieldViews::ClearSelection() {
+ OnBeforeUserAction();
+ model_->ClearSelection();
+ OnCaretBoundsChanged();
+ SchedulePaint();
+ OnAfterUserAction();
+}
+
+void NativeTextfieldViews::UpdateBorder() {
+ if (textfield_->draw_border()) {
+ gfx::Insets insets = GetInsets();
+ textfield_->SetHorizontalMargins(insets.left(), insets.right());
+ textfield_->SetVerticalMargins(insets.top(), insets.bottom());
+ } else {
+ textfield_->SetHorizontalMargins(0, 0);
+ textfield_->SetVerticalMargins(0, 0);
+ }
+}
+
+void NativeTextfieldViews::UpdateTextColor() {
+ SchedulePaint();
+}
+
+void NativeTextfieldViews::UpdateBackgroundColor() {
+ // TODO(oshima): Background has to match the border's shape.
+ set_background(
+ Background::CreateSolidBackground(textfield_->background_color()));
+ SchedulePaint();
+}
+
+void NativeTextfieldViews::UpdateReadOnly() {
+ // Update the default text style.
+ gfx::StyleRange default_style(GetRenderText()->default_style());
+ default_style.foreground = textfield_->read_only() ? kReadonlyTextColor :
+ textfield_->text_color();
+ GetRenderText()->set_default_style(default_style);
+ GetRenderText()->ApplyDefaultStyle();
+
+ SchedulePaint();
+ OnTextInputTypeChanged();
+}
+
+void NativeTextfieldViews::UpdateFont() {
+ // Update the default text style.
+ gfx::StyleRange default_style(GetRenderText()->default_style());
+ default_style.font = textfield_->font();
+ GetRenderText()->set_default_style(default_style);
+ GetRenderText()->ApplyDefaultStyle();
+
+ OnCaretBoundsChanged();
+}
+
+void NativeTextfieldViews::UpdateIsPassword() {
+ model_->set_is_password(textfield_->IsPassword());
+ OnCaretBoundsChanged();
+ SchedulePaint();
+ OnTextInputTypeChanged();
+}
+
+void NativeTextfieldViews::UpdateEnabled() {
+ SetEnabled(textfield_->IsEnabled());
+ SchedulePaint();
+ OnTextInputTypeChanged();
+}
+
+gfx::Insets NativeTextfieldViews::CalculateInsets() {
+ return GetInsets();
+}
+
+void NativeTextfieldViews::UpdateHorizontalMargins() {
+ int left, right;
+ if (!textfield_->GetHorizontalMargins(&left, &right))
+ return;
+ gfx::Insets inset = GetInsets();
+
+ text_border_->SetInsets(inset.top(), left, inset.bottom(), right);
+ OnCaretBoundsChanged();
+}
+
+void NativeTextfieldViews::UpdateVerticalMargins() {
+ int top, bottom;
+ if (!textfield_->GetVerticalMargins(&top, &bottom))
+ return;
+ gfx::Insets inset = GetInsets();
+ text_border_->SetInsets(top, inset.left(), bottom, inset.right());
+ OnCaretBoundsChanged();
+}
+
+bool NativeTextfieldViews::SetFocus() {
+ return false;
+}
+
+View* NativeTextfieldViews::GetView() {
+ return this;
+}
+
+gfx::NativeView NativeTextfieldViews::GetTestingHandle() const {
+ NOTREACHED();
+ return NULL;
+}
+
+bool NativeTextfieldViews::IsIMEComposing() const {
+ return model_->HasCompositionText();
+}
+
+void NativeTextfieldViews::GetSelectedRange(ui::Range* range) const {
+ model_->GetSelectedRange(range);
+}
+
+void NativeTextfieldViews::SelectRange(const ui::Range& range) {
+ model_->SelectRange(range);
+ OnCaretBoundsChanged();
+ SchedulePaint();
+}
+
+void NativeTextfieldViews::GetSelectionModel(gfx::SelectionModel* sel) const {
+ model_->GetSelectionModel(sel);
+}
+
+void NativeTextfieldViews::SelectSelectionModel(
+ const gfx::SelectionModel& sel) {
+ model_->SelectSelectionModel(sel);
+ OnCaretBoundsChanged();
+ SchedulePaint();
+}
+
+size_t NativeTextfieldViews::GetCursorPosition() const {
+ return model_->GetCursorPosition();
+}
+
+bool NativeTextfieldViews::HandleKeyPressed(const KeyEvent& e) {
+ TextfieldController* controller = textfield_->GetController();
+ bool handled = false;
+ if (controller)
+ handled = controller->HandleKeyEvent(textfield_, e);
+ return handled || HandleKeyEvent(e);
+}
+
+bool NativeTextfieldViews::HandleKeyReleased(const KeyEvent& e) {
+ return true;
+}
+
+void NativeTextfieldViews::HandleFocus() {
+ GetRenderText()->set_focused(true);
+ is_cursor_visible_ = true;
+ SchedulePaint();
+ OnCaretBoundsChanged();
+ // Start blinking cursor.
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&NativeTextfieldViews::UpdateCursor,
+ cursor_timer_.GetWeakPtr()),
+ kCursorVisibleTimeMs);
+}
+
+void NativeTextfieldViews::HandleBlur() {
+ GetRenderText()->set_focused(false);
+ // Stop blinking cursor.
+ cursor_timer_.InvalidateWeakPtrs();
+ if (is_cursor_visible_) {
+ is_cursor_visible_ = false;
+ RepaintCursor();
+ }
+
+ if (touch_selection_controller_.get())
+ touch_selection_controller_->ClientViewLostFocus();
+}
+
+ui::TextInputClient* NativeTextfieldViews::GetTextInputClient() {
+ return textfield_->read_only() ? NULL : this;
+}
+
+void NativeTextfieldViews::ClearEditHistory() {
+ model_->ClearEditHistory();
+}
+
+/////////////////////////////////////////////////////////////////
+// NativeTextfieldViews, ui::SimpleMenuModel::Delegate overrides:
+
+bool NativeTextfieldViews::IsCommandIdChecked(int command_id) const {
+ return true;
+}
+
+bool NativeTextfieldViews::IsCommandIdEnabled(int command_id) const {
+ bool editable = !textfield_->read_only();
+ string16 result;
+ switch (command_id) {
+ case IDS_APP_CUT:
+ return editable && model_->HasSelection();
+ case IDS_APP_COPY:
+ return model_->HasSelection();
+ case IDS_APP_PASTE:
+ ViewsDelegate::views_delegate->GetClipboard()
+ ->ReadText(ui::Clipboard::BUFFER_STANDARD, &result);
+ return editable && !result.empty();
+ case IDS_APP_DELETE:
+ return editable && model_->HasSelection();
+ case IDS_APP_SELECT_ALL:
+ return true;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+bool NativeTextfieldViews::GetAcceleratorForCommandId(int command_id,
+ ui::Accelerator* accelerator) {
+ return false;
+}
+
+void NativeTextfieldViews::ExecuteCommand(int command_id) {
+ bool text_changed = false;
+ bool editable = !textfield_->read_only();
+ OnBeforeUserAction();
+ switch (command_id) {
+ case IDS_APP_CUT:
+ if (editable)
+ text_changed = model_->Cut();
+ break;
+ case IDS_APP_COPY:
+ model_->Copy();
+ break;
+ case IDS_APP_PASTE:
+ if (editable)
+ text_changed = Paste();
+ break;
+ case IDS_APP_DELETE:
+ if (editable)
+ text_changed = model_->Delete();
+ break;
+ case IDS_APP_SELECT_ALL:
+ SelectAll();
+ break;
+ default:
+ NOTREACHED() << "unknown command: " << command_id;
+ break;
+ }
+
+ // The cursor must have changed if text changed during cut/paste/delete.
+ UpdateAfterChange(text_changed, text_changed);
+ OnAfterUserAction();
+}
+
+void NativeTextfieldViews::ApplyStyleRange(const gfx::StyleRange& style) {
+ GetRenderText()->ApplyStyleRange(style);
+ SchedulePaint();
+}
+
+void NativeTextfieldViews::ApplyDefaultStyle() {
+ GetRenderText()->ApplyDefaultStyle();
+ SchedulePaint();
+}
+
+void NativeTextfieldViews::OnBoundsChanged(const gfx::Rect& previous_bounds) {
+ // Set the RenderText display area.
+ gfx::Insets insets = GetInsets();
+ gfx::Rect display_rect(insets.left(),
+ insets.top(),
+ width() - insets.width(),
+ height() - insets.height());
+ GetRenderText()->SetDisplayRect(display_rect);
+ OnCaretBoundsChanged();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldViews, ui::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_->CancelCompositionText();
+ 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 (GetRenderText()->insert_mode())
+ 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 (GetRenderText()->insert_mode())
+ model_->InsertChar(ch);
+ else
+ model_->ReplaceChar(ch);
+ skip_input_method_cancel_composition_ = false;
+ UpdateAfterChange(true, true);
+ OnAfterUserAction();
+}
+
+ui::TextInputType NativeTextfieldViews::GetTextInputType() const {
+ return textfield_->GetTextInputType();
+}
+
+gfx::Rect NativeTextfieldViews::GetCaretBounds() {
+ return GetRenderText()->GetUpdatedCursorBounds();
+}
+
+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;
+
+ gfx::SelectionModel sel;
+ model_->GetSelectionModel(&sel);
+ range->set_start(sel.selection_start());
+ range->set_end(sel.selection_end());
+ 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,
+ string16* text) {
+ if (GetTextInputType() != ui::TEXT_INPUT_TYPE_TEXT || !range.IsValid())
+ return false;
+
+ ui::Range text_range;
+ if (!GetTextRange(&text_range) || !text_range.Contains(range))
+ return false;
+
+ *text = model_->GetTextFromRange(range);
+ return true;
+}
+
+void NativeTextfieldViews::OnInputMethodChanged() {
+ NOTIMPLEMENTED();
+}
+
+bool NativeTextfieldViews::ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection direction) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+void NativeTextfieldViews::OnCompositionTextConfirmedOrCleared() {
+ if (skip_input_method_cancel_composition_)
+ return;
+ DCHECK(textfield_->GetInputMethod());
+ textfield_->GetInputMethod()->CancelComposition(textfield_);
+}
+
+gfx::RenderText* NativeTextfieldViews::GetRenderText() const {
+ return model_->render_text();
+}
+
+void NativeTextfieldViews::UpdateCursor() {
+ is_cursor_visible_ = !is_cursor_visible_;
+ RepaintCursor();
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&NativeTextfieldViews::UpdateCursor,
+ cursor_timer_.GetWeakPtr()),
+ is_cursor_visible_ ? kCursorVisibleTimeMs : kCursorInvisibleTimeMs);
+}
+
+void NativeTextfieldViews::RepaintCursor() {
+ gfx::Rect r(GetCaretBounds());
+ r.Inset(-1, -1, -1, -1);
+ SchedulePaintInRect(r);
+}
+
+void NativeTextfieldViews::PaintTextAndCursor(gfx::Canvas* canvas) {
+ canvas->Save();
+ GetRenderText()->set_cursor_visible(is_drop_cursor_visible_ ||
+ (is_cursor_visible_ && !model_->HasSelection()));
+ // Draw the text, cursor, and selection.
+ GetRenderText()->Draw(canvas);
+ canvas->Restore();
+}
+
+bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) {
+ // TODO(oshima): Refactor and consolidate with ExecuteCommand.
+ if (key_event.type() == ui::ET_KEY_PRESSED) {
+ ui::KeyboardCode key_code = key_event.key_code();
+ // TODO(oshima): shift-tab does not work. Figure out why and fix.
+ if (key_code == ui::VKEY_TAB)
+ return false;
+
+ OnBeforeUserAction();
+ bool editable = !textfield_->read_only();
+ bool selection = key_event.IsShiftDown();
+ bool control = key_event.IsControlDown();
+ bool text_changed = false;
+ bool cursor_changed = false;
+ switch (key_code) {
+ case ui::VKEY_Z:
+ if (control && editable)
+ cursor_changed = text_changed = model_->Undo();
+ break;
+ case ui::VKEY_Y:
+ if (control && editable)
+ cursor_changed = text_changed = model_->Redo();
+ break;
+ case ui::VKEY_A:
+ if (control) {
+ model_->SelectAll();
+ cursor_changed = true;
+ }
+ break;
+ case ui::VKEY_X:
+ if (control && editable)
+ cursor_changed = text_changed = model_->Cut();
+ break;
+ case ui::VKEY_C:
+ if (control)
+ model_->Copy();
+ break;
+ case ui::VKEY_V:
+ if (control && editable)
+ cursor_changed = text_changed = Paste();
+ break;
+ case ui::VKEY_RIGHT:
+ model_->MoveCursorRight(
+ control ? gfx::WORD_BREAK : gfx::CHARACTER_BREAK, selection);
+ cursor_changed = true;
+ break;
+ case ui::VKEY_LEFT:
+ model_->MoveCursorLeft(
+ control ? gfx::WORD_BREAK : gfx::CHARACTER_BREAK, selection);
+ cursor_changed = true;
+ break;
+ case ui::VKEY_END:
+ case ui::VKEY_HOME:
+ if ((key_code == ui::VKEY_HOME) ==
+ (GetRenderText()->GetTextDirection() == base::i18n::RIGHT_TO_LEFT))
+ model_->MoveCursorRight(gfx::LINE_BREAK, selection);
+ else
+ model_->MoveCursorLeft(gfx::LINE_BREAK, selection);
+ cursor_changed = true;
+ break;
+ case ui::VKEY_BACK:
+ if (!editable)
+ break;
+ if (!model_->HasSelection()) {
+ if (selection && control) {
+ // If both shift and control are pressed, then erase upto the
+ // beginning of the buffer in ChromeOS. In windows, do nothing.
+#if defined(OS_WIN)
+ break;
+#else
+ model_->MoveCursorLeft(gfx::LINE_BREAK, true);
+#endif
+ } else if (control) {
+ // If only control is pressed, then erase the previous word.
+ model_->MoveCursorLeft(gfx::WORD_BREAK, true);
+ }
+ }
+ text_changed = model_->Backspace();
+ cursor_changed = true;
+ break;
+ case ui::VKEY_DELETE:
+ if (!editable)
+ break;
+ if (!model_->HasSelection()) {
+ if (selection && control) {
+ // If both shift and control are pressed, then erase upto the
+ // end of the buffer in ChromeOS. In windows, do nothing.
+#if defined(OS_WIN)
+ break;
+#else
+ model_->MoveCursorRight(gfx::LINE_BREAK, true);
+#endif
+ } else if (control) {
+ // If only control is pressed, then erase the next word.
+ model_->MoveCursorRight(gfx::WORD_BREAK, true);
+ }
+ }
+ cursor_changed = text_changed = model_->Delete();
+ break;
+ case ui::VKEY_INSERT:
+ GetRenderText()->ToggleInsertMode();
+ cursor_changed = true;
+ break;
+ default:
+ break;
+ }
+
+ // We must have input method in order to support text input.
+ DCHECK(textfield_->GetInputMethod());
+
+ UpdateAfterChange(text_changed, cursor_changed);
+ OnAfterUserAction();
+ return (text_changed || cursor_changed);
+ }
+ return false;
+}
+
+bool NativeTextfieldViews::MoveCursorTo(const gfx::Point& point, bool select) {
+ if (!model_->MoveCursorTo(point, select))
+ return false;
+ OnCaretBoundsChanged();
+ return true;
+}
+
+void NativeTextfieldViews::PropagateTextChange() {
+ textfield_->SyncText();
+}
+
+void NativeTextfieldViews::UpdateAfterChange(bool text_changed,
+ bool cursor_changed) {
+ if (text_changed)
+ PropagateTextChange();
+ if (cursor_changed) {
+ is_cursor_visible_ = true;
+ RepaintCursor();
+ }
+ if (text_changed || cursor_changed) {
+ OnCaretBoundsChanged();
+ SchedulePaint();
+ }
+}
+
+void NativeTextfieldViews::UpdateContextMenu() {
+ if (!context_menu_contents_.get()) {
+ context_menu_contents_.reset(new ui::SimpleMenuModel(this));
+ context_menu_contents_->AddItemWithStringId(IDS_APP_CUT, IDS_APP_CUT);
+ context_menu_contents_->AddItemWithStringId(IDS_APP_COPY, IDS_APP_COPY);
+ context_menu_contents_->AddItemWithStringId(IDS_APP_PASTE, IDS_APP_PASTE);
+ context_menu_contents_->AddItemWithStringId(IDS_APP_DELETE, IDS_APP_DELETE);
+ context_menu_contents_->AddSeparator();
+ context_menu_contents_->AddItemWithStringId(IDS_APP_SELECT_ALL,
+ IDS_APP_SELECT_ALL);
+
+ context_menu_delegate_.reset(
+ new views::MenuModelAdapter(context_menu_contents_.get()));
+ context_menu_runner_.reset(
+ new MenuRunner(new views::MenuItemView(context_menu_delegate_.get())));
+ }
+
+ context_menu_delegate_->BuildMenu(context_menu_runner_->GetMenu());
+}
+
+void NativeTextfieldViews::OnTextInputTypeChanged() {
+ // TODO(suzhe): changed from DCHECK. See http://crbug.com/81320.
+ if (textfield_->GetInputMethod())
+ textfield_->GetInputMethod()->OnTextInputTypeChanged(textfield_);
+}
+
+void NativeTextfieldViews::OnCaretBoundsChanged() {
+ // TODO(suzhe): changed from DCHECK. See http://crbug.com/81320.
+ if (textfield_->GetInputMethod())
+ textfield_->GetInputMethod()->OnCaretBoundsChanged(textfield_);
+
+ // Notify selection controller
+ if (!touch_selection_controller_.get())
+ return;
+ gfx::RenderText* render_text = GetRenderText();
+ const gfx::SelectionModel& sel = render_text->selection_model();
+ gfx::SelectionModel start_sel =
+ render_text->GetSelectionModelForSelectionStart();
+ gfx::Rect start_cursor = render_text->GetCursorBounds(start_sel, true);
+ gfx::Rect end_cursor = render_text->GetCursorBounds(sel, true);
+ gfx::Point start(start_cursor.x(), start_cursor.bottom() - 1);
+ gfx::Point end(end_cursor.x(), end_cursor.bottom() - 1);
+ touch_selection_controller_->SelectionChanged(start, end);
+}
+
+void NativeTextfieldViews::OnBeforeUserAction() {
+ TextfieldController* controller = textfield_->GetController();
+ if (controller)
+ controller->OnBeforeUserAction(textfield_);
+}
+
+void NativeTextfieldViews::OnAfterUserAction() {
+ TextfieldController* controller = textfield_->GetController();
+ if (controller)
+ controller->OnAfterUserAction(textfield_);
+}
+
+bool NativeTextfieldViews::Paste() {
+ const bool success = model_->Paste();
+
+ // Calls TextfieldController::ContentsChanged() explicitly if the paste action
+ // did not change the content at all. See http://crbug.com/79002
+ if (success && GetText() == textfield_->text()) {
+ TextfieldController* controller = textfield_->GetController();
+ if (controller)
+ controller->ContentsChanged(textfield_, textfield_->text());
+ }
+ return success;
+}
+
+void NativeTextfieldViews::TrackMouseClicks(const MouseEvent& event) {
+ if (event.IsOnlyLeftMouseButton()) {
+ base::TimeDelta time_delta = event.time_stamp() - last_click_time_;
+ gfx::Point location_delta = event.location().Subtract(last_click_location_);
+ if (time_delta.InMilliseconds() <= GetDoubleClickInterval() &&
+ !ExceededDragThreshold(location_delta.x(), location_delta.y())) {
+ aggregated_clicks_ = (aggregated_clicks_ + 1) % 3;
+ } else {
+ aggregated_clicks_ = 0;
+ }
+ last_click_time_ = event.time_stamp();
+ last_click_location_ = event.location();
+ }
+}
+
+void NativeTextfieldViews::HandleMousePressEvent(const MouseEvent& event) {
+ if (event.IsOnlyLeftMouseButton()) {
+ textfield_->RequestFocus();
+
+ initiating_drag_ = false;
+ bool can_drag = true;
+
+ switch(aggregated_clicks_) {
+ case 0:
+ if (can_drag && GetRenderText()->IsPointInSelection(event.location()))
+ initiating_drag_ = true;
+ else
+ MoveCursorTo(event.location(), event.IsShiftDown());
+ break;
+ case 1:
+ model_->SelectWord();
+ OnCaretBoundsChanged();
+ break;
+ case 2:
+ model_->SelectAll();
+ OnCaretBoundsChanged();
+ break;
+ default:
+ NOTREACHED();
+ }
+ SchedulePaint();
+ }
+}
+
+// static
+bool NativeTextfieldViews::ShouldInsertChar(char16 ch, int flags) {
+ // Filter out all control characters, including tab and new line characters,
+ // and all characters with Alt modifier. But we need to allow characters with
+ // AltGr modifier.
+ // On Windows AltGr is represented by Alt+Ctrl, and on Linux it's a different
+ // flag that we don't care about.
+ return ((ch >= 0x20 && ch < 0x7F) || ch > 0x9F) &&
+ (flags & ~(ui::EF_SHIFT_DOWN | ui::EF_CAPS_LOCK_DOWN)) != ui::EF_ALT_DOWN;
+}
+
+#if defined(USE_AURA)
+// static
+NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper(
+ Textfield* field) {
+ return new NativeTextfieldViews(field);
+}
+#endif
+
+} // namespace views
diff --git a/ui/views/controls/textfield/native_textfield_views.h b/ui/views/controls/textfield/native_textfield_views.h
new file mode 100644
index 0000000..2cae9f4
--- /dev/null
+++ b/ui/views/controls/textfield/native_textfield_views.h
@@ -0,0 +1,267 @@
+// 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.
+
+#ifndef UI_VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_VIEWS_H_
+#define UI_VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_VIEWS_H_
+#pragma once
+
+#include "base/memory/weak_ptr.h"
+#include "base/string16.h"
+#include "ui/base/ime/text_input_client.h"
+#include "ui/base/models/simple_menu_model.h"
+#include "ui/gfx/font.h"
+#include "ui/views/controls/textfield/native_textfield_wrapper.h"
+#include "ui/views/controls/textfield/textfield_views_model.h"
+#include "ui/views/touchui/touch_selection_controller.h"
+#include "views/border.h"
+#include "views/context_menu_controller.h"
+#include "views/drag_controller.h"
+#include "views/view.h"
+
+namespace base {
+class Time;
+}
+
+namespace gfx {
+class Canvas;
+}
+
+namespace views {
+
+class FocusableBorder;
+class KeyEvent;
+class MenuModelAdapter;
+class MenuRunner;
+
+// A views/skia only implementation of NativeTextfieldWrapper.
+// No platform specific code is used.
+// Following features are not yet supported.
+// * BIDI/Complex script.
+// * Support surrogate pair, or maybe we should just use UTF32 internally.
+// * X selection (only if we want to support).
+// Once completed, this will replace Textfield, NativeTextfieldWin and
+// NativeTextfieldGtk.
+class VIEWS_EXPORT NativeTextfieldViews : public TouchSelectionClientView,
+ public ContextMenuController,
+ public DragController,
+ public NativeTextfieldWrapper,
+ public ui::TextInputClient,
+ public TextfieldViewsModel::Delegate {
+ public:
+ explicit NativeTextfieldViews(Textfield* parent);
+ virtual ~NativeTextfieldViews();
+
+ // View overrides:
+ virtual gfx::NativeCursor GetCursor(const MouseEvent& event) OVERRIDE;
+ virtual bool OnMousePressed(const MouseEvent& event) OVERRIDE;
+ virtual bool OnMouseDragged(const MouseEvent& event) OVERRIDE;
+ virtual void OnMouseReleased(const MouseEvent& event) OVERRIDE;
+ virtual bool OnKeyPressed(const KeyEvent& event) OVERRIDE;
+ virtual bool GetDropFormats(
+ int* formats,
+ std::set<OSExchangeData::CustomFormat>* custom_formats) OVERRIDE;
+ virtual bool CanDrop(const OSExchangeData& data) OVERRIDE;
+ virtual int OnDragUpdated(const DropTargetEvent& event) OVERRIDE;
+ virtual int OnPerformDrop(const DropTargetEvent& event) OVERRIDE;
+ virtual void OnDragDone() OVERRIDE;
+ virtual bool OnKeyReleased(const KeyEvent& event) OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual void OnFocus() OVERRIDE;
+ virtual void OnBlur() OVERRIDE;
+
+ // TouchSelectionClientView overrides:
+ virtual void SelectRect(const gfx::Point& start,
+ const gfx::Point& end) OVERRIDE;
+
+ // ContextMenuController overrides:
+ virtual void ShowContextMenuForView(View* source,
+ const gfx::Point& p,
+ bool is_mouse_gesture) OVERRIDE;
+
+ // Overridden from DragController:
+ virtual void WriteDragDataForView(View* sender,
+ const gfx::Point& press_pt,
+ OSExchangeData* data) OVERRIDE;
+ virtual int GetDragOperationsForView(View* sender,
+ const gfx::Point& p) OVERRIDE;
+ virtual bool CanStartDragForView(View* sender,
+ const gfx::Point& press_pt,
+ const gfx::Point& p) OVERRIDE;
+
+ // NativeTextfieldWrapper overrides:
+ virtual string16 GetText() const OVERRIDE;
+ virtual void UpdateText() OVERRIDE;
+ virtual void AppendText(const string16& text) OVERRIDE;
+ virtual string16 GetSelectedText() const OVERRIDE;
+ virtual void SelectAll() OVERRIDE;
+ virtual void ClearSelection() OVERRIDE;
+ virtual void UpdateBorder() OVERRIDE;
+ virtual void UpdateTextColor() OVERRIDE;
+ virtual void UpdateBackgroundColor() OVERRIDE;
+ virtual void UpdateReadOnly() OVERRIDE;
+ virtual void UpdateFont() OVERRIDE;
+ virtual void UpdateIsPassword() OVERRIDE;
+ virtual void UpdateEnabled() OVERRIDE;
+ virtual gfx::Insets CalculateInsets() OVERRIDE;
+ virtual void UpdateHorizontalMargins() OVERRIDE;
+ virtual void UpdateVerticalMargins() OVERRIDE;
+ virtual bool SetFocus() OVERRIDE;
+ virtual View* GetView() OVERRIDE;
+ virtual gfx::NativeView GetTestingHandle() const OVERRIDE;
+ virtual bool IsIMEComposing() const OVERRIDE;
+ virtual void GetSelectedRange(ui::Range* range) const OVERRIDE;
+ virtual void SelectRange(const ui::Range& range) OVERRIDE;
+ virtual void GetSelectionModel(gfx::SelectionModel* sel) const OVERRIDE;
+ virtual void SelectSelectionModel(const gfx::SelectionModel& sel) OVERRIDE;
+ virtual size_t GetCursorPosition() const OVERRIDE;
+ virtual bool HandleKeyPressed(const KeyEvent& e) OVERRIDE;
+ virtual bool HandleKeyReleased(const KeyEvent& e) OVERRIDE;
+ virtual void HandleFocus() OVERRIDE;
+ virtual void HandleBlur() OVERRIDE;
+ virtual ui::TextInputClient* GetTextInputClient() OVERRIDE;
+ virtual void ApplyStyleRange(const gfx::StyleRange& style) OVERRIDE;
+ virtual void ApplyDefaultStyle() OVERRIDE;
+ virtual void ClearEditHistory() OVERRIDE;
+
+ // ui::SimpleMenuModel::Delegate overrides
+ virtual bool IsCommandIdChecked(int command_id) const OVERRIDE;
+ virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE;
+ virtual bool GetAcceleratorForCommandId(
+ int command_id,
+ ui::Accelerator* accelerator) OVERRIDE;
+ virtual void ExecuteCommand(int command_id) OVERRIDE;
+
+ // class name of internal
+ static const char kViewClassName[];
+
+ protected:
+ // View override.
+ virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE;
+
+ private:
+ friend class NativeTextfieldViewsTest;
+ friend class TouchSelectionControllerImplTest;
+
+ // Overridden from ui::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() const 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,
+ string16* text) OVERRIDE;
+ virtual void OnInputMethodChanged() OVERRIDE;
+ virtual bool ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection direction) OVERRIDE;
+
+ // Overridden from TextfieldViewsModel::Delegate:
+ virtual void OnCompositionTextConfirmedOrCleared() OVERRIDE;
+
+ // Returns the TextfieldViewsModel's text/cursor/selection rendering model.
+ gfx::RenderText* GetRenderText() const;
+
+ // A callback function to periodically update the cursor state.
+ void UpdateCursor();
+
+ // Repaint the cursor.
+ void RepaintCursor();
+
+ // Update the cursor_bounds and text_offset.
+ void UpdateCursorBoundsAndTextOffset(size_t cursor_pos, bool insert_mode);
+
+ void PaintTextAndCursor(gfx::Canvas* canvas);
+
+ // Handle the keyevent.
+ bool HandleKeyEvent(const KeyEvent& key_event);
+
+ // Helper function to call MoveCursorTo on the TextfieldViewsModel.
+ bool MoveCursorTo(const gfx::Point& point, bool select);
+
+ // Utility function to inform the parent textfield (and its controller if any)
+ // that the text in the textfield has changed.
+ void PropagateTextChange();
+
+ // Does necessary updates when the text and/or the position of the cursor
+ // changed.
+ void UpdateAfterChange(bool text_changed, bool cursor_changed);
+
+ // Utility function to prepare the context menu..
+ void UpdateContextMenu();
+
+ // Convenience method to call InputMethod::OnTextInputTypeChanged();
+ void OnTextInputTypeChanged();
+
+ // Convenience method to call InputMethod::OnCaretBoundsChanged();
+ void OnCaretBoundsChanged();
+
+ // Convenience method to call TextfieldController::OnBeforeUserAction();
+ void OnBeforeUserAction();
+
+ // Convenience method to call TextfieldController::OnAfterUserAction();
+ void OnAfterUserAction();
+
+ // Calls |model_->Paste()| and calls TextfieldController::ContentsChanged()
+ // explicitly if paste succeeded.
+ bool Paste();
+
+ // Tracks the mouse clicks for single/double/triple clicks.
+ void TrackMouseClicks(const MouseEvent& event);
+
+ // Handles mouse press events.
+ void HandleMousePressEvent(const MouseEvent& event);
+
+ // Checks if a char is ok to be inserted into the textfield. The |ch| is a
+ // modified character, i.e., modifiers took effect when generating this char.
+ static bool ShouldInsertChar(char16 ch, int flags);
+
+ // The parent textfield, the owner of this object.
+ Textfield* textfield_;
+
+ // The text model.
+ scoped_ptr<TextfieldViewsModel> model_;
+
+ // The reference to the border class. The object is owned by View::border_.
+ FocusableBorder* text_border_;
+
+ // The textfield's text and drop cursor visibility.
+ bool is_cursor_visible_;
+ // The drop cursor is a visual cue for where dragged text will be dropped.
+ bool is_drop_cursor_visible_;
+
+ // True if InputMethod::CancelComposition() should not be called.
+ bool skip_input_method_cancel_composition_;
+
+ // Is the user potentially dragging and dropping from this view?
+ bool initiating_drag_;
+
+ // A runnable method factory for callback to update the cursor.
+ base::WeakPtrFactory<NativeTextfieldViews> cursor_timer_;
+
+ // State variables used to track double and triple clicks.
+ size_t aggregated_clicks_;
+ base::Time last_click_time_;
+ gfx::Point last_click_location_;
+
+ // Context menu and its content list for the textfield.
+ scoped_ptr<ui::SimpleMenuModel> context_menu_contents_;
+ scoped_ptr<views::MenuModelAdapter> context_menu_delegate_;
+ scoped_ptr<views::MenuRunner> context_menu_runner_;
+
+ scoped_ptr<TouchSelectionController> touch_selection_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeTextfieldViews);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_VIEWS_H_
diff --git a/ui/views/controls/textfield/native_textfield_views_unittest.cc b/ui/views/controls/textfield/native_textfield_views_unittest.cc
new file mode 100644
index 0000000..1c15e7a
--- /dev/null
+++ b/ui/views/controls/textfield/native_textfield_views_unittest.cc
@@ -0,0 +1,1573 @@
+// 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.
+
+#include <string>
+#include <vector>
+
+#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/pickle.h"
+#include "base/utf_string_conversions.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/ime/text_input_client.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/render_text.h"
+#include "ui/views/controls/textfield/native_textfield_views.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/controls/textfield/textfield_views_model.h"
+#include "ui/views/events/event.h"
+#include "ui/views/focus/focus_manager.h"
+#include "ui/views/ime/mock_input_method.h"
+#include "ui/views/test/test_views_delegate.h"
+#include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/native_widget_private.h"
+#include "ui/views/widget/widget.h"
+#include "views/views_delegate.h"
+
+// Drag and drop for aura in linux hasn't been implemented yet.
+// Bug http://crbug.com/97845
+#if defined(USE_AURA) && defined(OS_LINUX)
+#define MAYBE_DragAndDrop_InitiateDrag DISABLED_DragAndDrop_InitiateDrag
+#define MAYBE_DragAndDrop_ToTheLeft DISABLED_DragAndDrop_ToTheLeft
+#define MAYBE_DragAndDrop_ToTheRight DISABLED_DragAndDrop_ToTheRight
+#define MAYBE_DragAndDrop_Canceled DISABLED_DragAndDrop_Canceled
+#else
+#define MAYBE_DragAndDrop_InitiateDrag DragAndDrop_InitiateDrag
+#define MAYBE_DragAndDrop_ToTheLeft DragAndDrop_ToTheLeft
+#define MAYBE_DragAndDrop_ToTheRight DragAndDrop_ToTheRight
+#define MAYBE_DragAndDrop_Canceled DragAndDrop_Canceled
+#endif // OS_LINUX && USE_AURA
+
+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 ui::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);
+};
+
+const char16 kHebrewLetterSamekh = 0x05E1;
+
+} // namespace
+
+namespace views {
+
+// Convert to Wide so that the printed string will be readable when
+// check fails.
+#define EXPECT_STR_EQ(ascii, utf16) \
+ EXPECT_EQ(ASCIIToWide(ascii), UTF16ToWide(utf16))
+#define EXPECT_STR_NE(ascii, utf16) \
+ EXPECT_NE(ASCIIToWide(ascii), UTF16ToWide(utf16))
+
+// TODO(oshima): Move tests that are independent of TextfieldViews to
+// textfield_unittests.cc once we move the test utility functions
+// from chrome/browser/automation/ to ui/base/test/.
+class NativeTextfieldViewsTest : public ViewsTestBase,
+ public TextfieldController {
+ public:
+ NativeTextfieldViewsTest()
+ : widget_(NULL),
+ textfield_(NULL),
+ textfield_view_(NULL),
+ model_(NULL),
+ input_method_(NULL),
+ on_before_user_action_(0),
+ on_after_user_action_(0) {
+ }
+
+ // ::testing::Test:
+ virtual void SetUp() {
+ ViewsTestBase::SetUp();
+ Widget::SetPureViews(true);
+ }
+
+ virtual void TearDown() {
+ if (widget_)
+ widget_->Close();
+ Widget::SetPureViews(false);
+ ViewsTestBase::TearDown();
+ }
+
+ // TextfieldController:
+ virtual void ContentsChanged(Textfield* sender,
+ const string16& new_contents) {
+ ASSERT_NE(last_contents_, new_contents);
+ last_contents_ = new_contents;
+ }
+
+ virtual bool HandleKeyEvent(Textfield* sender,
+ const KeyEvent& key_event) {
+
+ // TODO(oshima): figure out how to test the keystroke.
+ 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 TestTextfield(style);
+ textfield_->SetController(this);
+ widget_ = new Widget;
+ Widget::InitParams params(Widget::InitParams::TYPE_POPUP);
+ params.bounds = gfx::Rect(100, 100, 100, 100);
+ widget_->Init(params);
+ View* container = new View();
+ widget_->SetContentsView(container);
+ container->AddChildView(textfield_);
+
+ textfield_view_ = static_cast<NativeTextfieldViews*>(
+ textfield_->GetNativeWrapperForTesting());
+ textfield_view_->SetBoundsRect(params.bounds);
+ textfield_->set_id(1);
+
+ for (int i = 1; i < count; i++) {
+ Textfield* textfield = new Textfield(style);
+ container->AddChildView(textfield);
+ textfield->set_id(i + 1);
+ }
+
+ DCHECK(textfield_view_);
+ model_ = textfield_view_->model_.get();
+ model_->ClearEditHistory();
+
+ input_method_ = new MockInputMethod();
+ widget_->ReplaceInputMethod(input_method_);
+
+ // Assumes the Widget is always focused.
+ input_method_->OnFocus();
+
+ textfield_->RequestFocus();
+ }
+
+ ui::MenuModel* GetContextMenuModel() {
+ textfield_view_->UpdateContextMenu();
+ return textfield_view_->context_menu_contents_.get();
+ }
+
+ protected:
+ void SendKeyEvent(ui::KeyboardCode key_code,
+ bool shift,
+ bool control,
+ bool caps_lock) {
+ int flags = (shift ? ui::EF_SHIFT_DOWN : 0) |
+ (control ? ui::EF_CONTROL_DOWN : 0) |
+ (caps_lock ? ui::EF_CAPS_LOCK_DOWN : 0);
+ KeyEvent event(ui::ET_KEY_PRESSED, key_code, flags);
+ input_method_->DispatchKeyEvent(event);
+ }
+
+ void SendKeyEvent(ui::KeyboardCode key_code, bool shift, bool control) {
+ SendKeyEvent(key_code, shift, control, false);
+ }
+
+ void SendKeyEvent(ui::KeyboardCode key_code) {
+ SendKeyEvent(key_code, false, false);
+ }
+
+ void SendKeyEvent(char16 ch) {
+ if (ch < 0x80) {
+ ui::KeyboardCode code =
+ ch == ' ' ? ui::VKEY_SPACE :
+ static_cast<ui::KeyboardCode>(ui::VKEY_A + ch - 'a');
+ SendKeyEvent(code);
+ } else {
+ KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_UNKNOWN, 0);
+ event.set_character(ch);
+ input_method_->DispatchKeyEvent(event);
+ }
+ }
+
+ string16 GetClipboardText() const {
+ string16 text;
+ views::ViewsDelegate::views_delegate->GetClipboard()->
+ ReadText(ui::Clipboard::BUFFER_STANDARD, &text);
+ return text;
+ }
+
+ void SetClipboardText(const std::string& text) {
+ ui::ScopedClipboardWriter clipboard_writer(
+ views::ViewsDelegate::views_delegate->GetClipboard());
+ clipboard_writer.WriteText(ASCIIToUTF16(text));
+ }
+
+ View* GetFocusedView() {
+ return widget_->GetFocusManager()->GetFocusedView();
+ }
+
+ int GetCursorPositionX(int cursor_pos) {
+ gfx::RenderText* render_text = textfield_view_->GetRenderText();
+ return render_text->GetCursorBounds(
+ gfx::SelectionModel(cursor_pos), false).x();
+ }
+
+ // Get the current cursor bounds.
+ gfx::Rect GetCursorBounds() {
+ gfx::RenderText* render_text = textfield_view_->GetRenderText();
+ gfx::Rect bounds = render_text->GetUpdatedCursorBounds();
+ return bounds;
+ }
+
+ // Get the cursor bounds of |sel|.
+ gfx::Rect GetCursorBounds(const gfx::SelectionModel& sel) {
+ gfx::RenderText* render_text = textfield_view_->GetRenderText();
+ gfx::Rect bounds = render_text->GetCursorBounds(sel, true);
+ return bounds;
+ }
+
+ gfx::Rect GetDisplayRect() {
+ return textfield_view_->GetRenderText()->display_rect();
+ }
+
+ // Mouse click on the point whose x-axis is |bound|'s x plus |x_offset| and
+ // y-axis is in the middle of |bound|'s vertical range.
+ void MouseClick(const gfx::Rect bound, int x_offset) {
+ int x = bound.x() + x_offset;
+ int y = bound.y() + bound.height() / 2;
+ MouseEvent click(ui::ET_MOUSE_PRESSED, x, y, ui::EF_LEFT_BUTTON_DOWN);
+ textfield_view_->OnMousePressed(click);
+ MouseEvent release(ui::ET_MOUSE_RELEASED, x, y, ui::EF_LEFT_BUTTON_DOWN);
+ textfield_view_->OnMouseReleased(release);
+ }
+
+ // This is to avoid double/triple click.
+ void NonClientMouseClick() {
+ MouseEvent click(ui::ET_MOUSE_PRESSED, 0, 0,
+ ui::EF_LEFT_BUTTON_DOWN | ui::EF_IS_NON_CLIENT);
+ textfield_view_->OnMousePressed(click);
+ MouseEvent release(ui::ET_MOUSE_RELEASED, 0, 0,
+ ui::EF_LEFT_BUTTON_DOWN | ui::EF_IS_NON_CLIENT);
+ textfield_view_->OnMouseReleased(release);
+ }
+
+ // Wrap for visibility in test classes.
+ ui::TextInputType GetTextInputType() {
+ return textfield_view_->GetTextInputType();
+ }
+
+ void VerifyTextfieldContextMenuContents(bool textfield_has_selection,
+ ui::MenuModel* menu_model) {
+ EXPECT_TRUE(menu_model->IsEnabledAt(4 /* Separator */));
+ EXPECT_TRUE(menu_model->IsEnabledAt(5 /* SELECT ALL */));
+ EXPECT_EQ(textfield_has_selection, menu_model->IsEnabledAt(0 /* CUT */));
+ EXPECT_EQ(textfield_has_selection, menu_model->IsEnabledAt(1 /* COPY */));
+ EXPECT_EQ(textfield_has_selection, menu_model->IsEnabledAt(3 /* DELETE */));
+ string16 str(GetClipboardText());
+ EXPECT_NE(str.empty(), menu_model->IsEnabledAt(2 /* PASTE */));
+ }
+
+ // We need widget to populate wrapper class.
+ Widget* widget_;
+
+ 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, 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_->GetText());
+ 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_->GetText());
+ 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_TRUE(last_contents_.empty());
+}
+
+TEST_F(NativeTextfieldViewsTest, KeyTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ SendKeyEvent(ui::VKEY_C, true, false);
+ EXPECT_STR_EQ("C", textfield_->text());
+ EXPECT_STR_EQ("C", last_contents_);
+ last_contents_.clear();
+
+ SendKeyEvent(ui::VKEY_R, false, false);
+ EXPECT_STR_EQ("Cr", textfield_->text());
+ EXPECT_STR_EQ("Cr", last_contents_);
+
+ textfield_->SetText(ASCIIToUTF16(""));
+ 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_);
+}
+
+TEST_F(NativeTextfieldViewsTest, ControlAndSelectTest) {
+ // Insert a test string in a textfield.
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(ASCIIToUTF16("one two three"));
+ SendKeyEvent(ui::VKEY_RIGHT,
+ true /* shift */, false /* control */);
+ SendKeyEvent(ui::VKEY_RIGHT, true, false);
+ SendKeyEvent(ui::VKEY_RIGHT, true, false);
+
+ EXPECT_STR_EQ("one", textfield_->GetSelectedText());
+
+ // Test word select.
+ SendKeyEvent(ui::VKEY_RIGHT, true, true);
+ EXPECT_STR_EQ("one two", textfield_->GetSelectedText());
+ SendKeyEvent(ui::VKEY_RIGHT, true, true);
+ EXPECT_STR_EQ("one two three", textfield_->GetSelectedText());
+ SendKeyEvent(ui::VKEY_LEFT, true, true);
+ EXPECT_STR_EQ("one two ", textfield_->GetSelectedText());
+ SendKeyEvent(ui::VKEY_LEFT, true, true);
+ EXPECT_STR_EQ("one ", textfield_->GetSelectedText());
+
+ // Replace the selected text.
+ 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());
+
+ SendKeyEvent(ui::VKEY_END, true, false);
+ EXPECT_STR_EQ("two three", textfield_->GetSelectedText());
+ SendKeyEvent(ui::VKEY_HOME, true, false);
+ EXPECT_STR_EQ("ZERO ", textfield_->GetSelectedText());
+}
+
+TEST_F(NativeTextfieldViewsTest, InsertionDeletionTest) {
+ // Insert a test string in a textfield.
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ char test_str[] = "this is a test";
+ for (size_t i = 0; i < sizeof(test_str); i++) {
+ // This is ugly and should be replaced by a utility standard function.
+ // See comment in NativeTextfieldViews::GetPrintableChar.
+ char c = test_str[i];
+ ui::KeyboardCode code =
+ c == ' ' ? ui::VKEY_SPACE :
+ static_cast<ui::KeyboardCode>(ui::VKEY_A + c - 'a');
+ SendKeyEvent(code);
+ }
+ EXPECT_STR_EQ(test_str, textfield_->text());
+
+ // Move the cursor around.
+ for (int i = 0; i < 6; i++) {
+ SendKeyEvent(ui::VKEY_LEFT);
+ }
+ SendKeyEvent(ui::VKEY_RIGHT);
+
+ // Delete using backspace and check resulting string.
+ 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++) {
+ SendKeyEvent(ui::VKEY_DELETE);
+ }
+ EXPECT_STR_EQ("this is ", textfield_->text());
+
+ // Select all and replace with "k".
+ textfield_->SelectAll();
+ SendKeyEvent(ui::VKEY_K);
+ EXPECT_STR_EQ("k", textfield_->text());
+
+ // Delete the previous word from cursor.
+ textfield_->SetText(ASCIIToUTF16("one two three four"));
+ 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.
+ 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
+ EXPECT_STR_EQ("three ", textfield_->text());
+#endif
+
+ // Delete the next word from cursor.
+ textfield_->SetText(ASCIIToUTF16("one two three four"));
+ 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.
+ 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
+ EXPECT_STR_EQ(" two", textfield_->text());
+#endif
+}
+
+TEST_F(NativeTextfieldViewsTest, PasswordTest) {
+ InitTextfield(Textfield::STYLE_PASSWORD);
+
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, GetTextInputType());
+
+ 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_TRUE(last_contents_.empty());
+}
+
+TEST_F(NativeTextfieldViewsTest, InputTypeSetsPassword) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+
+ // Defaults to TEXT
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, GetTextInputType());
+
+ // Setting to passwords also sets password state of textfield.
+ textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD);
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, GetTextInputType());
+ EXPECT_TRUE(textfield_->IsPassword());
+}
+
+TEST_F(NativeTextfieldViewsTest, PasswordSetsInputType) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+
+ // Defaults to TEXT
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, GetTextInputType());
+
+ textfield_->SetPassword(true);
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, GetTextInputType());
+
+ textfield_->SetPassword(false);
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, GetTextInputType());
+}
+
+TEST_F(NativeTextfieldViewsTest, TextInputType) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+
+ // Defaults to TEXT
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, GetTextInputType());
+
+ // And can be set.
+ textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_URL);
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_URL, GetTextInputType());
+
+ // Readonly textfields have type NONE
+ textfield_->SetReadOnly(true);
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE, GetTextInputType());
+
+ textfield_->SetReadOnly(false);
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_URL, GetTextInputType());
+
+ // As do disabled textfields
+ textfield_->SetEnabled(false);
+ EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE, GetTextInputType());
+}
+
+TEST_F(NativeTextfieldViewsTest, OnKeyPressReturnValueTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+
+ // 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.
+ 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) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+
+ // Test with trailing whitespace.
+ textfield_->SetText(ASCIIToUTF16("one two hre "));
+
+ // Send the cursor at the end.
+ SendKeyEvent(ui::VKEY_END);
+
+ // Ctrl+Left should move the cursor just before the last word.
+ 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.
+ 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.
+ 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_);
+
+ // Test with leading whitespace.
+ textfield_->SetText(ASCIIToUTF16(" ne two"));
+
+ // Send the cursor at the beginning.
+ SendKeyEvent(ui::VKEY_HOME);
+
+ // Ctrl+Right, then Ctrl+Left should move the cursor to the beginning of the
+ // first word.
+ 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.
+ SendKeyEvent(ui::VKEY_LEFT, false, true);
+ // Ctrl+Left again should move the cursor back to the very beginning.
+ SendKeyEvent(ui::VKEY_LEFT, false, true);
+ SendKeyEvent(ui::VKEY_DELETE);
+ EXPECT_STR_EQ("one two", textfield_->text());
+ EXPECT_STR_EQ("one two", last_contents_);
+}
+
+TEST_F(NativeTextfieldViewsTest, FocusTraversalTest) {
+ InitTextfields(Textfield::STYLE_DEFAULT, 3);
+ textfield_->RequestFocus();
+
+ EXPECT_EQ(1, GetFocusedView()->id());
+ widget_->GetFocusManager()->AdvanceFocus(false);
+ EXPECT_EQ(2, GetFocusedView()->id());
+ widget_->GetFocusManager()->AdvanceFocus(false);
+ EXPECT_EQ(3, GetFocusedView()->id());
+ // Cycle back to the first textfield.
+ widget_->GetFocusManager()->AdvanceFocus(false);
+ EXPECT_EQ(1, GetFocusedView()->id());
+
+ widget_->GetFocusManager()->AdvanceFocus(true);
+ EXPECT_EQ(3, GetFocusedView()->id());
+ widget_->GetFocusManager()->AdvanceFocus(true);
+ EXPECT_EQ(2, GetFocusedView()->id());
+ widget_->GetFocusManager()->AdvanceFocus(true);
+ EXPECT_EQ(1, GetFocusedView()->id());
+ // Cycle back to the last textfield.
+ widget_->GetFocusManager()->AdvanceFocus(true);
+ EXPECT_EQ(3, GetFocusedView()->id());
+
+ // Request focus should still work.
+ textfield_->RequestFocus();
+ EXPECT_EQ(1, GetFocusedView()->id());
+
+ // Test if clicking on textfield view sets the focus to textfield_.
+ widget_->GetFocusManager()->AdvanceFocus(true);
+ EXPECT_EQ(3, GetFocusedView()->id());
+ MouseEvent click(ui::ET_MOUSE_PRESSED, 0, 0, ui::EF_LEFT_BUTTON_DOWN);
+ textfield_view_->OnMousePressed(click);
+ EXPECT_EQ(1, GetFocusedView()->id());
+}
+
+TEST_F(NativeTextfieldViewsTest, ContextMenuDisplayTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(ASCIIToUTF16("hello world"));
+ EXPECT_TRUE(GetContextMenuModel());
+ VerifyTextfieldContextMenuContents(false, GetContextMenuModel());
+
+ textfield_->SelectAll();
+ VerifyTextfieldContextMenuContents(true, GetContextMenuModel());
+}
+
+TEST_F(NativeTextfieldViewsTest, DoubleAndTripleClickTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(ASCIIToUTF16("hello world"));
+ MouseEvent click(ui::ET_MOUSE_PRESSED, 0, 0, ui::EF_LEFT_BUTTON_DOWN);
+ MouseEvent release(ui::ET_MOUSE_RELEASED, 0, 0, ui::EF_LEFT_BUTTON_DOWN);
+ MouseEvent double_click(ui::ET_MOUSE_PRESSED, 0, 0,
+ ui::EF_LEFT_BUTTON_DOWN | ui::EF_IS_DOUBLE_CLICK);
+
+ // Test for double click.
+ textfield_view_->OnMousePressed(click);
+ textfield_view_->OnMouseReleased(release);
+ EXPECT_TRUE(textfield_->GetSelectedText().empty());
+ textfield_view_->OnMousePressed(double_click);
+ textfield_view_->OnMouseReleased(release);
+ EXPECT_STR_EQ("hello", textfield_->GetSelectedText());
+
+ // Test for triple click.
+ textfield_view_->OnMousePressed(click);
+ textfield_view_->OnMouseReleased(release);
+ EXPECT_STR_EQ("hello world", textfield_->GetSelectedText());
+
+ // Another click should reset back to single click.
+ textfield_view_->OnMousePressed(click);
+ textfield_view_->OnMouseReleased(release);
+ EXPECT_TRUE(textfield_->GetSelectedText().empty());
+}
+
+TEST_F(NativeTextfieldViewsTest, DragToSelect) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(ASCIIToUTF16("hello world"));
+ const int kStart = GetCursorPositionX(5);
+ const int kEnd = 500;
+ MouseEvent click_a(ui::ET_MOUSE_PRESSED, kStart, 0, ui::EF_LEFT_BUTTON_DOWN);
+ MouseEvent click_b(ui::ET_MOUSE_PRESSED, kEnd, 0, ui::EF_LEFT_BUTTON_DOWN);
+ MouseEvent drag_left(ui::ET_MOUSE_DRAGGED, 0, 0, ui::EF_LEFT_BUTTON_DOWN);
+ MouseEvent drag_right(ui::ET_MOUSE_DRAGGED, kEnd, 0, ui::EF_LEFT_BUTTON_DOWN);
+ MouseEvent release(ui::ET_MOUSE_RELEASED, kEnd, 0, ui::EF_LEFT_BUTTON_DOWN);
+ textfield_view_->OnMousePressed(click_a);
+ EXPECT_TRUE(textfield_->GetSelectedText().empty());
+ // Check that dragging left selects the beginning of the string.
+ textfield_view_->OnMouseDragged(drag_left);
+ string16 text_left = textfield_->GetSelectedText();
+ EXPECT_STR_EQ("hello", text_left);
+ // Check that dragging right selects the rest of the string.
+ textfield_view_->OnMouseDragged(drag_right);
+ string16 text_right = textfield_->GetSelectedText();
+ EXPECT_STR_EQ(" world", text_right);
+ // Check that releasing in the same location does not alter the selection.
+ textfield_view_->OnMouseReleased(release);
+ EXPECT_EQ(text_right, textfield_->GetSelectedText());
+ // Check that dragging from beyond the text length works too.
+ textfield_view_->OnMousePressed(click_b);
+ textfield_view_->OnMouseDragged(drag_left);
+ textfield_view_->OnMouseReleased(release);
+ EXPECT_EQ(textfield_->text(), textfield_->GetSelectedText());
+}
+
+#if defined(OS_WIN) || defined(TOOLKIT_USES_GTK)
+TEST_F(NativeTextfieldViewsTest, DragAndDrop_AcceptDrop) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(ASCIIToUTF16("hello world"));
+
+ ui::OSExchangeData data;
+ string16 string(ASCIIToUTF16("string "));
+ data.SetString(string);
+ int formats = 0;
+ std::set<OSExchangeData::CustomFormat> custom_formats;
+
+ // Ensure that disabled textfields do not accept drops.
+ textfield_->SetEnabled(false);
+ EXPECT_FALSE(textfield_view_->GetDropFormats(&formats, &custom_formats));
+ EXPECT_EQ(0, formats);
+ EXPECT_TRUE(custom_formats.empty());
+ EXPECT_FALSE(textfield_view_->CanDrop(data));
+ textfield_->SetEnabled(true);
+
+ // Ensure that read-only textfields do not accept drops.
+ textfield_->SetReadOnly(true);
+ EXPECT_FALSE(textfield_view_->GetDropFormats(&formats, &custom_formats));
+ EXPECT_EQ(0, formats);
+ EXPECT_TRUE(custom_formats.empty());
+ EXPECT_FALSE(textfield_view_->CanDrop(data));
+ textfield_->SetReadOnly(false);
+
+ // Ensure that enabled and editable textfields do accept drops.
+ EXPECT_TRUE(textfield_view_->GetDropFormats(&formats, &custom_formats));
+ EXPECT_EQ(ui::OSExchangeData::STRING, formats);
+ EXPECT_TRUE(custom_formats.empty());
+ EXPECT_TRUE(textfield_view_->CanDrop(data));
+ DropTargetEvent drop(data, GetCursorPositionX(6), 0,
+ ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE);
+ EXPECT_EQ(ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE,
+ textfield_view_->OnDragUpdated(drop));
+ EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, textfield_view_->OnPerformDrop(drop));
+ EXPECT_STR_EQ("hello string world", textfield_->text());
+
+ // Ensure that textfields do not accept non-OSExchangeData::STRING types.
+ ui::OSExchangeData bad_data;
+ bad_data.SetFilename(FilePath(FILE_PATH_LITERAL("x")));
+#if defined(OS_WIN)
+ bad_data.SetPickledData(CF_BITMAP, Pickle());
+ bad_data.SetFileContents(FilePath(L"x"), "x");
+ bad_data.SetHtml(string16(ASCIIToUTF16("x")), GURL("x.org"));
+ ui::OSExchangeData::DownloadFileInfo download(FilePath(), NULL);
+ bad_data.SetDownloadFileInfo(download);
+#else
+ // Skip OSExchangeDataProviderWin::SetURL, which also sets CF_TEXT / STRING.
+ bad_data.SetURL(GURL("x.org"), string16(ASCIIToUTF16("x")));
+ bad_data.SetPickledData(GDK_SELECTION_PRIMARY, Pickle());
+#endif
+ EXPECT_FALSE(textfield_view_->CanDrop(bad_data));
+}
+#endif
+
+TEST_F(NativeTextfieldViewsTest, MAYBE_DragAndDrop_InitiateDrag) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(ASCIIToUTF16("hello string world"));
+
+ // Ensure the textfield will provide selected text for drag data.
+ string16 string;
+ ui::OSExchangeData data;
+ const ui::Range kStringRange(6, 12);
+ textfield_->SelectRange(kStringRange);
+ const gfx::Point kStringPoint(GetCursorPositionX(9), 0);
+ textfield_view_->WriteDragDataForView(NULL, kStringPoint, &data);
+ EXPECT_TRUE(data.GetString(&string));
+ EXPECT_EQ(textfield_->GetSelectedText(), string);
+
+ // Ensure that disabled textfields do not support drag operations.
+ textfield_->SetEnabled(false);
+ EXPECT_EQ(ui::DragDropTypes::DRAG_NONE,
+ textfield_view_->GetDragOperationsForView(NULL, kStringPoint));
+ textfield_->SetEnabled(true);
+ // Ensure that textfields without selections do not support drag operations.
+ textfield_->ClearSelection();
+ EXPECT_EQ(ui::DragDropTypes::DRAG_NONE,
+ textfield_view_->GetDragOperationsForView(NULL, kStringPoint));
+ textfield_->SelectRange(kStringRange);
+ // Ensure that textfields only initiate drag operations inside the selection.
+ EXPECT_EQ(ui::DragDropTypes::DRAG_NONE,
+ textfield_view_->GetDragOperationsForView(NULL, gfx::Point()));
+ EXPECT_FALSE(textfield_view_->CanStartDragForView(NULL, gfx::Point(),
+ gfx::Point()));
+ EXPECT_EQ(ui::DragDropTypes::DRAG_COPY,
+ textfield_view_->GetDragOperationsForView(NULL, kStringPoint));
+ EXPECT_TRUE(textfield_view_->CanStartDragForView(NULL, kStringPoint,
+ gfx::Point()));
+ // Ensure that textfields support local moves.
+ EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_COPY,
+ textfield_view_->GetDragOperationsForView(textfield_view_, kStringPoint));
+}
+
+TEST_F(NativeTextfieldViewsTest, MAYBE_DragAndDrop_ToTheRight) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(ASCIIToUTF16("hello world"));
+
+ string16 string;
+ ui::OSExchangeData data;
+ int formats = 0;
+ int operations = 0;
+ std::set<OSExchangeData::CustomFormat> custom_formats;
+
+ // Start dragging "ello".
+ textfield_->SelectRange(ui::Range(1, 5));
+ MouseEvent click_a(ui::ET_MOUSE_PRESSED, GetCursorPositionX(3), 0,
+ ui::EF_LEFT_BUTTON_DOWN);
+ textfield_view_->OnMousePressed(click_a);
+ EXPECT_TRUE(textfield_view_->CanStartDragForView(textfield_view_,
+ click_a.location(), gfx::Point()));
+ operations = textfield_view_->GetDragOperationsForView(textfield_view_,
+ click_a.location());
+ EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_COPY,
+ operations);
+ textfield_view_->WriteDragDataForView(NULL, click_a.location(), &data);
+ EXPECT_TRUE(data.GetString(&string));
+ EXPECT_EQ(textfield_->GetSelectedText(), string);
+ EXPECT_TRUE(textfield_view_->GetDropFormats(&formats, &custom_formats));
+ EXPECT_EQ(ui::OSExchangeData::STRING, formats);
+ EXPECT_TRUE(custom_formats.empty());
+
+ // Drop "ello" after "w".
+ const gfx::Point kDropPoint(GetCursorPositionX(7), 0);
+ EXPECT_TRUE(textfield_view_->CanDrop(data));
+ DropTargetEvent drop_a(data, kDropPoint.x(), 0, operations);
+ EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE,
+ textfield_view_->OnDragUpdated(drop_a));
+ EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE,
+ textfield_view_->OnPerformDrop(drop_a));
+ EXPECT_STR_EQ("h welloorld", textfield_->text());
+ textfield_view_->OnDragDone();
+
+ // Undo/Redo the drag&drop change.
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("hello world", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("hello world", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("h welloorld", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("h welloorld", textfield_->text());
+}
+
+TEST_F(NativeTextfieldViewsTest, MAYBE_DragAndDrop_ToTheLeft) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(ASCIIToUTF16("hello world"));
+
+ string16 string;
+ ui::OSExchangeData data;
+ int formats = 0;
+ int operations = 0;
+ std::set<OSExchangeData::CustomFormat> custom_formats;
+
+ // Start dragging " worl".
+ textfield_->SelectRange(ui::Range(5, 10));
+ MouseEvent click_a(ui::ET_MOUSE_PRESSED, GetCursorPositionX(7), 0,
+ ui::EF_LEFT_BUTTON_DOWN);
+ textfield_view_->OnMousePressed(click_a);
+ EXPECT_TRUE(textfield_view_->CanStartDragForView(textfield_view_,
+ click_a.location(), gfx::Point()));
+ operations = textfield_view_->GetDragOperationsForView(textfield_view_,
+ click_a.location());
+ EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_COPY,
+ operations);
+ textfield_view_->WriteDragDataForView(NULL, click_a.location(), &data);
+ EXPECT_TRUE(data.GetString(&string));
+ EXPECT_EQ(textfield_->GetSelectedText(), string);
+ EXPECT_TRUE(textfield_view_->GetDropFormats(&formats, &custom_formats));
+ EXPECT_EQ(ui::OSExchangeData::STRING, formats);
+ EXPECT_TRUE(custom_formats.empty());
+
+ // Drop " worl" after "h".
+ EXPECT_TRUE(textfield_view_->CanDrop(data));
+ DropTargetEvent drop_a(data, GetCursorPositionX(1), 0, operations);
+ EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE,
+ textfield_view_->OnDragUpdated(drop_a));
+ EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE,
+ textfield_view_->OnPerformDrop(drop_a));
+ EXPECT_STR_EQ("h worlellod", textfield_->text());
+ textfield_view_->OnDragDone();
+
+ // Undo/Redo the drag&drop change.
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("hello world", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("hello world", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("h worlellod", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("h worlellod", textfield_->text());
+}
+
+TEST_F(NativeTextfieldViewsTest, MAYBE_DragAndDrop_Canceled) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(ASCIIToUTF16("hello world"));
+
+ // Start dragging "worl".
+ textfield_->SelectRange(ui::Range(6, 10));
+ MouseEvent click(ui::ET_MOUSE_PRESSED, GetCursorPositionX(8), 0,
+ ui::EF_LEFT_BUTTON_DOWN);
+ textfield_view_->OnMousePressed(click);
+ ui::OSExchangeData data;
+ textfield_view_->WriteDragDataForView(NULL, click.location(), &data);
+ EXPECT_TRUE(textfield_view_->CanDrop(data));
+ // Drag the text over somewhere valid, outside the current selection.
+ DropTargetEvent drop(data, GetCursorPositionX(2), 0,
+ ui::DragDropTypes::DRAG_MOVE);
+ EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE, textfield_view_->OnDragUpdated(drop));
+ // "Cancel" the drag, via move and release over the selection, and OnDragDone.
+ MouseEvent drag(ui::ET_MOUSE_DRAGGED, GetCursorPositionX(9), 0,
+ ui::EF_LEFT_BUTTON_DOWN);
+ MouseEvent release(ui::ET_MOUSE_RELEASED, GetCursorPositionX(9), 0,
+ ui::EF_LEFT_BUTTON_DOWN);
+ textfield_view_->OnMouseDragged(drag);
+ textfield_view_->OnMouseReleased(release);
+ textfield_view_->OnDragDone();
+ EXPECT_EQ(ASCIIToUTF16("hello world"), textfield_->text());
+}
+
+TEST_F(NativeTextfieldViewsTest, ReadOnlyTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(ASCIIToUTF16(" one two three "));
+ textfield_->SetReadOnly(true);
+ SendKeyEvent(ui::VKEY_HOME);
+ EXPECT_EQ(0U, textfield_->GetCursorPosition());
+
+ SendKeyEvent(ui::VKEY_END);
+ EXPECT_EQ(15U, textfield_->GetCursorPosition());
+
+ SendKeyEvent(ui::VKEY_LEFT, false, false);
+ EXPECT_EQ(14U, textfield_->GetCursorPosition());
+
+ SendKeyEvent(ui::VKEY_LEFT, false, true);
+ EXPECT_EQ(9U, textfield_->GetCursorPosition());
+
+ SendKeyEvent(ui::VKEY_LEFT, true, true);
+ EXPECT_EQ(5U, textfield_->GetCursorPosition());
+ EXPECT_STR_EQ("two ", textfield_->GetSelectedText());
+
+ textfield_->SelectAll();
+ EXPECT_STR_EQ(" one two three ", textfield_->GetSelectedText());
+
+ // CUT&PASTE does not work, but COPY works
+ SetClipboardText("Test");
+ SendKeyEvent(ui::VKEY_X, false, true);
+ EXPECT_STR_EQ(" one two three ", textfield_->GetSelectedText());
+ string16 str(GetClipboardText());
+ EXPECT_STR_NE(" one two three ", str);
+
+ SendKeyEvent(ui::VKEY_C, false, true);
+ views::ViewsDelegate::views_delegate->GetClipboard()->
+ ReadText(ui::Clipboard::BUFFER_STANDARD, &str);
+ EXPECT_STR_EQ(" one two three ", str);
+
+ // SetText should work even in read only mode.
+ textfield_->SetText(ASCIIToUTF16(" four five six "));
+ EXPECT_STR_EQ(" four five six ", textfield_->text());
+
+ // Paste shouldn't work.
+ SendKeyEvent(ui::VKEY_V, false, true);
+ EXPECT_STR_EQ(" four five six ", textfield_->text());
+ EXPECT_TRUE(textfield_->GetSelectedText().empty());
+
+ textfield_->SelectAll();
+ EXPECT_STR_EQ(" four five six ", textfield_->GetSelectedText());
+
+ // Text field is unmodifiable and selection shouldn't change.
+ SendKeyEvent(ui::VKEY_DELETE);
+ EXPECT_STR_EQ(" four five six ", textfield_->GetSelectedText());
+ SendKeyEvent(ui::VKEY_BACK);
+ EXPECT_STR_EQ(" four five six ", textfield_->GetSelectedText());
+ SendKeyEvent(ui::VKEY_T);
+ EXPECT_STR_EQ(" four five six ", textfield_->GetSelectedText());
+}
+
+TEST_F(NativeTextfieldViewsTest, TextInputClientTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ ui::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());
+}
+
+TEST_F(NativeTextfieldViewsTest, UndoRedoTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ SendKeyEvent(ui::VKEY_A);
+ EXPECT_STR_EQ("a", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("a", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("a", textfield_->text());
+
+ // AppendText
+ textfield_->AppendText(ASCIIToUTF16("b"));
+ last_contents_.clear(); // AppendText doesn't call ContentsChanged.
+ EXPECT_STR_EQ("ab", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("a", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("ab", textfield_->text());
+
+ // SetText
+ SendKeyEvent(ui::VKEY_C);
+ // Undo'ing append moves the cursor to the end for now.
+ // no-op SetText won't add new edit. See TextfieldViewsModel::SetText
+ // description.
+ EXPECT_STR_EQ("abc", textfield_->text());
+ textfield_->SetText(ASCIIToUTF16("abc"));
+ EXPECT_STR_EQ("abc", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("ab", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("abc", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("abc", textfield_->text());
+ textfield_->SetText(ASCIIToUTF16("123"));
+ textfield_->SetText(ASCIIToUTF16("123"));
+ EXPECT_STR_EQ("123", textfield_->text());
+ SendKeyEvent(ui::VKEY_END, false, false);
+ SendKeyEvent(ui::VKEY_4, false, false);
+ EXPECT_STR_EQ("1234", textfield_->text());
+ last_contents_.clear();
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("123", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ // the insert edit "c" and set edit "123" are merged to single edit,
+ // so text becomes "ab" after undo.
+ EXPECT_STR_EQ("ab", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("a", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("ab", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("123", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("1234", textfield_->text());
+
+ // Undoing to the same text shouldn't call ContentsChanged.
+ SendKeyEvent(ui::VKEY_A, false, true); // select all
+ SendKeyEvent(ui::VKEY_A);
+ EXPECT_STR_EQ("a", textfield_->text());
+ SendKeyEvent(ui::VKEY_B);
+ SendKeyEvent(ui::VKEY_C);
+ EXPECT_STR_EQ("abc", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("1234", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("abc", textfield_->text());
+
+ // Delete/Backspace
+ SendKeyEvent(ui::VKEY_BACK);
+ EXPECT_STR_EQ("ab", textfield_->text());
+ SendKeyEvent(ui::VKEY_HOME);
+ SendKeyEvent(ui::VKEY_DELETE);
+ EXPECT_STR_EQ("b", textfield_->text());
+ SendKeyEvent(ui::VKEY_A, false, true);
+ SendKeyEvent(ui::VKEY_DELETE);
+ EXPECT_STR_EQ("", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("b", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("ab", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("abc", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("ab", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("b", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("", textfield_->text());
+
+ // Insert
+ textfield_->SetText(ASCIIToUTF16("123"));
+ SendKeyEvent(ui::VKEY_HOME);
+ SendKeyEvent(ui::VKEY_INSERT);
+ SendKeyEvent(ui::VKEY_A);
+ EXPECT_STR_EQ("a23", textfield_->text());
+ SendKeyEvent(ui::VKEY_B);
+ EXPECT_STR_EQ("ab3", textfield_->text());
+ SendKeyEvent(ui::VKEY_Z, false, true);
+ EXPECT_STR_EQ("123", textfield_->text());
+ SendKeyEvent(ui::VKEY_Y, false, true);
+ EXPECT_STR_EQ("ab3", textfield_->text());
+}
+
+TEST_F(NativeTextfieldViewsTest, TextCursorDisplayTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ // LTR-RTL string in LTR context.
+ SendKeyEvent('a');
+ EXPECT_STR_EQ("a", textfield_->text());
+ int x = GetCursorBounds().x();
+ int prev_x = x;
+
+ SendKeyEvent('b');
+ EXPECT_STR_EQ("ab", textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_LT(prev_x, x);
+ prev_x = x;
+
+ SendKeyEvent(0x05E1);
+ EXPECT_EQ(WideToUTF16(L"ab\x05E1"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_EQ(prev_x, x);
+
+ SendKeyEvent(0x05E2);
+ EXPECT_EQ(WideToUTF16(L"ab\x05E1\x5E2"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_EQ(prev_x, x);
+
+ // Clear text.
+ SendKeyEvent(ui::VKEY_A, false, true);
+ SendKeyEvent('\n');
+
+ // RTL-LTR string in LTR context.
+ SendKeyEvent(0x05E1);
+ EXPECT_EQ(WideToUTF16(L"\x05E1"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_EQ(GetDisplayRect().x(), x);
+ prev_x = x;
+
+ SendKeyEvent(0x05E2);
+ EXPECT_EQ(WideToUTF16(L"\x05E1\x05E2"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_EQ(prev_x, x);
+
+ SendKeyEvent('a');
+ EXPECT_EQ(WideToUTF16(L"\x05E1\x5E2"L"a"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_LT(prev_x, x);
+ prev_x = x;
+
+ SendKeyEvent('b');
+ EXPECT_EQ(WideToUTF16(L"\x05E1\x5E2"L"ab"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_LT(prev_x, x);
+}
+
+TEST_F(NativeTextfieldViewsTest, TextCursorDisplayInRTLTest) {
+ std::string locale = l10n_util::GetApplicationLocale("");
+ base::i18n::SetICUDefaultLocale("he");
+
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ // LTR-RTL string in RTL context.
+ SendKeyEvent('a');
+ EXPECT_STR_EQ("a", textfield_->text());
+ int x = GetCursorBounds().x();
+ EXPECT_EQ(GetDisplayRect().right() - 1, x);
+ int prev_x = x;
+
+ SendKeyEvent('b');
+ EXPECT_STR_EQ("ab", textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_EQ(prev_x, x);
+
+ SendKeyEvent(0x05E1);
+ EXPECT_EQ(WideToUTF16(L"ab\x05E1"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_GT(prev_x, x);
+ prev_x = x;
+
+ SendKeyEvent(0x05E2);
+ EXPECT_EQ(WideToUTF16(L"ab\x05E1\x5E2"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_GT(prev_x, x);
+
+ SendKeyEvent(ui::VKEY_A, false, true);
+ SendKeyEvent('\n');
+
+ // RTL-LTR string in RTL context.
+ SendKeyEvent(0x05E1);
+ EXPECT_EQ(WideToUTF16(L"\x05E1"), textfield_->text());
+ x = GetCursorBounds().x();
+ prev_x = x;
+
+ SendKeyEvent(0x05E2);
+ EXPECT_EQ(WideToUTF16(L"\x05E1\x05E2"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_GT(prev_x, x);
+ prev_x = x;
+
+ SendKeyEvent('a');
+ EXPECT_EQ(WideToUTF16(L"\x05E1\x5E2"L"a"), textfield_->text());
+ x = GetCursorBounds().x();
+#if defined(OS_WIN)
+ // In Windows, the text is always in LTR directionality even in RTL UI.
+ // TODO(xji): it should change if we fix the directionality in Window's
+ // NativeTextfieldViews
+ EXPECT_LT(prev_x, x);
+#else
+ EXPECT_EQ(prev_x, x);
+#endif
+ prev_x = x;
+
+ SendKeyEvent('b');
+ EXPECT_EQ(WideToUTF16(L"\x05E1\x5E2"L"ab"), textfield_->text());
+ x = GetCursorBounds().x();
+ EXPECT_EQ(prev_x, x);
+
+ // Reset locale.
+ base::i18n::SetICUDefaultLocale(locale);
+}
+
+TEST_F(NativeTextfieldViewsTest, HitInsideTextAreaTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(WideToUTF16(L"ab\x05E1\x5E2"));
+ std::vector<gfx::Rect> cursor_bounds;
+
+ // Save each cursor bound.
+ gfx::SelectionModel sel(0, 0, gfx::SelectionModel::LEADING);
+ cursor_bounds.push_back(GetCursorBounds(sel));
+
+ sel = gfx::SelectionModel(1, 0, gfx::SelectionModel::TRAILING);
+ gfx::Rect bound = GetCursorBounds(sel);
+ sel = gfx::SelectionModel(1, 1, gfx::SelectionModel::LEADING);
+ EXPECT_EQ(bound.x(), GetCursorBounds(sel).x());
+ cursor_bounds.push_back(bound);
+
+ // Check that a cursor at the end of the Latin portion of the text is at the
+ // same position as a cursor placed at the end of the RTL Hebrew portion.
+ sel = gfx::SelectionModel(2, 1, gfx::SelectionModel::TRAILING);
+ bound = GetCursorBounds(sel);
+ sel = gfx::SelectionModel(4, 3, gfx::SelectionModel::TRAILING);
+ EXPECT_EQ(bound.x(), GetCursorBounds(sel).x());
+ cursor_bounds.push_back(bound);
+
+ sel = gfx::SelectionModel(3, 2, gfx::SelectionModel::TRAILING);
+ bound = GetCursorBounds(sel);
+ sel = gfx::SelectionModel(3, 3, gfx::SelectionModel::LEADING);
+ EXPECT_EQ(bound.x(), GetCursorBounds(sel).x());
+ cursor_bounds.push_back(bound);
+
+ sel = gfx::SelectionModel(2, 2, gfx::SelectionModel::LEADING);
+ bound = GetCursorBounds(sel);
+ sel = gfx::SelectionModel(4, 2, gfx::SelectionModel::LEADING);
+ EXPECT_EQ(bound.x(), GetCursorBounds(sel).x());
+ cursor_bounds.push_back(bound);
+
+ // Expected cursor position when clicking left and right of each character.
+ size_t cursor_pos_expected[] = {0, 1, 1, 2, 4, 3, 3, 2};
+
+ int index = 0;
+ for (int i = 0; i < static_cast<int>(cursor_bounds.size() - 1); ++i) {
+ int half_width = (cursor_bounds[i + 1].x() - cursor_bounds[i].x()) / 2;
+ MouseClick(cursor_bounds[i], half_width / 2);
+ EXPECT_EQ(cursor_pos_expected[index++], textfield_->GetCursorPosition());
+
+ // To avoid trigger double click. Not using sleep() since it takes longer
+ // for the test to run if using sleep().
+ NonClientMouseClick();
+
+ MouseClick(cursor_bounds[i + 1], - (half_width / 2));
+ EXPECT_EQ(cursor_pos_expected[index++], textfield_->GetCursorPosition());
+
+ NonClientMouseClick();
+ }
+}
+
+TEST_F(NativeTextfieldViewsTest, HitOutsideTextAreaTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+
+ // LTR-RTL string in LTR context.
+ textfield_->SetText(WideToUTF16(L"ab\x05E1\x5E2"));
+
+ SendKeyEvent(ui::VKEY_HOME);
+ gfx::Rect bound = GetCursorBounds();
+ MouseClick(bound, -10);
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ SendKeyEvent(ui::VKEY_END);
+ bound = GetCursorBounds();
+ MouseClick(bound, 10);
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ NonClientMouseClick();
+
+ // RTL-LTR string in LTR context.
+ textfield_->SetText(WideToUTF16(L"\x05E1\x5E2"L"ab"));
+
+ SendKeyEvent(ui::VKEY_HOME);
+ bound = GetCursorBounds();
+#if defined(OS_WIN)
+ MouseClick(bound, -10);
+#else
+ MouseClick(bound, 10);
+#endif
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ SendKeyEvent(ui::VKEY_END);
+ bound = GetCursorBounds();
+#if defined(OS_WIN)
+ MouseClick(bound, 10);
+#else
+ MouseClick(bound, -10);
+#endif
+ EXPECT_EQ(bound, GetCursorBounds());
+}
+
+TEST_F(NativeTextfieldViewsTest, HitOutsideTextAreaInRTLTest) {
+ std::string locale = l10n_util::GetApplicationLocale("");
+ base::i18n::SetICUDefaultLocale("he");
+
+ InitTextfield(Textfield::STYLE_DEFAULT);
+
+ // RTL-LTR string in RTL context.
+ textfield_->SetText(WideToUTF16(L"\x05E1\x5E2"L"ab"));
+ SendKeyEvent(ui::VKEY_HOME);
+ gfx::Rect bound = GetCursorBounds();
+ MouseClick(bound, 10);
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ SendKeyEvent(ui::VKEY_END);
+ bound = GetCursorBounds();
+ MouseClick(bound, -10);
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ NonClientMouseClick();
+
+ // LTR-RTL string in RTL context.
+ textfield_->SetText(WideToUTF16(L"ab\x05E1\x5E2"));
+ SendKeyEvent(ui::VKEY_HOME);
+ bound = GetCursorBounds();
+#if defined(OS_WIN)
+ MouseClick(bound, 10);
+#else
+ MouseClick(bound, -10);
+#endif
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ SendKeyEvent(ui::VKEY_END);
+ bound = GetCursorBounds();
+#if defined(OS_WIN)
+ MouseClick(bound, -10);
+#else
+ MouseClick(bound, 10);
+#endif
+ EXPECT_EQ(bound, GetCursorBounds());
+
+ // Reset locale.
+ base::i18n::SetICUDefaultLocale(locale);
+}
+
+// This verifies that |bound| is contained by |display|. |bound|'s right edge
+// must be less than |diaplay|'s right edge.
+void OverflowCursorBoundTestVerifier(const gfx::Rect& display,
+ const gfx::Rect& bound) {
+ EXPECT_LE(display.x(), bound.x());
+ EXPECT_GT(display.right(), bound.right());
+ EXPECT_LE(display.y(), bound.y());
+ EXPECT_GE(display.bottom(), bound.bottom());
+}
+
+TEST_F(NativeTextfieldViewsTest, OverflowTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+
+ string16 str;
+ for (int i = 0; i < 500; ++i)
+ SendKeyEvent('a');
+ SendKeyEvent(kHebrewLetterSamekh);
+ gfx::Rect bound = GetCursorBounds();
+ gfx::Rect display = GetDisplayRect();
+ OverflowCursorBoundTestVerifier(display, bound);
+
+ // Test mouse pointing.
+ MouseClick(bound, -1);
+ EXPECT_EQ(500U, textfield_->GetCursorPosition());
+
+ // Clear text.
+ SendKeyEvent(ui::VKEY_A, false, true);
+ SendKeyEvent('\n');
+
+ for (int i = 0; i < 500; ++i)
+ SendKeyEvent(kHebrewLetterSamekh);
+ SendKeyEvent('a');
+ bound = GetCursorBounds();
+ display = GetDisplayRect();
+ OverflowCursorBoundTestVerifier(display, bound);
+
+ MouseClick(bound, -1);
+ EXPECT_EQ(501U, textfield_->GetCursorPosition());
+}
+
+TEST_F(NativeTextfieldViewsTest, OverflowInRTLTest) {
+ std::string locale = l10n_util::GetApplicationLocale("");
+ base::i18n::SetICUDefaultLocale("he");
+
+ InitTextfield(Textfield::STYLE_DEFAULT);
+
+ string16 str;
+ for (int i = 0; i < 500; ++i)
+ SendKeyEvent('a');
+ SendKeyEvent(kHebrewLetterSamekh);
+ gfx::Rect bound = GetCursorBounds();
+ gfx::Rect display = GetDisplayRect();
+ OverflowCursorBoundTestVerifier(display, bound);
+
+ MouseClick(bound, 1);
+ EXPECT_EQ(501U, textfield_->GetCursorPosition());
+
+ // Clear text.
+ SendKeyEvent(ui::VKEY_A, false, true);
+ SendKeyEvent('\n');
+
+ for (int i = 0; i < 500; ++i)
+ SendKeyEvent(kHebrewLetterSamekh);
+ SendKeyEvent('a');
+ bound = GetCursorBounds();
+ display = GetDisplayRect();
+ OverflowCursorBoundTestVerifier(display, bound);
+
+
+#if !defined(OS_WIN)
+ // TODO(jennyz): NonClientMouseClick() does not work for os_win builds;
+ // see crbug.com/104150. The mouse click in the next test will be confused
+ // as a double click, which breaks the test. Disable the test until the
+ // issue is fixed.
+ NonClientMouseClick();
+
+ MouseClick(bound, 1);
+#if defined(OS_WIN)
+ // In Windows, the text is always in LTR directionality even in RTL UI.
+ // TODO(xji): it should change if we fix the directionality in Window's
+ // NativeTextfieldViews
+ EXPECT_EQ(0U, textfield_->GetCursorPosition());
+#else
+ EXPECT_EQ(500U, textfield_->GetCursorPosition());
+#endif
+#endif // !defined(OS_WIN)
+
+ // Reset locale.
+ base::i18n::SetICUDefaultLocale(locale);
+}
+
+} // namespace views
diff --git a/ui/views/controls/textfield/native_textfield_wayland.cc b/ui/views/controls/textfield/native_textfield_wayland.cc
new file mode 100644
index 0000000..63d02c7
--- /dev/null
+++ b/ui/views/controls/textfield/native_textfield_wayland.cc
@@ -0,0 +1,20 @@
+// 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.
+
+#include "base/logging.h"
+#include "ui/views/controls/textfield/native_textfield_views.h"
+#include "ui/views/controls/textfield/textfield.h"
+
+namespace views {
+
+///////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldWrapper:
+
+// static
+NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper(
+ Textfield* field) {
+ return new NativeTextfieldViews(field);
+}
+
+} // namespace views
diff --git a/ui/views/controls/textfield/native_textfield_win.cc b/ui/views/controls/textfield/native_textfield_win.cc
new file mode 100644
index 0000000..0741fd6
--- /dev/null
+++ b/ui/views/controls/textfield/native_textfield_win.cc
@@ -0,0 +1,1168 @@
+// 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.
+
+#include "ui/views/controls/textfield/native_textfield_win.h"
+
+#include <algorithm>
+
+#include "base/i18n/case_conversion.h"
+#include "base/i18n/rtl.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "base/win/windows_version.h"
+#include "grit/ui_strings.h"
+#include "skia/ext/skia_utils_win.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+#include "ui/base/keycodes/keyboard_code_conversion_win.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/l10n_util_win.h"
+#include "ui/base/range/range.h"
+#include "ui/base/win/mouse_wheel_util.h"
+#include "ui/gfx/native_theme_win.h"
+#include "ui/views/controls/native/native_view_host.h"
+#include "ui/views/controls/textfield/native_textfield_views.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/focus/focus_manager.h"
+#include "ui/views/widget/widget.h"
+#include "views/controls/label.h"
+#include "views/controls/menu/menu_2.h"
+#include "views/controls/menu/menu_win.h"
+#include "views/metrics.h"
+#include "views/views_delegate.h"
+
+namespace views {
+
+///////////////////////////////////////////////////////////////////////////////
+// Helper classes
+
+NativeTextfieldWin::ScopedFreeze::ScopedFreeze(NativeTextfieldWin* edit,
+ ITextDocument* text_object_model)
+ : edit_(edit),
+ text_object_model_(text_object_model) {
+ // Freeze the screen.
+ if (text_object_model_) {
+ long count;
+ text_object_model_->Freeze(&count);
+ }
+}
+
+NativeTextfieldWin::ScopedFreeze::~ScopedFreeze() {
+ // Unfreeze the screen.
+ if (text_object_model_) {
+ long count;
+ text_object_model_->Unfreeze(&count);
+ if (count == 0) {
+ // We need to UpdateWindow() here instead of InvalidateRect() because, as
+ // far as I can tell, the edit likes to synchronously erase its background
+ // when unfreezing, thus requiring us to synchronously redraw if we don't
+ // want flicker.
+ edit_->UpdateWindow();
+ }
+ }
+}
+
+NativeTextfieldWin::ScopedSuspendUndo::ScopedSuspendUndo(
+ ITextDocument* text_object_model)
+ : text_object_model_(text_object_model) {
+ // Suspend Undo processing.
+ if (text_object_model_)
+ text_object_model_->Undo(tomSuspend, NULL);
+}
+
+NativeTextfieldWin::ScopedSuspendUndo::~ScopedSuspendUndo() {
+ // Resume Undo processing.
+ if (text_object_model_)
+ text_object_model_->Undo(tomResume, NULL);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldWin
+
+bool NativeTextfieldWin::did_load_library_ = false;
+
+NativeTextfieldWin::NativeTextfieldWin(Textfield* textfield)
+ : textfield_(textfield),
+ tracking_double_click_(false),
+ double_click_time_(0),
+ can_discard_mousemove_(false),
+ contains_mouse_(false),
+ ime_discard_composition_(false),
+ ime_composition_start_(0),
+ ime_composition_length_(0),
+ container_view_(new NativeViewHost),
+ bg_color_(0) {
+ if (!did_load_library_)
+ did_load_library_ = !!LoadLibrary(L"riched20.dll");
+
+ DWORD style = kDefaultEditStyle | ES_AUTOHSCROLL;
+ if (textfield_->style() & Textfield::STYLE_PASSWORD)
+ style |= ES_PASSWORD;
+
+ if (textfield_->read_only())
+ style |= ES_READONLY;
+
+ // Make sure we apply RTL related extended window styles if necessary.
+ DWORD ex_style = l10n_util::GetExtendedStyles();
+
+ RECT r = {0, 0, textfield_->width(), textfield_->height()};
+ Create(textfield_->GetWidget()->GetNativeView(), r, NULL, style, ex_style);
+
+ if (textfield_->style() & Textfield::STYLE_LOWERCASE) {
+ DCHECK((textfield_->style() & Textfield::STYLE_PASSWORD) == 0);
+ SetEditStyle(SES_LOWERCASE, SES_LOWERCASE);
+ }
+
+ // Set up the text_object_model_.
+ base::win::ScopedComPtr<IRichEditOle, &IID_IRichEditOle> ole_interface;
+ ole_interface.Attach(GetOleInterface());
+ if (ole_interface)
+ text_object_model_.QueryFrom(ole_interface);
+
+ InitializeAccessibilityInfo();
+}
+
+NativeTextfieldWin::~NativeTextfieldWin() {
+ if (IsWindow())
+ DestroyWindow();
+}
+
+// static
+bool NativeTextfieldWin::IsDoubleClick(const POINT& origin,
+ const POINT& current,
+ DWORD elapsed_time) {
+ // The CXDOUBLECLK and CYDOUBLECLK system metrics describe the width and
+ // height of a rectangle around the origin position, inside of which clicks
+ // within the double click time are considered double clicks.
+ return (elapsed_time <= GetDoubleClickTime()) &&
+ (abs(current.x - origin.x) <= (GetSystemMetrics(SM_CXDOUBLECLK) / 2)) &&
+ (abs(current.y - origin.y) <= (GetSystemMetrics(SM_CYDOUBLECLK) / 2));
+}
+
+// static
+bool NativeTextfieldWin::IsNumPadDigit(int key_code, bool extended_key) {
+ if (key_code >= VK_NUMPAD0 && key_code <= VK_NUMPAD9)
+ return true;
+
+ // Check for num pad keys without NumLock.
+ // Note: there is no easy way to know if a the key that was pressed comes from
+ // the num pad or the rest of the keyboard. Investigating how
+ // TranslateMessage() generates the WM_KEYCHAR from an
+ // ALT + <NumPad sequences> it appears it looks at the extended key flag
+ // (which is on if the key pressed comes from one of the 3 clusters to
+ // the left of the numeric keypad). So we use it as well.
+ return !extended_key &&
+ ((key_code >= VK_PRIOR && key_code <= VK_DOWN) || // All keys but 5
+ // and 0.
+ (key_code == VK_CLEAR) || // Key 5.
+ (key_code == VK_INSERT)); // Key 0.
+}
+
+void NativeTextfieldWin::AttachHack() {
+ // See the code in textfield.cc that calls this for why this is here.
+ container_view_->set_focus_view(textfield_);
+ container_view_->Attach(m_hWnd);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldWin, NativeTextfieldWrapper implementation:
+
+string16 NativeTextfieldWin::GetText() const {
+ int len = GetTextLength() + 1;
+ if (len <= 1)
+ return string16();
+
+ string16 str;
+ GetWindowText(WriteInto(&str, len), len);
+ // The text get from GetWindowText() might be wrapped with explicit bidi
+ // control characters. Refer to UpdateText() for detail. Without such
+ // wrapping, in RTL chrome, a pure LTR string ending with parenthesis will
+ // not be displayed correctly in a textfield. For example, "Yahoo!" will be
+ // displayed as "!Yahoo", and "Google (by default)" will be displayed as
+ // "(Google (by default".
+ return base::i18n::StripWrappingBidiControlCharacters(WideToUTF16(str));
+}
+
+void NativeTextfieldWin::UpdateText() {
+ string16 text = textfield_->text();
+ // Adjusting the string direction before setting the text in order to make
+ // sure both RTL and LTR strings are displayed properly.
+ base::i18n::AdjustStringForLocaleDirection(&text);
+ if (textfield_->style() & Textfield::STYLE_LOWERCASE)
+ text = base::i18n::ToLower(text);
+ SetWindowText(text.c_str());
+ UpdateAccessibleValue(text);
+}
+
+void NativeTextfieldWin::AppendText(const string16& text) {
+ int text_length = GetWindowTextLength();
+ ::SendMessage(m_hWnd, TBM_SETSEL, true, MAKELPARAM(text_length, text_length));
+ ::SendMessage(m_hWnd, EM_REPLACESEL, false,
+ reinterpret_cast<LPARAM>(text.c_str()));
+}
+
+string16 NativeTextfieldWin::GetSelectedText() const {
+ // Figure out the length of the selection.
+ CHARRANGE sel;
+ GetSel(sel);
+ if (sel.cpMin == sel.cpMax) // GetSelText() crashes on NULL input.
+ return string16();
+
+ // Grab the selected text.
+ string16 str;
+ GetSelText(WriteInto(&str, sel.cpMax - sel.cpMin + 1));
+ return str;
+}
+
+void NativeTextfieldWin::SelectAll() {
+ // Select from the end to the front so that the first part of the text is
+ // always visible.
+ SetSel(GetTextLength(), 0);
+}
+
+void NativeTextfieldWin::ClearSelection() {
+ SetSel(GetTextLength(), GetTextLength());
+}
+
+void NativeTextfieldWin::UpdateBorder() {
+ SetWindowPos(NULL, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_FRAMECHANGED | SWP_NOACTIVATE |
+ SWP_NOOWNERZORDER | SWP_NOSIZE);
+}
+
+void NativeTextfieldWin::UpdateTextColor() {
+ CHARFORMAT cf = {0};
+ cf.dwMask = CFM_COLOR;
+ cf.crTextColor = textfield_->use_default_text_color() ?
+ GetSysColor(textfield_->read_only() ? COLOR_GRAYTEXT : COLOR_WINDOWTEXT) :
+ skia::SkColorToCOLORREF(textfield_->text_color());
+ CRichEditCtrl::SetDefaultCharFormat(cf);
+}
+
+void NativeTextfieldWin::UpdateBackgroundColor() {
+ if (!textfield_->use_default_background_color()) {
+ bg_color_ = skia::SkColorToCOLORREF(textfield_->background_color());
+ } else {
+ bg_color_ = GetSysColor(textfield_->read_only() ? COLOR_3DFACE
+ : COLOR_WINDOW);
+ }
+ CRichEditCtrl::SetBackgroundColor(bg_color_);
+}
+
+void NativeTextfieldWin::UpdateReadOnly() {
+ SendMessage(m_hWnd, EM_SETREADONLY, textfield_->read_only(), 0);
+ UpdateAccessibleState(STATE_SYSTEM_READONLY, textfield_->read_only());
+}
+
+void NativeTextfieldWin::UpdateFont() {
+ SendMessage(m_hWnd, WM_SETFONT,
+ reinterpret_cast<WPARAM>(textfield_->font().GetNativeFont()),
+ TRUE);
+ // Setting the font blows away any text color we've set, so reset it.
+ UpdateTextColor();
+}
+
+void NativeTextfieldWin::UpdateIsPassword() {
+ // TODO: Need to implement for Windows.
+ UpdateAccessibleState(STATE_SYSTEM_PROTECTED, textfield_->IsPassword());
+}
+
+void NativeTextfieldWin::UpdateEnabled() {
+ SendMessage(m_hWnd, WM_ENABLE, textfield_->IsEnabled(), 0);
+ UpdateAccessibleState(STATE_SYSTEM_UNAVAILABLE, !textfield_->IsEnabled());
+}
+
+gfx::Insets NativeTextfieldWin::CalculateInsets() {
+ // NOTE: One would think GetThemeMargins would return the insets we should
+ // use, but it doesn't. The margins returned by GetThemeMargins are always
+ // 0.
+
+ // This appears to be the insets used by Windows.
+ return gfx::Insets(3, 3, 3, 3);
+}
+
+void NativeTextfieldWin::UpdateHorizontalMargins() {
+ int left, right;
+ if (!textfield_->GetHorizontalMargins(&left, &right))
+ return;
+
+ // SendMessage expects the two values to be packed into one using MAKELONG
+ // so we truncate to 16 bits if necessary.
+ SendMessage(m_hWnd, EM_SETMARGINS,
+ EC_LEFTMARGIN | EC_RIGHTMARGIN,
+ MAKELONG(left & 0xFFFF, right & 0xFFFF));
+}
+
+void NativeTextfieldWin::UpdateVerticalMargins() {
+ int top, bottom;
+ if (!textfield_->GetVerticalMargins(&top, &bottom))
+ return;
+
+ if (top == 0 && bottom == 0) {
+ // Do nothing, default margins are 0 already.
+ return;
+ }
+ // Non-zero margins case.
+ NOTIMPLEMENTED();
+}
+
+bool NativeTextfieldWin::SetFocus() {
+ // Focus the associated HWND.
+ //container_view_->Focus();
+ ::SetFocus(m_hWnd);
+ return true;
+}
+
+View* NativeTextfieldWin::GetView() {
+ return container_view_;
+}
+
+gfx::NativeView NativeTextfieldWin::GetTestingHandle() const {
+ return m_hWnd;
+}
+
+bool NativeTextfieldWin::IsIMEComposing() const {
+ // Retrieve the length of the composition string to check if an IME is
+ // composing text. (If this length is > 0 then an IME is being used to compose
+ // text.)
+ HIMC imm_context = ImmGetContext(m_hWnd);
+ if (!imm_context)
+ return false;
+
+ const int composition_size = ImmGetCompositionString(imm_context, GCS_COMPSTR,
+ NULL, 0);
+ ImmReleaseContext(m_hWnd, imm_context);
+ return composition_size > 0;
+}
+
+void NativeTextfieldWin::GetSelectedRange(ui::Range* range) const {
+ NOTREACHED();
+}
+
+void NativeTextfieldWin::SelectRange(const ui::Range& range) {
+ NOTREACHED();
+}
+
+void NativeTextfieldWin::GetSelectionModel(gfx::SelectionModel* sel) const {
+ NOTREACHED();
+}
+
+void NativeTextfieldWin::SelectSelectionModel(const gfx::SelectionModel& sel) {
+ NOTREACHED();
+}
+
+size_t NativeTextfieldWin::GetCursorPosition() const {
+ NOTREACHED();
+ return 0U;
+}
+
+bool NativeTextfieldWin::HandleKeyPressed(const views::KeyEvent& event) {
+ return false;
+}
+
+bool NativeTextfieldWin::HandleKeyReleased(const views::KeyEvent& event) {
+ return false;
+}
+
+void NativeTextfieldWin::HandleFocus() {
+}
+
+void NativeTextfieldWin::HandleBlur() {
+}
+
+ui::TextInputClient* NativeTextfieldWin::GetTextInputClient() {
+ return NULL;
+}
+
+void NativeTextfieldWin::ApplyStyleRange(const gfx::StyleRange& style) {
+ NOTREACHED();
+}
+
+void NativeTextfieldWin::ApplyDefaultStyle() {
+ NOTREACHED();
+}
+
+void NativeTextfieldWin::ClearEditHistory() {
+ NOTREACHED();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldWin, ui::SimpleMenuModel::Delegate implementation:
+
+bool NativeTextfieldWin::IsCommandIdChecked(int command_id) const {
+ return false;
+}
+
+bool NativeTextfieldWin::IsCommandIdEnabled(int command_id) const {
+ switch (command_id) {
+ case IDS_APP_UNDO: return !textfield_->read_only() && !!CanUndo();
+ case IDS_APP_CUT: return !textfield_->read_only() &&
+ !textfield_->IsPassword() && !!CanCut();
+ case IDS_APP_COPY: return !!CanCopy() && !textfield_->IsPassword();
+ case IDS_APP_PASTE: return !textfield_->read_only() && !!CanPaste();
+ case IDS_APP_SELECT_ALL: return !!CanSelectAll();
+ default: NOTREACHED();
+ return false;
+ }
+}
+
+bool NativeTextfieldWin::GetAcceleratorForCommandId(int command_id,
+ ui::Accelerator* accelerator) {
+ // The standard Ctrl-X, Ctrl-V and Ctrl-C are not defined as accelerators
+ // anywhere so we need to check for them explicitly here.
+ switch (command_id) {
+ case IDS_APP_CUT:
+ *accelerator = ui::Accelerator(ui::VKEY_X, false, true, false);
+ return true;
+ case IDS_APP_COPY:
+ *accelerator = ui::Accelerator(ui::VKEY_C, false, true, false);
+ return true;
+ case IDS_APP_PASTE:
+ *accelerator = ui::Accelerator(ui::VKEY_V, false, true, false);
+ return true;
+ }
+ return container_view_->GetWidget()->GetAccelerator(command_id, accelerator);
+}
+
+void NativeTextfieldWin::ExecuteCommand(int command_id) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ switch (command_id) {
+ case IDS_APP_UNDO: Undo(); break;
+ case IDS_APP_CUT: Cut(); break;
+ case IDS_APP_COPY: Copy(); break;
+ case IDS_APP_PASTE: Paste(); break;
+ case IDS_APP_SELECT_ALL: SelectAll(); break;
+ default: NOTREACHED(); break;
+ }
+ OnAfterPossibleChange(true);
+}
+
+void NativeTextfieldWin::InitializeAccessibilityInfo() {
+ // Set the accessible state.
+ accessibility_state_ = 0;
+
+ base::win::ScopedComPtr<IAccPropServices> pAccPropServices;
+ HRESULT hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER,
+ IID_IAccPropServices, reinterpret_cast<void**>(&pAccPropServices));
+ if (!SUCCEEDED(hr))
+ return;
+
+ VARIANT var;
+
+ // Set the accessible role.
+ var.vt = VT_I4;
+ var.lVal = ROLE_SYSTEM_TEXT;
+ hr = pAccPropServices->SetHwndProp(m_hWnd, OBJID_CLIENT,
+ CHILDID_SELF, PROPID_ACC_ROLE, var);
+
+ // Set the accessible name by getting the label text.
+ View* parent = textfield_->parent();
+ int label_index = parent->GetIndexOf(textfield_) - 1;
+ if (label_index >= 0) {
+ // Try to find the name of this text field.
+ // We expect it to be a Label preceeding this view (if it exists).
+ string16 name;
+ View* label_view = parent->child_at(label_index);
+ if (label_view->GetClassName() == Label::kViewClassName) {
+ ui::AccessibleViewState state;
+ label_view->GetAccessibleState(&state);
+ hr = pAccPropServices->SetHwndPropStr(m_hWnd, OBJID_CLIENT,
+ CHILDID_SELF, PROPID_ACC_NAME, state.name.c_str());
+ }
+ }
+}
+
+void NativeTextfieldWin::UpdateAccessibleState(uint32 state_flag,
+ bool set_value) {
+ base::win::ScopedComPtr<IAccPropServices> pAccPropServices;
+ HRESULT hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER,
+ IID_IAccPropServices, reinterpret_cast<void**>(&pAccPropServices));
+ if (!SUCCEEDED(hr))
+ return;
+
+ VARIANT var;
+ var.vt = VT_I4;
+ var.lVal = set_value ? accessibility_state_ | state_flag
+ : accessibility_state_ & ~state_flag;
+ hr = pAccPropServices->SetHwndProp(m_hWnd, OBJID_CLIENT,
+ CHILDID_SELF, PROPID_ACC_STATE, var);
+
+ ::NotifyWinEvent(EVENT_OBJECT_STATECHANGE, m_hWnd, OBJID_CLIENT,
+ CHILDID_SELF);
+}
+
+void NativeTextfieldWin::UpdateAccessibleValue(const string16& value) {
+ base::win::ScopedComPtr<IAccPropServices> pAccPropServices;
+ HRESULT hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER,
+ IID_IAccPropServices, reinterpret_cast<void**>(&pAccPropServices));
+ if (!SUCCEEDED(hr))
+ return;
+
+ hr = pAccPropServices->SetHwndPropStr(m_hWnd, OBJID_CLIENT,
+ CHILDID_SELF, PROPID_ACC_VALUE, value.c_str());
+
+ ::NotifyWinEvent(EVENT_OBJECT_VALUECHANGE, m_hWnd, OBJID_CLIENT,
+ CHILDID_SELF);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldWin, private:
+
+void NativeTextfieldWin::OnChar(TCHAR ch, UINT repeat_count, UINT flags) {
+ HandleKeystroke();
+}
+
+void NativeTextfieldWin::OnContextMenu(HWND window, const POINT& point) {
+ POINT p(point);
+ if (point.x == -1 || point.y == -1) {
+ GetCaretPos(&p);
+ MapWindowPoints(HWND_DESKTOP, &p, 1);
+ }
+ BuildContextMenu();
+ context_menu_->RunContextMenuAt(gfx::Point(p));
+}
+
+void NativeTextfieldWin::OnCopy() {
+ if (textfield_->IsPassword())
+ return;
+
+ const string16 text(GetSelectedText());
+ if (!text.empty() && ViewsDelegate::views_delegate) {
+ ui::ScopedClipboardWriter scw(
+ ViewsDelegate::views_delegate->GetClipboard());
+ scw.WriteText(text);
+ }
+}
+
+void NativeTextfieldWin::OnCut() {
+ if (textfield_->read_only() || textfield_->IsPassword())
+ return;
+
+ OnCopy();
+
+ // This replace selection will have no effect (even on the undo stack) if the
+ // current selection is empty.
+ ReplaceSel(L"", true);
+}
+
+LRESULT NativeTextfieldWin::OnImeChar(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ // http://crbug.com/7707: a rich-edit control may crash when it receives a
+ // WM_IME_CHAR message while it is processing a WM_IME_COMPOSITION message.
+ // Since view controls don't need WM_IME_CHAR messages, we prevent WM_IME_CHAR
+ // messages from being dispatched to view controls via the CallWindowProc()
+ // call.
+ return 0;
+}
+
+LRESULT NativeTextfieldWin::OnImeStartComposition(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ // Users may press alt+shift or control+shift keys to change their keyboard
+ // layouts. So, we retrieve the input locale identifier everytime we start
+ // an IME composition.
+ int language_id = PRIMARYLANGID(GetKeyboardLayout(0));
+ ime_discard_composition_ =
+ language_id == LANG_JAPANESE || language_id == LANG_CHINESE;
+ ime_composition_start_ = 0;
+ ime_composition_length_ = 0;
+
+ return DefWindowProc(message, wparam, lparam);
+}
+
+LRESULT NativeTextfieldWin::OnImeComposition(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ text_before_change_.clear();
+ LRESULT result = DefWindowProc(message, wparam, lparam);
+
+ ime_composition_start_ = 0;
+ ime_composition_length_ = 0;
+ if (ime_discard_composition_) {
+ // Call IMM32 functions to retrieve the position and the length of the
+ // ongoing composition string and notify the OnAfterPossibleChange()
+ // function that it should discard the composition string from a search
+ // string. We should not call IMM32 functions in the function because it
+ // is called when an IME is not composing a string.
+ HIMC imm_context = ImmGetContext(m_hWnd);
+ if (imm_context) {
+ CHARRANGE selection;
+ GetSel(selection);
+ const int cursor_position =
+ ImmGetCompositionString(imm_context, GCS_CURSORPOS, NULL, 0);
+ if (cursor_position >= 0)
+ ime_composition_start_ = selection.cpMin - cursor_position;
+
+ const int composition_size =
+ ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0);
+ if (composition_size >= 0)
+ ime_composition_length_ = composition_size / sizeof(wchar_t);
+
+ ImmReleaseContext(m_hWnd, imm_context);
+ }
+ }
+
+ // If we allow OnAfterPossibleChange() to redraw the text, it will do this by
+ // setting the edit's text directly, which can cancel the current IME
+ // composition or cause other adverse affects. So we set |should_redraw_text|
+ // to false.
+ OnAfterPossibleChange(false);
+ return result;
+}
+
+LRESULT NativeTextfieldWin::OnImeEndComposition(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ // Bug 11863: Korean IMEs send a WM_IME_ENDCOMPOSITION message without
+ // sending any WM_IME_COMPOSITION messages when a user deletes all
+ // composition characters, i.e. a composition string becomes empty. To handle
+ // this case, we need to update the find results when a composition is
+ // finished or canceled.
+ textfield_->SyncText();
+ return DefWindowProc(message, wparam, lparam);
+}
+
+void NativeTextfieldWin::OnKeyDown(TCHAR key, UINT repeat_count, UINT flags) {
+ // NOTE: Annoyingly, ctrl-alt-<key> generates WM_KEYDOWN rather than
+ // WM_SYSKEYDOWN, so we need to check (flags & KF_ALTDOWN) in various places
+ // in this function even with a WM_SYSKEYDOWN handler.
+
+ switch (key) {
+
+ // Ignore Return
+ case VK_RETURN:
+ return;
+
+ // Hijacking Editing Commands
+ //
+ // We hijack the keyboard short-cuts for Cut, Copy, and Paste here so that
+ // they go through our clipboard routines. This allows us to be smarter
+ // about how we interact with the clipboard and avoid bugs in the
+ // CRichEditCtrl. If we didn't hijack here, the edit control would handle
+ // these internally with sending the WM_CUT, WM_COPY, or WM_PASTE messages.
+ //
+ // Cut: Shift-Delete and Ctrl-x are treated as cut. Ctrl-Shift-Delete and
+ // Ctrl-Shift-x are not treated as cut even though the underlying
+ // CRichTextEdit would treat them as such.
+ // Copy: Ctrl-c is treated as copy. Shift-Ctrl-c is not.
+ // Paste: Shift-Insert and Ctrl-v are tread as paste. Ctrl-Shift-Insert and
+ // Ctrl-Shift-v are not.
+ //
+ // This behavior matches most, but not all Windows programs, and largely
+ // conforms to what users expect.
+
+ case VK_DELETE:
+ case 'X':
+ if ((flags & KF_ALTDOWN) ||
+ (GetKeyState((key == 'X') ? VK_CONTROL : VK_SHIFT) >= 0))
+ break;
+ if (GetKeyState((key == 'X') ? VK_SHIFT : VK_CONTROL) >= 0) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ Cut();
+ OnAfterPossibleChange(true);
+ }
+ return;
+
+ case 'C':
+ if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0))
+ break;
+ if (GetKeyState(VK_SHIFT) >= 0)
+ Copy();
+ return;
+
+ case VK_INSERT:
+ // Ignore insert by itself, so we don't turn overtype mode on/off.
+ if (!(flags & KF_ALTDOWN) && (GetKeyState(VK_SHIFT) >= 0) &&
+ (GetKeyState(VK_CONTROL) >= 0))
+ return;
+ // Fall through to the next case (ie. Shift-Insert == Ctrl-V).
+ case 'V':
+ if ((flags & KF_ALTDOWN) ||
+ (GetKeyState((key == 'V') ? VK_CONTROL : VK_SHIFT) >= 0))
+ break;
+ if (GetKeyState((key == 'V') ? VK_SHIFT : VK_CONTROL) >= 0) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ Paste();
+ OnAfterPossibleChange(true);
+ }
+ return;
+
+ case 0xbb: // Ctrl-'='. Triggers subscripting, even in plain text mode.
+ // We don't use VK_OEM_PLUS in case the macro isn't defined.
+ // (e.g., we don't have this symbol in embeded environment).
+ return;
+
+ case VK_PROCESSKEY:
+ // This key event is consumed by an IME.
+ // We ignore this event because an IME sends WM_IME_COMPOSITION messages
+ // when it updates the CRichEditCtrl text.
+ return;
+ }
+
+ // CRichEditCtrl changes its text on WM_KEYDOWN instead of WM_CHAR for many
+ // different keys (backspace, ctrl-v, ...), so we call this in both cases.
+ HandleKeystroke();
+}
+
+void NativeTextfieldWin::OnLButtonDblClk(UINT keys, const CPoint& point) {
+ // Save the double click info for later triple-click detection.
+ tracking_double_click_ = true;
+ double_click_point_ = point;
+ double_click_time_ = GetCurrentMessage()->time;
+
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ DefWindowProc(WM_LBUTTONDBLCLK, keys,
+ MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y));
+ OnAfterPossibleChange(true);
+}
+
+void NativeTextfieldWin::OnLButtonDown(UINT keys, const CPoint& point) {
+ // Check for triple click, then reset tracker. Should be safe to subtract
+ // double_click_time_ from the current message's time even if the timer has
+ // wrapped in between.
+ const bool is_triple_click = tracking_double_click_ &&
+ IsDoubleClick(double_click_point_, point,
+ GetCurrentMessage()->time - double_click_time_);
+ tracking_double_click_ = false;
+
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ DefWindowProc(WM_LBUTTONDOWN, keys,
+ MAKELPARAM(ClipXCoordToVisibleText(point.x, is_triple_click),
+ point.y));
+ OnAfterPossibleChange(true);
+}
+
+void NativeTextfieldWin::OnLButtonUp(UINT keys, const CPoint& point) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ DefWindowProc(WM_LBUTTONUP, keys,
+ MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y));
+ OnAfterPossibleChange(true);
+}
+
+void NativeTextfieldWin::OnMouseLeave() {
+ SetContainsMouse(false);
+}
+
+LRESULT NativeTextfieldWin::OnMouseWheel(UINT message,
+ WPARAM w_param,
+ LPARAM l_param) {
+ // Reroute the mouse-wheel to the window under the mouse pointer if
+ // applicable.
+ if (ui::RerouteMouseWheel(m_hWnd, w_param, l_param))
+ return 0;
+ return DefWindowProc(message, w_param, l_param);
+}
+
+void NativeTextfieldWin::OnMouseMove(UINT keys, const CPoint& point) {
+ SetContainsMouse(true);
+ // Clamp the selection to the visible text so the user can't drag to select
+ // the "phantom newline". In theory we could achieve this by clipping the X
+ // coordinate, but in practice the edit seems to behave nondeterministically
+ // with similar sequences of clipped input coordinates fed to it. Maybe it's
+ // reading the mouse cursor position directly?
+ //
+ // This solution has a minor visual flaw, however: if there's a visible
+ // cursor at the edge of the text (only true when there's no selection),
+ // dragging the mouse around outside that edge repaints the cursor on every
+ // WM_MOUSEMOVE instead of allowing it to blink normally. To fix this, we
+ // special-case this exact case and discard the WM_MOUSEMOVE messages instead
+ // of passing them along.
+ //
+ // But even this solution has a flaw! (Argh.) In the case where the user
+ // has a selection that starts at the edge of the edit, and proceeds to the
+ // middle of the edit, and the user is dragging back past the start edge to
+ // remove the selection, there's a redraw problem where the change between
+ // having the last few bits of text still selected and having nothing
+ // selected can be slow to repaint (which feels noticeably strange). This
+ // occurs if you only let the edit receive a single WM_MOUSEMOVE past the
+ // edge of the text. I think on each WM_MOUSEMOVE the edit is repainting its
+ // previous state, then updating its internal variables to the new state but
+ // not repainting. To fix this, we allow one more WM_MOUSEMOVE through after
+ // the selection has supposedly been shrunk to nothing; this makes the edit
+ // redraw the selection quickly so it feels smooth.
+ CHARRANGE selection;
+ GetSel(selection);
+ const bool possibly_can_discard_mousemove =
+ (selection.cpMin == selection.cpMax) &&
+ (((selection.cpMin == 0) &&
+ (ClipXCoordToVisibleText(point.x, false) > point.x)) ||
+ ((selection.cpMin == GetTextLength()) &&
+ (ClipXCoordToVisibleText(point.x, false) < point.x)));
+ if (!can_discard_mousemove_ || !possibly_can_discard_mousemove) {
+ can_discard_mousemove_ = possibly_can_discard_mousemove;
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ // Force the Y coordinate to the center of the clip rect. The edit
+ // behaves strangely when the cursor is dragged vertically: if the cursor
+ // is in the middle of the text, drags inside the clip rect do nothing,
+ // and drags outside the clip rect act as if the cursor jumped to the
+ // left edge of the text. When the cursor is at the right edge, drags of
+ // just a few pixels vertically end up selecting the "phantom newline"...
+ // sometimes.
+ RECT r;
+ GetRect(&r);
+ DefWindowProc(WM_MOUSEMOVE, keys,
+ MAKELPARAM(point.x, (r.bottom - r.top) / 2));
+ OnAfterPossibleChange(true);
+ }
+}
+
+int NativeTextfieldWin::OnNCCalcSize(BOOL w_param, LPARAM l_param) {
+ content_insets_.Set(0, 0, 0, 0);
+ if (textfield_->draw_border())
+ content_insets_ = CalculateInsets();
+ if (w_param) {
+ NCCALCSIZE_PARAMS* nc_params =
+ reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param);
+ nc_params->rgrc[0].left += content_insets_.left();
+ nc_params->rgrc[0].right -= content_insets_.right();
+ nc_params->rgrc[0].top += content_insets_.top();
+ nc_params->rgrc[0].bottom -= content_insets_.bottom();
+ } else {
+ RECT* rect = reinterpret_cast<RECT*>(l_param);
+ rect->left += content_insets_.left();
+ rect->right -= content_insets_.right();
+ rect->top += content_insets_.top();
+ rect->bottom -= content_insets_.bottom();
+ }
+ return 0;
+}
+
+void NativeTextfieldWin::OnNCPaint(HRGN region) {
+ if (!textfield_->draw_border())
+ return;
+
+ HDC hdc = GetWindowDC();
+
+ CRect window_rect;
+ GetWindowRect(&window_rect);
+ // Convert to be relative to 0x0.
+ window_rect.MoveToXY(0, 0);
+
+ ExcludeClipRect(hdc,
+ window_rect.left + content_insets_.left(),
+ window_rect.top + content_insets_.top(),
+ window_rect.right - content_insets_.right(),
+ window_rect.bottom - content_insets_.bottom());
+
+ HBRUSH brush = CreateSolidBrush(bg_color_);
+ FillRect(hdc, &window_rect, brush);
+ DeleteObject(brush);
+
+ int part;
+ int state;
+
+ if (base::win::GetVersion() < base::win::VERSION_VISTA) {
+ part = EP_EDITTEXT;
+
+ if (!textfield_->IsEnabled())
+ state = ETS_DISABLED;
+ else if (textfield_->read_only())
+ state = ETS_READONLY;
+ else if (!contains_mouse_)
+ state = ETS_NORMAL;
+ else
+ state = ETS_HOT;
+ } else {
+ part = EP_EDITBORDER_HVSCROLL;
+
+ if (!textfield_->IsEnabled())
+ state = EPSHV_DISABLED;
+ else if (GetFocus() == m_hWnd)
+ state = EPSHV_FOCUSED;
+ else if (contains_mouse_)
+ state = EPSHV_HOT;
+ else
+ state = EPSHV_NORMAL;
+ // Vista doesn't appear to have a unique state for readonly.
+ }
+
+ int classic_state =
+ (!textfield_->IsEnabled() || textfield_->read_only()) ? DFCS_INACTIVE : 0;
+
+ gfx::NativeThemeWin::instance()->PaintTextField(hdc, part, state,
+ classic_state, &window_rect,
+ bg_color_, false, true);
+
+ // NOTE: I tried checking the transparent property of the theme and invoking
+ // drawParentBackground, but it didn't seem to make a difference.
+
+ ReleaseDC(hdc);
+}
+
+void NativeTextfieldWin::OnNonLButtonDown(UINT keys, const CPoint& point) {
+ // Interestingly, the edit doesn't seem to cancel triple clicking when the
+ // x-buttons (which usually means "thumb buttons") are pressed, so we only
+ // call this for M and R down.
+ tracking_double_click_ = false;
+ SetMsgHandled(false);
+}
+
+void NativeTextfieldWin::OnPaste() {
+ if (textfield_->read_only() || !ViewsDelegate::views_delegate)
+ return;
+
+ ui::Clipboard* clipboard = ViewsDelegate::views_delegate->GetClipboard();
+ if (!clipboard->IsFormatAvailable(ui::Clipboard::GetPlainTextWFormatType(),
+ ui::Clipboard::BUFFER_STANDARD))
+ return;
+
+ string16 clipboard_str;
+ clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &clipboard_str);
+ if (!clipboard_str.empty()) {
+ string16 collapsed(CollapseWhitespace(clipboard_str, false));
+ if (textfield_->style() & Textfield::STYLE_LOWERCASE)
+ collapsed = base::i18n::ToLower(collapsed);
+ // Force a Paste operation to trigger ContentsChanged, even if identical
+ // contents are pasted into the text box. See http://crbug.com/79002
+ ReplaceSel(L"", false);
+ textfield_->SyncText();
+ text_before_change_.clear();
+ ReplaceSel(collapsed.c_str(), true);
+ }
+}
+
+void NativeTextfieldWin::OnSetFocus(HWND hwnd) {
+ SetMsgHandled(FALSE); // We still want the default processing of the message.
+
+ views::FocusManager* focus_manager = textfield_->GetFocusManager();
+ if (!focus_manager) {
+ NOTREACHED();
+ return;
+ }
+ focus_manager->SetFocusedView(textfield_);
+}
+
+void NativeTextfieldWin::OnSysChar(TCHAR ch, UINT repeat_count, UINT flags) {
+ // Nearly all alt-<xxx> combos result in beeping rather than doing something
+ // useful, so we discard most. Exceptions:
+ // * ctrl-alt-<xxx>, which is sometimes important, generates WM_CHAR instead
+ // of WM_SYSCHAR, so it doesn't need to be handled here.
+ // * alt-space gets translated by the default WM_SYSCHAR handler to a
+ // WM_SYSCOMMAND to open the application context menu, so we need to allow
+ // it through.
+ if (ch == VK_SPACE)
+ SetMsgHandled(false);
+}
+
+void NativeTextfieldWin::HandleKeystroke() {
+ const MSG* msg = GetCurrentMessage();
+ ScopedFreeze freeze(this, GetTextObjectModel());
+
+ TextfieldController* controller = textfield_->GetController();
+ bool handled = false;
+ if (controller) {
+ KeyEvent event(*msg);
+ handled = controller->HandleKeyEvent(textfield_, event);
+ }
+
+ if (!handled) {
+ OnBeforePossibleChange();
+
+ if (msg->wParam == ui::VKEY_HOME || msg->wParam == ui::VKEY_END) {
+ // DefWindowProc() might reset the keyboard layout when it receives a
+ // keydown event for VKEY_HOME or VKEY_END. When the window was created
+ // with WS_EX_LAYOUTRTL and the current keyboard layout is not a RTL one,
+ // if the input text is pure LTR text, the layout changes to the first RTL
+ // keyboard layout in keyboard layout queue; if the input text is
+ // bidirectional text, the layout changes to the keyboard layout of the
+ // first RTL character in input text. When the window was created without
+ // WS_EX_LAYOUTRTL and the current keyboard layout is not a LTR one, if
+ // the input text is pure RTL text, the layout changes to English; if the
+ // input text is bidirectional text, the layout changes to the keyboard
+ // layout of the first LTR character in input text. Such keyboard layout
+ // change behavior is surprising and inconsistent with keyboard behavior
+ // elsewhere, so reset the layout in this case.
+ HKL layout = GetKeyboardLayout(0);
+ DefWindowProc(msg->message, msg->wParam, msg->lParam);
+ ActivateKeyboardLayout(layout, KLF_REORDER);
+ } else {
+ DefWindowProc(msg->message, msg->wParam, msg->lParam);
+ }
+
+ // CRichEditCtrl automatically turns on IMF_AUTOKEYBOARD when the user
+ // inputs an RTL character, making it difficult for the user to control
+ // what language is set as they type. Force this off to make the edit's
+ // behavior more stable.
+ const int lang_options = SendMessage(EM_GETLANGOPTIONS, 0, 0);
+ if (lang_options & IMF_AUTOKEYBOARD)
+ SendMessage(EM_SETLANGOPTIONS, 0, lang_options & ~IMF_AUTOKEYBOARD);
+
+ OnAfterPossibleChange(true);
+ }
+}
+
+void NativeTextfieldWin::OnBeforePossibleChange() {
+ // Record our state.
+ text_before_change_ = GetText();
+}
+
+void NativeTextfieldWin::OnAfterPossibleChange(bool should_redraw_text) {
+ // Prevent the user from selecting the "phantom newline" at the end of the
+ // edit. If they try, we just silently move the end of the selection back to
+ // the end of the real text.
+ CHARRANGE new_sel;
+ GetSel(new_sel);
+ const int length = GetTextLength();
+ if (new_sel.cpMax > length) {
+ new_sel.cpMax = length;
+ if (new_sel.cpMin > length)
+ new_sel.cpMin = length;
+ SetSel(new_sel);
+ }
+
+ string16 new_text(GetText());
+ if (new_text != text_before_change_) {
+ if (ime_discard_composition_ && ime_composition_start_ >= 0 &&
+ ime_composition_length_ > 0) {
+ // A string retrieved with a GetText() call contains a string being
+ // composed by an IME. We remove the composition string from this search
+ // string.
+ new_text.erase(ime_composition_start_, ime_composition_length_);
+ ime_composition_start_ = 0;
+ ime_composition_length_ = 0;
+ if (new_text.empty())
+ return;
+ }
+ textfield_->SyncText();
+ UpdateAccessibleValue(textfield_->text());
+
+ if (should_redraw_text) {
+ CHARRANGE original_sel;
+ GetSel(original_sel);
+ string16 text(GetText());
+ ScopedSuspendUndo suspend_undo(GetTextObjectModel());
+
+ SelectAll();
+ ReplaceSel(reinterpret_cast<LPCTSTR>(text.c_str()), true);
+ SetSel(original_sel);
+ }
+ }
+}
+
+LONG NativeTextfieldWin::ClipXCoordToVisibleText(LONG x,
+ bool is_triple_click) const {
+ // Clip the X coordinate to the left edge of the text. Careful:
+ // PosFromChar(0) may return a negative X coordinate if the beginning of the
+ // text has scrolled off the edit, so don't go past the clip rect's edge.
+ PARAFORMAT2 pf2;
+ GetParaFormat(pf2);
+ // Calculation of the clipped coordinate is more complicated if the paragraph
+ // layout is RTL layout, or if there is RTL characters inside the LTR layout
+ // paragraph.
+ bool ltr_text_in_ltr_layout = true;
+ if ((pf2.wEffects & PFE_RTLPARA) ||
+ base::i18n::StringContainsStrongRTLChars(GetText())) {
+ ltr_text_in_ltr_layout = false;
+ }
+ const int length = GetTextLength();
+ RECT r;
+ GetRect(&r);
+ // The values returned by PosFromChar() seem to refer always
+ // to the left edge of the character's bounding box.
+ const LONG first_position_x = PosFromChar(0).x;
+ LONG min_x = first_position_x;
+ if (!ltr_text_in_ltr_layout) {
+ for (int i = 1; i < length; ++i)
+ min_x = std::min(min_x, PosFromChar(i).x);
+ }
+ const LONG left_bound = std::max(r.left, min_x);
+
+ // PosFromChar(length) is a phantom character past the end of the text. It is
+ // not necessarily a right bound; in RTL controls it may be a left bound. So
+ // treat it as a right bound only if it is to the right of the first
+ // character.
+ LONG right_bound = r.right;
+ LONG end_position_x = PosFromChar(length).x;
+ if (end_position_x >= first_position_x) {
+ right_bound = std::min(right_bound, end_position_x); // LTR case.
+ }
+ // For trailing characters that are 2 pixels wide of less (like "l" in some
+ // fonts), we have a problem:
+ // * Clicks on any pixel within the character will place the cursor before
+ // the character.
+ // * Clicks on the pixel just after the character will not allow triple-
+ // click to work properly (true for any last character width).
+ // So, we move to the last pixel of the character when this is a
+ // triple-click, and moving to one past the last pixel in all other
+ // scenarios. This way, all clicks that can move the cursor will place it at
+ // the end of the text, but triple-click will still work.
+ if (x < left_bound) {
+ return (is_triple_click && ltr_text_in_ltr_layout) ? left_bound - 1 :
+ left_bound;
+ }
+ if ((length == 0) || (x < right_bound))
+ return x;
+ return is_triple_click ? (right_bound - 1) : right_bound;
+}
+
+void NativeTextfieldWin::SetContainsMouse(bool contains_mouse) {
+ if (contains_mouse == contains_mouse_)
+ return;
+
+ contains_mouse_ = contains_mouse;
+
+ if (!textfield_->draw_border())
+ return;
+
+ if (contains_mouse_) {
+ // Register for notification when the mouse leaves. Need to do this so
+ // that we can reset contains mouse properly.
+ TRACKMOUSEEVENT tme;
+ tme.cbSize = sizeof(tme);
+ tme.dwFlags = TME_LEAVE;
+ tme.hwndTrack = m_hWnd;
+ tme.dwHoverTime = 0;
+ TrackMouseEvent(&tme);
+ }
+ RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_FRAME);
+}
+
+ITextDocument* NativeTextfieldWin::GetTextObjectModel() const {
+ if (!text_object_model_) {
+ base::win::ScopedComPtr<IRichEditOle, &IID_IRichEditOle> ole_interface;
+ ole_interface.Attach(GetOleInterface());
+ if (ole_interface)
+ text_object_model_.QueryFrom(ole_interface);
+ }
+ return text_object_model_;
+}
+
+void NativeTextfieldWin::BuildContextMenu() {
+ if (context_menu_contents_.get())
+ return;
+ context_menu_contents_.reset(new ui::SimpleMenuModel(this));
+ context_menu_contents_->AddItemWithStringId(IDS_APP_UNDO, IDS_APP_UNDO);
+ context_menu_contents_->AddSeparator();
+ context_menu_contents_->AddItemWithStringId(IDS_APP_CUT, IDS_APP_CUT);
+ context_menu_contents_->AddItemWithStringId(IDS_APP_COPY, IDS_APP_COPY);
+ context_menu_contents_->AddItemWithStringId(IDS_APP_PASTE, IDS_APP_PASTE);
+ context_menu_contents_->AddSeparator();
+ context_menu_contents_->AddItemWithStringId(IDS_APP_SELECT_ALL,
+ IDS_APP_SELECT_ALL);
+ context_menu_.reset(new Menu2(context_menu_contents_.get()));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldWrapper, public:
+
+// static
+NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper(
+ Textfield* field) {
+ if (views::Widget::IsPureViews())
+ return new NativeTextfieldViews(field);
+ return new NativeTextfieldWin(field);
+}
+
+} // namespace views
diff --git a/ui/views/controls/textfield/native_textfield_win.h b/ui/views/controls/textfield/native_textfield_win.h
new file mode 100644
index 0000000..307060c
--- /dev/null
+++ b/ui/views/controls/textfield/native_textfield_win.h
@@ -0,0 +1,294 @@
+// 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.
+
+#ifndef UI_VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_WIN_H_
+#define UI_VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_WIN_H_
+#pragma once
+
+#include <atlbase.h>
+#include <atlapp.h>
+#include <atlcrack.h>
+#include <atlctrls.h>
+#include <atlmisc.h>
+#include <oleacc.h>
+#include <tom.h> // For ITextDocument, a COM interface to CRichEditCtrl
+#include <vsstyle.h>
+
+#include "base/string16.h"
+#include "base/win/scoped_comptr.h"
+#include "ui/base/models/simple_menu_model.h"
+#include "ui/gfx/insets.h"
+#include "ui/views/controls/textfield/native_textfield_wrapper.h"
+
+namespace gfx {
+class SelectionModel;
+}
+
+namespace views {
+
+class Menu2;
+class NativeViewHost;
+class Textfield;
+
+static const int kDefaultEditStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
+ WS_CLIPSIBLINGS;
+
+// TODO(beng): make a subclass of NativeControlWin instead.
+class NativeTextfieldWin
+ : public CWindowImpl<NativeTextfieldWin, CRichEditCtrl,
+ CWinTraits<kDefaultEditStyle> >,
+ public CRichEditCommands<NativeTextfieldWin>,
+ public NativeTextfieldWrapper,
+ public ui::SimpleMenuModel::Delegate {
+ public:
+ DECLARE_WND_CLASS(L"ViewsTextfieldEdit");
+
+ explicit NativeTextfieldWin(Textfield* parent);
+ ~NativeTextfieldWin();
+
+ // Returns true if the current point is close enough to the origin point in
+ // space and time that it would be considered a double click.
+ VIEWS_EXPORT static bool IsDoubleClick(const POINT& origin,
+ const POINT& current,
+ DWORD elapsed_time);
+
+ // Returns true if the virtual key code is a digit coming from the numeric
+ // keypad (with or without NumLock on). |extended_key| should be set to the
+ // extended key flag specified in the WM_KEYDOWN/UP where the |key_code|
+ // originated.
+ VIEWS_EXPORT static bool IsNumPadDigit(int key_code, bool extended_key);
+
+ // See the code in textfield.cc that calls this for why this is here.
+ void AttachHack();
+
+ // Overridden from NativeTextfieldWrapper:
+ virtual string16 GetText() const OVERRIDE;
+ virtual void UpdateText() OVERRIDE;
+ virtual void AppendText(const string16& text) OVERRIDE;
+ virtual string16 GetSelectedText() const OVERRIDE;
+ virtual void SelectAll() OVERRIDE;
+ virtual void ClearSelection() OVERRIDE;
+ virtual void UpdateBorder() OVERRIDE;
+ virtual void UpdateTextColor() OVERRIDE;
+ virtual void UpdateBackgroundColor() OVERRIDE;
+ virtual void UpdateReadOnly() OVERRIDE;
+ virtual void UpdateFont() OVERRIDE;
+ virtual void UpdateIsPassword() OVERRIDE;
+ virtual void UpdateEnabled() OVERRIDE;
+ virtual gfx::Insets CalculateInsets() OVERRIDE;
+ virtual void UpdateHorizontalMargins() OVERRIDE;
+ virtual void UpdateVerticalMargins() OVERRIDE;
+ virtual bool SetFocus() OVERRIDE;
+ virtual View* GetView() OVERRIDE;
+ virtual gfx::NativeView GetTestingHandle() const OVERRIDE;
+ virtual bool IsIMEComposing() const OVERRIDE;
+ virtual void GetSelectedRange(ui::Range* range) const OVERRIDE;
+ virtual void SelectRange(const ui::Range& range) OVERRIDE;
+ virtual void GetSelectionModel(gfx::SelectionModel* sel) const OVERRIDE;
+ virtual void SelectSelectionModel(const gfx::SelectionModel& sel) OVERRIDE;
+ virtual size_t GetCursorPosition() const OVERRIDE;
+ virtual bool HandleKeyPressed(const views::KeyEvent& event) OVERRIDE;
+ virtual bool HandleKeyReleased(const views::KeyEvent& event) OVERRIDE;
+ virtual void HandleFocus() OVERRIDE;
+ virtual void HandleBlur() OVERRIDE;
+ virtual ui::TextInputClient* GetTextInputClient() OVERRIDE;
+ virtual void ApplyStyleRange(const gfx::StyleRange& style) OVERRIDE;
+ virtual void ApplyDefaultStyle() OVERRIDE;
+ virtual void ClearEditHistory() OVERRIDE;
+
+ // Overridden from ui::SimpleMenuModel::Delegate:
+ virtual bool IsCommandIdChecked(int command_id) const OVERRIDE;
+ virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE;
+ virtual bool GetAcceleratorForCommandId(
+ int command_id,
+ ui::Accelerator* accelerator) OVERRIDE;
+ virtual void ExecuteCommand(int command_id) OVERRIDE;
+
+ // Update accessibility information.
+ void InitializeAccessibilityInfo();
+ void UpdateAccessibleState(uint32 state_flag, bool set_value);
+ void UpdateAccessibleValue(const string16& value);
+
+ // CWindowImpl
+ BEGIN_MSG_MAP(Edit)
+ MSG_WM_CHAR(OnChar)
+ MSG_WM_CONTEXTMENU(OnContextMenu)
+ MSG_WM_COPY(OnCopy)
+ MSG_WM_CUT(OnCut)
+ MESSAGE_HANDLER_EX(WM_IME_CHAR, OnImeChar)
+ MESSAGE_HANDLER_EX(WM_IME_STARTCOMPOSITION, OnImeStartComposition)
+ MESSAGE_HANDLER_EX(WM_IME_COMPOSITION, OnImeComposition)
+ MESSAGE_HANDLER_EX(WM_IME_ENDCOMPOSITION, OnImeEndComposition)
+ MSG_WM_KEYDOWN(OnKeyDown)
+ MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk)
+ MSG_WM_LBUTTONDOWN(OnLButtonDown)
+ MSG_WM_LBUTTONUP(OnLButtonUp)
+ MSG_WM_MBUTTONDOWN(OnNonLButtonDown)
+ MSG_WM_MOUSEMOVE(OnMouseMove)
+ MSG_WM_MOUSELEAVE(OnMouseLeave)
+ MESSAGE_HANDLER_EX(WM_MOUSEWHEEL, OnMouseWheel)
+ MSG_WM_NCCALCSIZE(OnNCCalcSize)
+ MSG_WM_NCPAINT(OnNCPaint)
+ MSG_WM_RBUTTONDOWN(OnNonLButtonDown)
+ MSG_WM_PASTE(OnPaste)
+ MSG_WM_SETFOCUS(OnSetFocus)
+ MSG_WM_SYSCHAR(OnSysChar) // WM_SYSxxx == WM_xxx with ALT down
+ MSG_WM_SYSKEYDOWN(OnKeyDown)
+ END_MSG_MAP()
+
+ private:
+ // This object freezes repainting of the edit until the object is destroyed.
+ // Some methods of the CRichEditCtrl draw synchronously to the screen. If we
+ // don't freeze, the user will see a rapid series of calls to these as
+ // flickers.
+ //
+ // Freezing the control while it is already frozen is permitted; the control
+ // will unfreeze once both freezes are released (the freezes stack).
+ class ScopedFreeze {
+ public:
+ ScopedFreeze(NativeTextfieldWin* edit, ITextDocument* text_object_model);
+ ~ScopedFreeze();
+
+ private:
+ NativeTextfieldWin* const edit_;
+ ITextDocument* const text_object_model_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedFreeze);
+ };
+
+ // This object suspends placing any operations on the edit's undo stack until
+ // the object is destroyed. If we don't do this, some of the operations we
+ // perform behind the user's back will be undoable by the user, which feels
+ // bizarre and confusing.
+ class ScopedSuspendUndo {
+ public:
+ explicit ScopedSuspendUndo(ITextDocument* text_object_model);
+ ~ScopedSuspendUndo();
+
+ private:
+ ITextDocument* const text_object_model_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedSuspendUndo);
+ };
+
+ // message handlers
+ void OnChar(TCHAR key, UINT repeat_count, UINT flags);
+ void OnContextMenu(HWND window, const POINT& point);
+ void OnCopy();
+ void OnCut();
+ LRESULT OnImeChar(UINT message, WPARAM wparam, LPARAM lparam);
+ LRESULT OnImeStartComposition(UINT message, WPARAM wparam, LPARAM lparam);
+ LRESULT OnImeComposition(UINT message, WPARAM wparam, LPARAM lparam);
+ LRESULT OnImeEndComposition(UINT message, WPARAM wparam, LPARAM lparam);
+ void OnKeyDown(TCHAR key, UINT repeat_count, UINT flags);
+ void OnLButtonDblClk(UINT keys, const CPoint& point);
+ void OnLButtonDown(UINT keys, const CPoint& point);
+ void OnLButtonUp(UINT keys, const CPoint& point);
+ void OnMouseLeave();
+ LRESULT OnMouseWheel(UINT message, WPARAM w_param, LPARAM l_param);
+ void OnMouseMove(UINT keys, const CPoint& point);
+ int OnNCCalcSize(BOOL w_param, LPARAM l_param);
+ void OnNCPaint(HRGN region);
+ void OnNonLButtonDown(UINT keys, const CPoint& point);
+ void OnPaste();
+ void OnSetFocus(HWND hwnd);
+ void OnSysChar(TCHAR ch, UINT repeat_count, UINT flags);
+
+ // Helper function for OnChar() and OnKeyDown() that handles keystrokes that
+ // could change the text in the edit.
+ // Note: This function assumes GetCurrentMessage() returns a MSG with
+ // msg > WM_KEYFIRST and < WM_KEYLAST.
+ void HandleKeystroke();
+
+ // Every piece of code that can change the edit should call these functions
+ // before and after the change. These functions determine if anything
+ // meaningful changed, and do any necessary updating and notification.
+ void OnBeforePossibleChange();
+
+ // When a user types a BIDI mirroring character (e.g. left parenthesis
+ // U+0028, which should be rendered as '(' in LTR context unless surrounded
+ // by RTL characters in both sides, and it should be rendered as ')' in RTL
+ // context unless surrounded by LTR characters in both sides.), the textfield
+ // does not properly mirror the character when necessary. However, if we
+ // explicitly set the text in the edit to the entire current string, then
+ // the BIDI mirroring characters will be mirrored properly. When
+ // |should_redraw_text| is true, we explicitly set the text in the edit to
+ // the entire current string any time the text changes.
+ void OnAfterPossibleChange(bool should_redraw_text);
+
+ // Given an X coordinate in client coordinates, returns that coordinate
+ // clipped to be within the horizontal bounds of the visible text.
+ //
+ // This is used in our mouse handlers to work around quirky behaviors of the
+ // underlying CRichEditCtrl like not supporting triple-click when the user
+ // doesn't click on the text itself.
+ //
+ // |is_triple_click| should be true iff this is the third click of a triple
+ // click. Sadly, we need to clip slightly differently in this case.
+ LONG ClipXCoordToVisibleText(LONG x, bool is_triple_click) const;
+
+ // Sets whether the mouse is in the edit. As necessary this redraws the
+ // edit.
+ void SetContainsMouse(bool contains_mouse);
+
+ // Getter for the text_object_model_, used by the ScopedFreeze class. Note
+ // that the pointer returned here is only valid as long as the Edit is still
+ // alive.
+ ITextDocument* GetTextObjectModel() const;
+
+ // Generates the contents of the context menu.
+ void BuildContextMenu();
+
+ // The Textfield this object is bound to.
+ Textfield* textfield_;
+
+ // We need to know if the user triple-clicks, so track double click points
+ // and times so we can see if subsequent clicks are actually triple clicks.
+ bool tracking_double_click_;
+ CPoint double_click_point_;
+ DWORD double_click_time_;
+
+ // Used to discard unnecessary WM_MOUSEMOVE events after the first such
+ // unnecessary event. See detailed comments in OnMouseMove().
+ bool can_discard_mousemove_;
+
+ // The text of this control before a possible change.
+ string16 text_before_change_;
+
+ // If true, the mouse is over the edit.
+ bool contains_mouse_;
+
+ static bool did_load_library_;
+
+ // The contents of the context menu for the edit.
+ scoped_ptr<ui::SimpleMenuModel> context_menu_contents_;
+ scoped_ptr<Menu2> context_menu_;
+
+ // Border insets.
+ gfx::Insets content_insets_;
+
+ // This interface is useful for accessing the CRichEditCtrl at a low level.
+ mutable base::win::ScopedComPtr<ITextDocument> text_object_model_;
+
+ // The position and the length of the ongoing composition string.
+ // These values are used for removing a composition string from a search
+ // text to emulate Firefox.
+ bool ime_discard_composition_;
+ int ime_composition_start_;
+ int ime_composition_length_;
+
+ // TODO(beng): remove this when we are a subclass of NativeControlWin.
+ NativeViewHost* container_view_;
+
+ COLORREF bg_color_;
+
+ // The accessibility state of this object.
+ int accessibility_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeTextfieldWin);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_WIN_H_
diff --git a/ui/views/controls/textfield/native_textfield_wrapper.h b/ui/views/controls/textfield/native_textfield_wrapper.h
new file mode 100644
index 0000000..3bc3f1d
--- /dev/null
+++ b/ui/views/controls/textfield/native_textfield_wrapper.h
@@ -0,0 +1,153 @@
+// 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.
+
+#ifndef UI_VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_WRAPPER_H_
+#define UI_VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_WRAPPER_H_
+#pragma once
+
+#include "base/string16.h"
+#include "ui/gfx/native_widget_types.h"
+#include "views/views_export.h"
+
+namespace gfx {
+class Insets;
+class SelectionModel;
+struct StyleRange;
+} // namespace gfx
+
+namespace ui {
+class Range;
+class TextInputClient;
+} // namespace ui
+
+namespace views {
+
+class KeyEvent;
+class Textfield;
+class View;
+
+// An interface implemented by an object that provides a platform-native
+// text field.
+class VIEWS_EXPORT NativeTextfieldWrapper {
+ public:
+ // The Textfield calls this when it is destroyed to clean up the wrapper
+ // object.
+ virtual ~NativeTextfieldWrapper() {}
+
+ // Gets the text displayed in the wrapped native text field.
+ virtual string16 GetText() const = 0;
+
+ // Updates the text displayed with the text held by the Textfield.
+ virtual void UpdateText() = 0;
+
+ // Adds the specified text to the text already displayed by the wrapped native
+ // text field.
+ virtual void AppendText(const string16& text) = 0;
+
+ // Gets the text that is selected in the wrapped native text field.
+ virtual string16 GetSelectedText() const = 0;
+
+ // Selects all the text in the edit. Use this in place of SetSelAll() to
+ // avoid selecting the "phantom newline" at the end of the edit.
+ virtual void SelectAll() = 0;
+
+ // Clears the selection within the edit field and sets the caret to the end.
+ virtual void ClearSelection() = 0;
+
+ // Updates the border display for the native text field with the state desired
+ // by the Textfield.
+ virtual void UpdateBorder() = 0;
+
+ // Updates the text color used when painting the native text field.
+ virtual void UpdateTextColor() = 0;
+
+ // Updates the background color used when painting the native text field.
+ virtual void UpdateBackgroundColor() = 0;
+
+ // Updates the read-only state of the native text field.
+ virtual void UpdateReadOnly() = 0;
+
+ // Updates the font used to render text in the native text field.
+ virtual void UpdateFont() = 0;
+
+ // Updates the visibility of the text in the native text field.
+ virtual void UpdateIsPassword() = 0;
+
+ // Updates the enabled state of the native text field.
+ virtual void UpdateEnabled() = 0;
+
+ // Returns the insets for the text field.
+ virtual gfx::Insets CalculateInsets() = 0;
+
+ // Updates the horizontal margins for the native text field.
+ virtual void UpdateHorizontalMargins() = 0;
+
+ // Updates the vertical margins for the native text field.
+ virtual void UpdateVerticalMargins() = 0;
+
+ // Sets the focus to the text field. Returns false if the wrapper
+ // didn't take focus.
+ virtual bool SetFocus() = 0;
+
+ // Retrieves the views::View that hosts the native control.
+ virtual View* GetView() = 0;
+
+ // Returns a handle to the underlying native view for testing.
+ virtual gfx::NativeView GetTestingHandle() const = 0;
+
+ // Returns whether or not an IME is composing text.
+ virtual bool IsIMEComposing() const = 0;
+
+ // Gets the selected range.
+ virtual void GetSelectedRange(ui::Range* range) const = 0;
+
+ // Selects the text given by |range|.
+ virtual void SelectRange(const ui::Range& range) = 0;
+
+ // Gets the selection model.
+ virtual void GetSelectionModel(gfx::SelectionModel* sel) const = 0;
+
+ // Selects the text given by |sel|.
+ virtual void SelectSelectionModel(const gfx::SelectionModel& sel) = 0;
+
+ // Returns the currnet cursor position.
+ virtual size_t GetCursorPosition() const = 0;
+
+ // Following methods are to forward key/focus related events to the
+ // views wrapper so that TextfieldViews can handle key inputs without
+ // having focus.
+
+ // Invoked when a key is pressed/release on Textfield. Subclasser
+ // should return true if the event has been processed and false
+ // otherwise.
+ // See also View::OnKeyPressed/OnKeyReleased.
+ virtual bool HandleKeyPressed(const views::KeyEvent& e) = 0;
+ virtual bool HandleKeyReleased(const views::KeyEvent& e) = 0;
+
+ // Invoked when focus is being moved from or to the Textfield.
+ // See also View::OnFocus/OnBlur.
+ 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 ui::TextInputClient* GetTextInputClient() = 0;
+
+ // Applies the |style| to the text specified by its range.
+ // See |Textfield::ApplyStyleRange| for detail.
+ virtual void ApplyStyleRange(const gfx::StyleRange& style) = 0;
+
+ // Applies the default style to the textfield.
+ virtual void ApplyDefaultStyle() = 0;
+
+ // Clears Edit history.
+ virtual void ClearEditHistory() = 0;
+
+ // Creates an appropriate NativeTextfieldWrapper for the platform.
+ static NativeTextfieldWrapper* CreateWrapper(Textfield* field);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_WRAPPER_H_
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc
new file mode 100644
index 0000000..74395f2
--- /dev/null
+++ b/ui/views/controls/textfield/textfield.cc
@@ -0,0 +1,453 @@
+// 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.
+
+#include "ui/views/controls/textfield/textfield.h"
+
+#if defined(TOOLKIT_USES_GTK)
+#include <gdk/gdkkeysyms.h>
+#endif
+
+#include <string>
+
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/ime/text_input_type.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/range/range.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/selection_model.h"
+#include "ui/views/controls/native/native_view_host.h"
+#include "ui/views/controls/textfield/native_textfield_wrapper.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/widget/widget.h"
+
+#if defined(OS_LINUX)
+#include "ui/base/keycodes/keyboard_code_conversion_gtk.h"
+#elif defined(OS_WIN)
+#include "base/win/win_util.h"
+// TODO(beng): this should be removed when the OS_WIN hack from
+// ViewHierarchyChanged is removed.
+#include "ui/views/controls/textfield/native_textfield_views.h"
+#include "ui/views/controls/textfield/native_textfield_win.h"
+#endif
+
+namespace views {
+
+// static
+const char Textfield::kViewClassName[] = "views/Textfield";
+
+/////////////////////////////////////////////////////////////////////////////
+// Textfield
+
+Textfield::Textfield()
+ : native_wrapper_(NULL),
+ controller_(NULL),
+ style_(STYLE_DEFAULT),
+ read_only_(false),
+ default_width_in_chars_(0),
+ draw_border_(true),
+ text_color_(SK_ColorBLACK),
+ use_default_text_color_(true),
+ background_color_(SK_ColorWHITE),
+ use_default_background_color_(true),
+ initialized_(false),
+ horizontal_margins_were_set_(false),
+ vertical_margins_were_set_(false),
+ text_input_type_(ui::TEXT_INPUT_TYPE_TEXT) {
+ set_focusable(true);
+}
+
+Textfield::Textfield(StyleFlags style)
+ : native_wrapper_(NULL),
+ controller_(NULL),
+ style_(style),
+ read_only_(false),
+ default_width_in_chars_(0),
+ draw_border_(true),
+ text_color_(SK_ColorBLACK),
+ use_default_text_color_(true),
+ background_color_(SK_ColorWHITE),
+ use_default_background_color_(true),
+ initialized_(false),
+ horizontal_margins_were_set_(false),
+ vertical_margins_were_set_(false),
+ text_input_type_(ui::TEXT_INPUT_TYPE_TEXT) {
+ set_focusable(true);
+ if (IsPassword())
+ SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD);
+}
+
+Textfield::~Textfield() {
+}
+
+void Textfield::SetController(TextfieldController* controller) {
+ controller_ = controller;
+}
+
+TextfieldController* Textfield::GetController() const {
+ return controller_;
+}
+
+void Textfield::SetReadOnly(bool read_only) {
+ read_only_ = read_only;
+ if (native_wrapper_) {
+ native_wrapper_->UpdateReadOnly();
+ native_wrapper_->UpdateTextColor();
+ native_wrapper_->UpdateBackgroundColor();
+ }
+}
+
+bool Textfield::IsPassword() const {
+ return style_ & STYLE_PASSWORD;
+}
+
+void Textfield::SetPassword(bool password) {
+ if (password) {
+ style_ = static_cast<StyleFlags>(style_ | STYLE_PASSWORD);
+ SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD);
+ } else {
+ style_ = static_cast<StyleFlags>(style_ & ~STYLE_PASSWORD);
+ SetTextInputType(ui::TEXT_INPUT_TYPE_TEXT);
+ }
+ if (native_wrapper_)
+ native_wrapper_->UpdateIsPassword();
+}
+
+
+ui::TextInputType Textfield::GetTextInputType() const {
+ if (read_only() || !IsEnabled())
+ return ui::TEXT_INPUT_TYPE_NONE;
+ return text_input_type_;
+}
+
+void Textfield::SetTextInputType(ui::TextInputType type) {
+ text_input_type_ = type;
+ bool should_be_password = type == ui::TEXT_INPUT_TYPE_PASSWORD;
+ if (IsPassword() != should_be_password)
+ SetPassword(should_be_password);
+}
+
+void Textfield::SetText(const string16& text) {
+ text_ = text;
+ if (native_wrapper_)
+ native_wrapper_->UpdateText();
+}
+
+void Textfield::AppendText(const string16& text) {
+ text_ += text;
+ if (native_wrapper_)
+ native_wrapper_->AppendText(text);
+}
+
+void Textfield::SelectAll() {
+ if (native_wrapper_)
+ native_wrapper_->SelectAll();
+}
+
+string16 Textfield::GetSelectedText() const {
+ if (native_wrapper_)
+ return native_wrapper_->GetSelectedText();
+ return string16();
+}
+
+void Textfield::ClearSelection() const {
+ if (native_wrapper_)
+ native_wrapper_->ClearSelection();
+}
+
+bool Textfield::HasSelection() const {
+ ui::Range range;
+ if (native_wrapper_)
+ native_wrapper_->GetSelectedRange(&range);
+ return !range.is_empty();
+}
+
+void Textfield::SetTextColor(SkColor color) {
+ text_color_ = color;
+ use_default_text_color_ = false;
+ if (native_wrapper_)
+ native_wrapper_->UpdateTextColor();
+}
+
+void Textfield::UseDefaultTextColor() {
+ use_default_text_color_ = true;
+ if (native_wrapper_)
+ native_wrapper_->UpdateTextColor();
+}
+
+void Textfield::SetBackgroundColor(SkColor color) {
+ background_color_ = color;
+ use_default_background_color_ = false;
+ if (native_wrapper_)
+ native_wrapper_->UpdateBackgroundColor();
+}
+
+void Textfield::UseDefaultBackgroundColor() {
+ use_default_background_color_ = true;
+ if (native_wrapper_)
+ native_wrapper_->UpdateBackgroundColor();
+}
+
+void Textfield::SetFont(const gfx::Font& font) {
+ font_ = font;
+ if (native_wrapper_)
+ native_wrapper_->UpdateFont();
+ PreferredSizeChanged();
+}
+
+void Textfield::SetHorizontalMargins(int left, int right) {
+ margins_.Set(margins_.top(), left, margins_.bottom(), right);
+ horizontal_margins_were_set_ = true;
+ if (native_wrapper_)
+ native_wrapper_->UpdateHorizontalMargins();
+ PreferredSizeChanged();
+}
+
+void Textfield::SetVerticalMargins(int top, int bottom) {
+ margins_.Set(top, margins_.left(), bottom, margins_.right());
+ vertical_margins_were_set_ = true;
+ if (native_wrapper_)
+ native_wrapper_->UpdateVerticalMargins();
+ PreferredSizeChanged();
+}
+
+void Textfield::RemoveBorder() {
+ if (!draw_border_)
+ return;
+
+ draw_border_ = false;
+ if (native_wrapper_)
+ native_wrapper_->UpdateBorder();
+}
+
+bool Textfield::GetHorizontalMargins(int* left, int* right) {
+ if (!horizontal_margins_were_set_)
+ return false;
+ *left = margins_.left();
+ *right = margins_.right();
+ return true;
+}
+
+bool Textfield::GetVerticalMargins(int* top, int* bottom) {
+ if (!vertical_margins_were_set_)
+ return false;
+ *top = margins_.top();
+ *bottom = margins_.bottom();
+ return true;
+}
+
+void Textfield::UpdateAllProperties() {
+ if (native_wrapper_) {
+ native_wrapper_->UpdateText();
+ native_wrapper_->UpdateTextColor();
+ native_wrapper_->UpdateBackgroundColor();
+ native_wrapper_->UpdateReadOnly();
+ native_wrapper_->UpdateFont();
+ native_wrapper_->UpdateEnabled();
+ native_wrapper_->UpdateBorder();
+ native_wrapper_->UpdateIsPassword();
+ native_wrapper_->UpdateHorizontalMargins();
+ native_wrapper_->UpdateVerticalMargins();
+ }
+}
+
+void Textfield::SyncText() {
+ 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 {
+ return native_wrapper_ && native_wrapper_->IsIMEComposing();
+}
+
+void Textfield::GetSelectedRange(ui::Range* range) const {
+ DCHECK(native_wrapper_);
+ native_wrapper_->GetSelectedRange(range);
+}
+
+void Textfield::SelectRange(const ui::Range& range) {
+ DCHECK(native_wrapper_);
+ native_wrapper_->SelectRange(range);
+}
+
+void Textfield::GetSelectionModel(gfx::SelectionModel* sel) const {
+ DCHECK(native_wrapper_);
+ native_wrapper_->GetSelectionModel(sel);
+}
+
+void Textfield::SelectSelectionModel(const gfx::SelectionModel& sel) {
+ DCHECK(native_wrapper_);
+ native_wrapper_->SelectSelectionModel(sel);
+}
+
+size_t Textfield::GetCursorPosition() const {
+ DCHECK(native_wrapper_);
+ return native_wrapper_->GetCursorPosition();
+}
+
+void Textfield::ApplyStyleRange(const gfx::StyleRange& style) {
+ DCHECK(native_wrapper_);
+ return native_wrapper_->ApplyStyleRange(style);
+}
+
+void Textfield::ApplyDefaultStyle() {
+ DCHECK(native_wrapper_);
+ native_wrapper_->ApplyDefaultStyle();
+}
+
+void Textfield::ClearEditHistory() {
+ DCHECK(native_wrapper_);
+ native_wrapper_->ClearEditHistory();
+}
+
+void Textfield::SetAccessibleName(const string16& name) {
+ accessible_name_ = name;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Textfield, View overrides:
+
+void Textfield::Layout() {
+ if (native_wrapper_) {
+ native_wrapper_->GetView()->SetBoundsRect(GetLocalBounds());
+ native_wrapper_->GetView()->Layout();
+ }
+}
+
+gfx::Size Textfield::GetPreferredSize() {
+ gfx::Insets insets;
+ if (draw_border_ && native_wrapper_)
+ insets = native_wrapper_->CalculateInsets();
+ return gfx::Size(font_.GetExpectedTextWidth(default_width_in_chars_) +
+ insets.width(), font_.GetHeight() + insets.height());
+}
+
+bool Textfield::IsFocusable() const {
+ return View::IsFocusable() && !read_only_;
+}
+
+void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse) {
+ SelectAll();
+}
+
+bool Textfield::SkipDefaultKeyEventProcessing(const KeyEvent& e) {
+ // TODO(hamaji): Figure out which keyboard combinations we need to add here,
+ // similar to LocationBarView::SkipDefaultKeyEventProcessing.
+ ui::KeyboardCode key = e.key_code();
+ if (key == ui::VKEY_BACK)
+ return true; // We'll handle BackSpace ourselves.
+
+#if defined(USE_AURA)
+ NOTIMPLEMENTED();
+#elif defined(OS_WIN)
+ // We don't translate accelerators for ALT + NumPad digit on Windows, they are
+ // used for entering special characters. We do translate alt-home.
+ if (e.IsAltDown() && (key != ui::VKEY_HOME) &&
+ NativeTextfieldWin::IsNumPadDigit(key,
+ (e.flags() & ui::EF_EXTENDED) != 0))
+ return true;
+#endif
+ return false;
+}
+
+void Textfield::OnPaintBackground(gfx::Canvas* canvas) {
+ // Overridden to be public - gtk_views_entry.cc wants to call it.
+ View::OnPaintBackground(canvas);
+}
+
+void Textfield::OnPaintFocusBorder(gfx::Canvas* canvas) {
+ if (NativeViewHost::kRenderNativeControlFocus)
+ View::OnPaintFocusBorder(canvas);
+}
+
+bool Textfield::OnKeyPressed(const views::KeyEvent& e) {
+ return native_wrapper_ && native_wrapper_->HandleKeyPressed(e);
+}
+
+bool Textfield::OnKeyReleased(const views::KeyEvent& e) {
+ return native_wrapper_ && native_wrapper_->HandleKeyReleased(e);
+}
+
+void Textfield::OnFocus() {
+ if (native_wrapper_)
+ native_wrapper_->HandleFocus();
+
+ // Forward the focus to the wrapper if it exists.
+ if (!native_wrapper_ || !native_wrapper_->SetFocus()) {
+ // If there is no wrapper or the wrapper didn't take focus, call
+ // View::Focus to clear the native focus so that we still get
+ // keyboard messages.
+ View::OnFocus();
+ }
+}
+
+void Textfield::OnBlur() {
+ if (native_wrapper_)
+ native_wrapper_->HandleBlur();
+}
+
+void Textfield::GetAccessibleState(ui::AccessibleViewState* state) {
+ state->role = ui::AccessibilityTypes::ROLE_TEXT;
+ state->name = accessible_name_;
+ if (read_only())
+ state->state |= ui::AccessibilityTypes::STATE_READONLY;
+ if (IsPassword())
+ state->state |= ui::AccessibilityTypes::STATE_PROTECTED;
+ state->value = text_;
+
+ DCHECK(native_wrapper_);
+ ui::Range range;
+ native_wrapper_->GetSelectedRange(&range);
+ state->selection_start = range.start();
+ state->selection_end = range.end();
+}
+
+ui::TextInputClient* Textfield::GetTextInputClient() {
+ return native_wrapper_ ? native_wrapper_->GetTextInputClient() : NULL;
+}
+
+void Textfield::OnEnabledChanged() {
+ View::OnEnabledChanged();
+ if (native_wrapper_)
+ native_wrapper_->UpdateEnabled();
+}
+
+void Textfield::ViewHierarchyChanged(bool is_add, View* parent, View* child) {
+ if (is_add && !native_wrapper_ && GetWidget() && !initialized_) {
+ initialized_ = true;
+
+ // The native wrapper's lifetime will be managed by the view hierarchy after
+ // we call AddChildView.
+ native_wrapper_ = NativeTextfieldWrapper::CreateWrapper(this);
+ AddChildView(native_wrapper_->GetView());
+ // TODO(beng): Move this initialization to NativeTextfieldWin once it
+ // subclasses NativeControlWin.
+ UpdateAllProperties();
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+ if (!views::Widget::IsPureViews()) {
+ // TODO(beng): remove this once NativeTextfieldWin subclasses
+ // NativeControlWin. This is currently called to perform post-AddChildView
+ // initialization for the wrapper. The GTK version subclasses things
+ // correctly and doesn't need this.
+ //
+ // Remove the include for native_textfield_win.h above when you fix this.
+ static_cast<NativeTextfieldWin*>(native_wrapper_)->AttachHack();
+ }
+#endif
+ }
+}
+
+std::string Textfield::GetClassName() const {
+ return kViewClassName;
+}
+
+} // namespace views
diff --git a/ui/views/controls/textfield/textfield.h b/ui/views/controls/textfield/textfield.h
new file mode 100644
index 0000000..abdd590
--- /dev/null
+++ b/ui/views/controls/textfield/textfield.h
@@ -0,0 +1,315 @@
+// 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.
+
+#ifndef UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_H_
+#define UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_H_
+#pragma once
+
+#include "build/build_config.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/string16.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/ime/text_input_type.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/views/controls/textfield/native_textfield_wrapper.h"
+#include "views/view.h"
+
+#if !defined(OS_LINUX)
+#include "base/logging.h"
+#endif
+
+namespace gfx {
+struct StyleRange;
+} // namespace gfx
+
+namespace ui {
+class Range;
+class TextInputClient;
+} // namespace ui
+
+namespace views {
+
+class KeyEvent;
+class TextfieldController;
+
+// This class implements a View that wraps a native text (edit) field.
+class VIEWS_EXPORT Textfield : public View {
+ public:
+ // The button's class name.
+ static const char kViewClassName[];
+
+ enum StyleFlags {
+ STYLE_DEFAULT = 0,
+ STYLE_PASSWORD = 1 << 0,
+ STYLE_LOWERCASE = 1 << 1
+ };
+
+ Textfield();
+ explicit Textfield(StyleFlags style);
+ virtual ~Textfield();
+
+ // TextfieldController accessors
+ void SetController(TextfieldController* controller);
+ TextfieldController* GetController() const;
+
+ // Gets/Sets whether or not the Textfield is read-only.
+ bool read_only() const { return read_only_; }
+ void SetReadOnly(bool read_only);
+
+ // Gets/Sets whether or not this Textfield is a password field.
+ // TODO(bryeung): Currently this is only used in
+ // chrome/browser/chromeos/options/wifi_config_view.cc, which is being
+ // converted to WebUI. Please remove this when that happens.
+ bool IsPassword() const;
+ void SetPassword(bool password);
+
+ // Gets/Sets the input type of this textfield.
+ ui::TextInputType GetTextInputType() const;
+ void SetTextInputType(ui::TextInputType type);
+
+ // Gets/Sets the text currently displayed in the Textfield.
+ const string16& text() const { return text_; }
+
+ // Sets the text currently displayed in the Textfield. This doesn't
+ // change the cursor position if the current cursor is within the
+ // new text's range, or moves the cursor to the end if the cursor is
+ // out of the new text's range.
+ void SetText(const string16& text);
+
+ // Appends the given string to the previously-existing text in the field.
+ void AppendText(const string16& text);
+
+ // Returns the text that is currently selected.
+ string16 GetSelectedText() const;
+
+ // Causes the edit field to be fully selected.
+ void SelectAll();
+
+ // Clears the selection within the edit field and sets the caret to the end.
+ void ClearSelection() const;
+
+ // Checks if there is any selected text.
+ bool HasSelection() const;
+
+ // Accessor for |style_|.
+ StyleFlags style() const { return style_; }
+
+ // Gets/Sets the text color to be used when painting the Textfield.
+ // Call |UseDefaultTextColor| to return to the system default colors.
+ SkColor text_color() const { return text_color_; }
+ void SetTextColor(SkColor color);
+
+ // Gets/Sets whether the default text color should be used when painting the
+ // Textfield.
+ bool use_default_text_color() const {
+ return use_default_text_color_;
+ }
+ void UseDefaultTextColor();
+
+ // Gets/Sets the background color to be used when painting the Textfield.
+ // Call |UseDefaultBackgroundColor| to return to the system default colors.
+ SkColor background_color() const { return background_color_; }
+ void SetBackgroundColor(SkColor color);
+
+ // Gets/Sets whether the default background color should be used when painting
+ // the Textfield.
+ bool use_default_background_color() const {
+ return use_default_background_color_;
+ }
+ void UseDefaultBackgroundColor();
+
+ // Gets/Sets the font used when rendering the text within the Textfield.
+ const gfx::Font& font() const { return font_; }
+ void SetFont(const gfx::Font& font);
+
+ // Sets the left and right margin (in pixels) within the text box. On Windows
+ // this is accomplished by packing the left and right margin into a single
+ // 32 bit number, so the left and right margins are effectively 16 bits.
+ void SetHorizontalMargins(int left, int right);
+
+ // Sets the top and bottom margins (in pixels) within the textfield.
+ // NOTE: in most cases height could be changed instead.
+ void SetVerticalMargins(int top, int bottom);
+
+ // Sets the default width of the text control. See default_width_in_chars_.
+ void set_default_width_in_chars(int default_width) {
+ default_width_in_chars_ = default_width;
+ }
+
+ // Removes the border from the edit box, giving it a 2D look.
+ bool draw_border() const { return draw_border_; }
+ void RemoveBorder();
+
+ // Sets the text to display when empty.
+ void set_text_to_display_when_empty(const string16& text) {
+ text_to_display_when_empty_ = text;
+#if !defined(OS_LINUX)
+ NOTIMPLEMENTED();
+#endif
+ }
+ const string16& text_to_display_when_empty() {
+ return text_to_display_when_empty_;
+ }
+
+ // Getter for the horizontal margins that were set. Returns false if
+ // horizontal margins weren't set.
+ bool GetHorizontalMargins(int* left, int* right);
+
+ // Getter for the vertical margins that were set. Returns false if vertical
+ // margins weren't set.
+ bool GetVerticalMargins(int* top, int* bottom);
+
+ // Updates all properties on the textfield. This is invoked internally.
+ // Users of Textfield never need to invoke this directly.
+ void UpdateAllProperties();
+
+ // Invoked by the edit control when the value changes. This method set
+ // the text_ member variable to the value contained in edit control.
+ // This is important because the edit control can be replaced if it has
+ // been deleted during a window close.
+ void SyncText();
+
+ // Returns whether or not an IME is composing text.
+ bool IsIMEComposing() const;
+
+ // Gets the selected range. This is views-implementation only and
+ // has to be called after the wrapper is created.
+ void GetSelectedRange(ui::Range* range) const;
+
+ // Selects the text given by |range|. This is views-implementation only and
+ // has to be called after the wrapper is created.
+ void SelectRange(const ui::Range& range);
+
+ // Gets the selection model. This is views-implementation only and
+ // has to be called after the wrapper is created.
+ void GetSelectionModel(gfx::SelectionModel* sel) const;
+
+ // Selects the text given by |sel|. This is views-implementation only and
+ // has to be called after the wrapper is created.
+ void SelectSelectionModel(const gfx::SelectionModel& sel);
+
+ // Returns the current cursor position. This is views-implementation
+ // only and has to be called after the wrapper is created.
+ size_t GetCursorPosition() const;
+
+ // Applies |style| to the text specified by its range. The style will be
+ // ignored if range is empty or invalid. This is views-implementation only and
+ // has to be called after the wrapper is created.
+ void ApplyStyleRange(const gfx::StyleRange& style);
+
+ // Applies the default style to the textfield. This is views-implementation
+ // only and has to be called after the wrapper is created.
+ void ApplyDefaultStyle();
+
+ // Clears Edit history.
+ void ClearEditHistory();
+
+ // Set the accessible name of the text field.
+ void SetAccessibleName(const string16& name);
+
+ // Provided only for testing:
+ gfx::NativeView GetTestingHandle() const {
+ return native_wrapper_ ? native_wrapper_->GetTestingHandle() : NULL;
+ }
+ NativeTextfieldWrapper* GetNativeWrapperForTesting() const {
+ return native_wrapper_;
+ }
+
+ // Overridden from View:
+ virtual void Layout() OVERRIDE;
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual bool IsFocusable() const OVERRIDE;
+ virtual void AboutToRequestFocusFromTabTraversal(bool reverse) OVERRIDE;
+ virtual bool SkipDefaultKeyEventProcessing(const KeyEvent& e) OVERRIDE;
+ virtual void OnEnabledChanged() OVERRIDE;
+ virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE;
+ virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE;
+ virtual bool OnKeyPressed(const views::KeyEvent& e) OVERRIDE;
+ virtual bool OnKeyReleased(const views::KeyEvent& e) OVERRIDE;
+ virtual void OnFocus() OVERRIDE;
+ virtual void OnBlur() OVERRIDE;
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+ virtual ui::TextInputClient* GetTextInputClient() OVERRIDE;
+
+ protected:
+ virtual void ViewHierarchyChanged(bool is_add, View* parent,
+ View* child) OVERRIDE;
+ virtual std::string GetClassName() const OVERRIDE;
+
+ // The object that actually implements the native text field.
+ NativeTextfieldWrapper* native_wrapper_;
+
+ private:
+ // This is the current listener for events from this Textfield.
+ TextfieldController* controller_;
+
+ // The mask of style options for this Textfield.
+ StyleFlags style_;
+
+ // The font used to render the text in the Textfield.
+ gfx::Font font_;
+
+ // The text displayed in the Textfield.
+ string16 text_;
+
+ // True if this Textfield cannot accept input and is read-only.
+ bool read_only_;
+
+ // The default number of average characters for the width of this text field.
+ // This will be reported as the "desired size". Defaults to 0.
+ int default_width_in_chars_;
+
+ // Whether the border is drawn.
+ bool draw_border_;
+
+ // The text color to be used when painting the Textfield, provided
+ // |use_default_text_color_| is set to false.
+ SkColor text_color_;
+
+ // When true, the system text color for Textfields is used when painting this
+ // Textfield. When false, the value of |text_color_| determines the
+ // Textfield's text color.
+ bool use_default_text_color_;
+
+ // The background color to be used when painting the Textfield, provided
+ // |use_default_background_color_| is set to false.
+ SkColor background_color_;
+
+ // When true, the system background color for Textfields is used when painting
+ // this Textfield. When false, the value of |background_color_| determines the
+ // Textfield's background color.
+ bool use_default_background_color_;
+
+ // TODO(beng): remove this once NativeTextfieldWin subclasses
+ // NativeControlWin.
+ bool initialized_;
+
+ // Holds inner textfield margins.
+ gfx::Insets margins_;
+
+ // Holds whether margins were set.
+ bool horizontal_margins_were_set_;
+ bool vertical_margins_were_set_;
+
+ // Text to display when empty.
+ string16 text_to_display_when_empty_;
+
+ // The accessible name of the text field.
+ string16 accessible_name_;
+
+ // The input type of this text field.
+ ui::TextInputType text_input_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(Textfield);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_H_
diff --git a/ui/views/controls/textfield/textfield_controller.h b/ui/views/controls/textfield/textfield_controller.h
new file mode 100644
index 0000000..ef2bdda
--- /dev/null
+++ b/ui/views/controls/textfield/textfield_controller.h
@@ -0,0 +1,46 @@
+// 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.
+
+#ifndef UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_CONTROLLER_H_
+#define UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_CONTROLLER_H_
+#pragma once
+
+#include "base/string16.h"
+
+namespace views {
+
+class KeyEvent;
+class Textfield;
+
+// This defines the callback interface for other code to be notified of changes
+// in the state of a text field.
+class TextfieldController {
+ public:
+ // 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;
+
+ // This method is called to get notified about keystrokes in the edit.
+ // Returns true if the message was handled and should not be processed
+ // further. If it returns false the processing continues.
+ virtual bool HandleKeyEvent(Textfield* sender,
+ const KeyEvent& key_event) = 0;
+
+ // Called before performing a user action that may change the textfield.
+ // It's currently only supported by Views implementation.
+ virtual void OnBeforeUserAction(Textfield* sender) {}
+
+ // Called after performing a user action that may change the textfield.
+ // It's currently only supported by Views implementation.
+ virtual void OnAfterUserAction(Textfield* sender) {}
+
+ protected:
+ virtual ~TextfieldController() {}
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_CONTROLLER_H_
diff --git a/ui/views/controls/textfield/textfield_views_model.cc b/ui/views/controls/textfield/textfield_views_model.cc
new file mode 100644
index 0000000..0895f91
--- /dev/null
+++ b/ui/views/controls/textfield/textfield_views_model.cc
@@ -0,0 +1,795 @@
+// 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.
+
+#include "ui/views/controls/textfield/textfield_views_model.h"
+
+#include <algorithm>
+
+#include "base/i18n/break_iterator.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/utf_string_conversions.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+#include "ui/base/range/range.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/render_text.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "views/views_delegate.h"
+
+namespace views {
+
+namespace internal {
+
+// An edit object holds enough information/state to undo/redo the
+// change. Two edits are merged when possible, for example, when
+// you type new characters in sequence. |Commit()| can be used to
+// mark an edit as an independent edit and it shouldn't be merged.
+// (For example, when you did undo/redo, or a text is appended via
+// API)
+class Edit {
+ public:
+ enum Type {
+ INSERT_EDIT,
+ DELETE_EDIT,
+ REPLACE_EDIT
+ };
+
+ virtual ~Edit() {
+ }
+
+ // Revert the change made by this edit in |model|.
+ void Undo(TextfieldViewsModel* model) {
+ model->ModifyText(new_text_start_, new_text_end(),
+ old_text_, old_text_start_,
+ old_cursor_pos_);
+ }
+
+ // Apply the change of this edit to the |model|.
+ void Redo(TextfieldViewsModel* model) {
+ model->ModifyText(old_text_start_, old_text_end(),
+ new_text_, new_text_start_,
+ new_cursor_pos_);
+ }
+
+ // Try to merge the |edit| into this edit. Returns true if merge was
+ // successful, or false otherwise. Merged edit will be deleted after
+ // redo and should not be reused.
+ bool Merge(const Edit* edit) {
+ if (edit->merge_with_previous()) {
+ MergeSet(edit);
+ return true;
+ }
+ return mergeable() && edit->mergeable() && DoMerge(edit);
+ }
+
+ // Commits the edit and marks as un-mergeable.
+ void Commit() { merge_type_ = DO_NOT_MERGE; }
+
+ private:
+ friend class InsertEdit;
+ friend class ReplaceEdit;
+ friend class DeleteEdit;
+
+ Edit(Type type,
+ MergeType merge_type,
+ size_t old_cursor_pos,
+ string16 old_text,
+ size_t old_text_start,
+ bool delete_backward,
+ size_t new_cursor_pos,
+ string16 new_text,
+ size_t new_text_start)
+ : type_(type),
+ merge_type_(merge_type),
+ old_cursor_pos_(old_cursor_pos),
+ old_text_(old_text),
+ old_text_start_(old_text_start),
+ delete_backward_(delete_backward),
+ new_cursor_pos_(new_cursor_pos),
+ new_text_(new_text),
+ new_text_start_(new_text_start) {
+ }
+
+ // A template method pattern that provides specific merge
+ // implementation for each type of edit.
+ virtual bool DoMerge(const Edit* edit) = 0;
+
+ Type type() const { return type_; }
+
+ // Can this edit be merged?
+ bool mergeable() const { return merge_type_ == MERGEABLE; }
+
+ // Should this edit be forcibly merged with the previous edit?
+ bool merge_with_previous() const {
+ return merge_type_ == MERGE_WITH_PREVIOUS;
+ }
+
+ // Returns the end index of the |old_text_|.
+ size_t old_text_end() const { return old_text_start_ + old_text_.length(); }
+
+ // Returns the end index of the |new_text_|.
+ size_t new_text_end() const { return new_text_start_ + new_text_.length(); }
+
+ // Merge the Set edit into the current edit. This is a special case to
+ // handle an omnibox setting autocomplete string after new character is
+ // typed in.
+ void MergeSet(const Edit* edit) {
+ CHECK_EQ(REPLACE_EDIT, edit->type_);
+ CHECK_EQ(0U, edit->old_text_start_);
+ CHECK_EQ(0U, edit->new_text_start_);
+ string16 old_text = edit->old_text_;
+ old_text.erase(new_text_start_, new_text_.length());
+ old_text.insert(old_text_start_, old_text_);
+ // SetText() replaces entire text. Set |old_text_| to the entire
+ // replaced text with |this| edit undone.
+ old_text_ = old_text;
+ old_text_start_ = edit->old_text_start_;
+ delete_backward_ = false;
+
+ new_text_ = edit->new_text_;
+ new_text_start_ = edit->new_text_start_;
+ merge_type_ = DO_NOT_MERGE;
+ }
+
+ Type type_;
+
+ // True if the edit can be marged.
+ MergeType merge_type_;
+ // Old cursor position.
+ size_t old_cursor_pos_;
+ // Deleted text by this edit.
+ string16 old_text_;
+ // The index of |old_text_|.
+ size_t old_text_start_;
+ // True if the deletion is made backward.
+ bool delete_backward_;
+ // New cursor position.
+ size_t new_cursor_pos_;
+ // Added text.
+ string16 new_text_;
+ // The index of |new_text_|
+ size_t new_text_start_;
+
+ DISALLOW_COPY_AND_ASSIGN(Edit);
+};
+
+class InsertEdit : public Edit {
+ public:
+ InsertEdit(bool mergeable, const string16& new_text, size_t at)
+ : Edit(INSERT_EDIT,
+ mergeable ? MERGEABLE : DO_NOT_MERGE,
+ at /* old cursor */,
+ string16(),
+ at,
+ false /* N/A */,
+ at + new_text.length() /* new cursor */,
+ new_text,
+ at) {
+ }
+
+ // Edit implementation.
+ virtual bool DoMerge(const Edit* edit) OVERRIDE {
+ if (edit->type() != INSERT_EDIT || new_text_end() != edit->new_text_start_)
+ return false;
+ // If continuous edit, merge it.
+ // TODO(oshima): gtk splits edits between whitespace. Find out what
+ // we want to here and implement if necessary.
+ new_text_ += edit->new_text_;
+ new_cursor_pos_ = edit->new_cursor_pos_;
+ return true;
+ }
+};
+
+class ReplaceEdit : public Edit {
+ public:
+ ReplaceEdit(MergeType merge_type,
+ const string16& old_text,
+ size_t old_cursor_pos,
+ size_t old_text_start,
+ bool backward,
+ size_t new_cursor_pos,
+ const string16& new_text,
+ size_t new_text_start)
+ : Edit(REPLACE_EDIT, merge_type,
+ old_cursor_pos,
+ old_text,
+ old_text_start,
+ backward,
+ new_cursor_pos,
+ new_text,
+ new_text_start) {
+ }
+
+ // Edit implementation.
+ virtual bool DoMerge(const Edit* edit) OVERRIDE {
+ if (edit->type() == DELETE_EDIT ||
+ new_text_end() != edit->old_text_start_ ||
+ edit->old_text_start_ != edit->new_text_start_)
+ return false;
+ old_text_ += edit->old_text_;
+ new_text_ += edit->new_text_;
+ new_cursor_pos_ = edit->new_cursor_pos_;
+ return true;
+ }
+};
+
+class DeleteEdit : public Edit {
+ public:
+ DeleteEdit(bool mergeable,
+ const string16& text,
+ size_t text_start,
+ bool backward)
+ : Edit(DELETE_EDIT,
+ mergeable ? MERGEABLE : DO_NOT_MERGE,
+ (backward ? text_start + text.length() : text_start),
+ text,
+ text_start,
+ backward,
+ text_start,
+ string16(),
+ text_start) {
+ }
+
+ // Edit implementation.
+ virtual bool DoMerge(const Edit* edit) OVERRIDE {
+ if (edit->type() != DELETE_EDIT)
+ return false;
+
+ if (delete_backward_) {
+ // backspace can be merged only with backspace at the
+ // same position.
+ if (!edit->delete_backward_ || old_text_start_ != edit->old_text_end())
+ return false;
+ old_text_start_ = edit->old_text_start_;
+ old_text_ = edit->old_text_ + old_text_;
+ new_cursor_pos_ = edit->new_cursor_pos_;
+ } else {
+ // delete can be merged only with delete at the same
+ // position.
+ if (edit->delete_backward_ || old_text_start_ != edit->old_text_start_)
+ return false;
+ old_text_ += edit->old_text_;
+ }
+ return true;
+ }
+};
+
+} // namespace internal
+
+using internal::Edit;
+using internal::DeleteEdit;
+using internal::InsertEdit;
+using internal::ReplaceEdit;
+using internal::MergeType;
+using internal::DO_NOT_MERGE;
+using internal::MERGE_WITH_PREVIOUS;
+using internal::MERGEABLE;
+
+/////////////////////////////////////////////////////////////////
+// TextfieldViewsModel: public
+
+TextfieldViewsModel::Delegate::~Delegate() {
+}
+
+TextfieldViewsModel::TextfieldViewsModel(Delegate* delegate)
+ : delegate_(delegate),
+ render_text_(gfx::RenderText::CreateRenderText()),
+ is_password_(false),
+ current_edit_(edit_history_.end()) {
+}
+
+TextfieldViewsModel::~TextfieldViewsModel() {
+ ClearEditHistory();
+ ClearComposition();
+}
+
+const string16& TextfieldViewsModel::GetText() const {
+ return render_text_->text();
+}
+
+bool TextfieldViewsModel::SetText(const string16& text) {
+ bool changed = false;
+ if (HasCompositionText()) {
+ ConfirmCompositionText();
+ changed = true;
+ }
+ if (GetText() != text) {
+ if (changed) // No need to remember composition.
+ Undo();
+ size_t old_cursor = GetCursorPosition();
+ size_t new_cursor = old_cursor > text.length() ? text.length() : old_cursor;
+ SelectAll();
+ // If there is a composition text, don't merge with previous edit.
+ // Otherwise, force merge the edits.
+ ExecuteAndRecordReplace(
+ changed ? DO_NOT_MERGE : MERGE_WITH_PREVIOUS,
+ old_cursor,
+ new_cursor,
+ text,
+ 0U);
+ render_text_->SetCursorPosition(new_cursor);
+ }
+ ClearSelection();
+ return changed;
+}
+
+void TextfieldViewsModel::Append(const string16& text) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
+ size_t save = GetCursorPosition();
+ if (render_text_->GetTextDirection() == base::i18n::LEFT_TO_RIGHT)
+ MoveCursorRight(gfx::LINE_BREAK, false);
+ else
+ MoveCursorLeft(gfx::LINE_BREAK, false);
+ InsertText(text);
+ render_text_->SetCursorPosition(save);
+ ClearSelection();
+}
+
+bool TextfieldViewsModel::Delete() {
+ if (HasCompositionText()) {
+ // No undo/redo for composition text.
+ CancelCompositionText();
+ return true;
+ }
+ if (HasSelection()) {
+ DeleteSelection();
+ return true;
+ }
+ if (GetText().length() > GetCursorPosition()) {
+ size_t cursor_position = GetCursorPosition();
+ size_t next_grapheme_index =
+ render_text_->GetIndexOfNextGrapheme(cursor_position);
+ ExecuteAndRecordDelete(cursor_position, next_grapheme_index, true);
+ return true;
+ }
+ return false;
+}
+
+bool TextfieldViewsModel::Backspace() {
+ if (HasCompositionText()) {
+ // No undo/redo for composition text.
+ CancelCompositionText();
+ return true;
+ }
+ if (HasSelection()) {
+ DeleteSelection();
+ return true;
+ }
+ if (GetCursorPosition() > 0) {
+ size_t cursor_position = GetCursorPosition();
+ ExecuteAndRecordDelete(cursor_position, cursor_position - 1, true);
+ return true;
+ }
+ return false;
+}
+
+size_t TextfieldViewsModel::GetCursorPosition() const {
+ return render_text_->GetCursorPosition();
+}
+
+void TextfieldViewsModel::MoveCursorLeft(gfx::BreakType break_type,
+ bool select) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
+ render_text_->MoveCursorLeft(break_type, select);
+}
+
+void TextfieldViewsModel::MoveCursorRight(gfx::BreakType break_type,
+ bool select) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
+ render_text_->MoveCursorRight(break_type, select);
+}
+
+bool TextfieldViewsModel::MoveCursorTo(const gfx::SelectionModel& selection) {
+ if (HasCompositionText()) {
+ ConfirmCompositionText();
+ // ConfirmCompositionText() updates cursor position. Need to reflect it in
+ // the SelectionModel parameter of MoveCursorTo().
+ if (render_text_->GetSelectionStart() != selection.selection_end())
+ return render_text_->SelectRange(ui::Range(
+ render_text_->GetSelectionStart(), selection.selection_end()));
+ gfx::SelectionModel sel(selection.selection_end(),
+ selection.caret_pos(),
+ selection.caret_placement());
+ return render_text_->MoveCursorTo(sel);
+ }
+ return render_text_->MoveCursorTo(selection);
+}
+
+bool TextfieldViewsModel::MoveCursorTo(const gfx::Point& point, bool select) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
+ return render_text_->MoveCursorTo(point, select);
+}
+
+string16 TextfieldViewsModel::GetSelectedText() const {
+ return GetText().substr(render_text_->MinOfSelection(),
+ (render_text_->MaxOfSelection() - render_text_->MinOfSelection()));
+}
+
+void TextfieldViewsModel::GetSelectedRange(ui::Range* range) const {
+ range->set_start(render_text_->GetSelectionStart());
+ range->set_end(render_text_->GetCursorPosition());
+}
+
+void TextfieldViewsModel::SelectRange(const ui::Range& range) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
+ render_text_->SelectRange(range);
+}
+
+void TextfieldViewsModel::GetSelectionModel(gfx::SelectionModel* sel) const {
+ *sel = render_text_->selection_model();
+}
+
+void TextfieldViewsModel::SelectSelectionModel(const gfx::SelectionModel& sel) {
+ if (HasCompositionText())
+ ConfirmCompositionText();
+ render_text_->MoveCursorTo(sel);
+}
+
+void TextfieldViewsModel::SelectAll() {
+ if (HasCompositionText())
+ ConfirmCompositionText();
+ render_text_->SelectAll();
+}
+
+void TextfieldViewsModel::SelectWord() {
+ if (HasCompositionText())
+ ConfirmCompositionText();
+ render_text_->SelectWord();
+}
+
+void TextfieldViewsModel::ClearSelection() {
+ if (HasCompositionText())
+ ConfirmCompositionText();
+ render_text_->ClearSelection();
+}
+
+bool TextfieldViewsModel::CanUndo() {
+ return edit_history_.size() && current_edit_ != edit_history_.end();
+}
+
+bool TextfieldViewsModel::CanRedo() {
+ if (!edit_history_.size())
+ return false;
+ // There is no redo iff the current edit is the last element
+ // in the history.
+ EditHistory::iterator iter = current_edit_;
+ return iter == edit_history_.end() || // at the top.
+ ++iter != edit_history_.end();
+}
+
+bool TextfieldViewsModel::Undo() {
+ if (!CanUndo())
+ return false;
+ DCHECK(!HasCompositionText());
+ if (HasCompositionText()) // safe guard for release build.
+ CancelCompositionText();
+
+ string16 old = GetText();
+ size_t old_cursor = GetCursorPosition();
+ (*current_edit_)->Commit();
+ (*current_edit_)->Undo(this);
+
+ if (current_edit_ == edit_history_.begin())
+ current_edit_ = edit_history_.end();
+ else
+ current_edit_--;
+ return old != GetText() || old_cursor != GetCursorPosition();
+}
+
+bool TextfieldViewsModel::Redo() {
+ if (!CanRedo())
+ return false;
+ DCHECK(!HasCompositionText());
+ if (HasCompositionText()) // safe guard for release build.
+ CancelCompositionText();
+
+ if (current_edit_ == edit_history_.end())
+ current_edit_ = edit_history_.begin();
+ else
+ current_edit_ ++;
+ string16 old = GetText();
+ size_t old_cursor = GetCursorPosition();
+ (*current_edit_)->Redo(this);
+ return old != GetText() || old_cursor != GetCursorPosition();
+}
+
+string16 TextfieldViewsModel::GetVisibleText() const {
+ return GetVisibleText(0U, GetText().length());
+}
+
+bool TextfieldViewsModel::Cut() {
+ if (!HasCompositionText() && HasSelection()) {
+ ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate
+ ->GetClipboard()).WriteText(GetSelectedText());
+ // A trick to let undo/redo handle cursor correctly.
+ // Undoing CUT moves the cursor to the end of the change rather
+ // than beginning, unlike Delete/Backspace.
+ // TODO(oshima): Change Delete/Backspace to use DeleteSelection,
+ // update DeleteEdit and remove this trick.
+ render_text_->SelectRange(ui::Range(render_text_->GetCursorPosition(),
+ render_text_->GetSelectionStart()));
+ DeleteSelection();
+ return true;
+ }
+ return false;
+}
+
+void TextfieldViewsModel::Copy() {
+ if (!HasCompositionText() && HasSelection()) {
+ ui::ScopedClipboardWriter(views::ViewsDelegate::views_delegate
+ ->GetClipboard()).WriteText(GetSelectedText());
+ }
+}
+
+bool TextfieldViewsModel::Paste() {
+ string16 result;
+ views::ViewsDelegate::views_delegate->GetClipboard()
+ ->ReadText(ui::Clipboard::BUFFER_STANDARD, &result);
+ if (!result.empty()) {
+ InsertTextInternal(result, false);
+ return true;
+ }
+ return false;
+}
+
+bool TextfieldViewsModel::HasSelection() const {
+ return !render_text_->EmptySelection();
+}
+
+void TextfieldViewsModel::DeleteSelection() {
+ DCHECK(!HasCompositionText());
+ DCHECK(HasSelection());
+ ExecuteAndRecordDelete(render_text_->GetSelectionStart(),
+ render_text_->GetCursorPosition(), false);
+}
+
+void TextfieldViewsModel::DeleteSelectionAndInsertTextAt(
+ const string16& text, size_t position) {
+ if (HasCompositionText())
+ CancelCompositionText();
+ ExecuteAndRecordReplace(DO_NOT_MERGE,
+ GetCursorPosition(),
+ position + text.length(),
+ text,
+ position);
+}
+
+string16 TextfieldViewsModel::GetTextFromRange(const ui::Range& range) const {
+ if (range.IsValid() && range.GetMin() < GetText().length())
+ return GetText().substr(range.GetMin(), range.length());
+ return string16();
+}
+
+void TextfieldViewsModel::GetTextRange(ui::Range* range) const {
+ *range = ui::Range(0, GetText().length());
+}
+
+void TextfieldViewsModel::SetCompositionText(
+ const ui::CompositionText& composition) {
+ if (HasCompositionText())
+ CancelCompositionText();
+ else if (HasSelection())
+ DeleteSelection();
+
+ if (composition.text.empty())
+ return;
+
+ size_t cursor = GetCursorPosition();
+ string16 new_text = GetText();
+ render_text_->SetText(new_text.insert(cursor, composition.text));
+ ui::Range range(cursor, cursor + composition.text.length());
+ render_text_->SetCompositionRange(range);
+ // TODO(msw): Support multiple composition underline ranges.
+
+ if (composition.selection.IsValid()) {
+ size_t start =
+ std::min(range.start() + composition.selection.start(), range.end());
+ size_t end =
+ std::min(range.start() + composition.selection.end(), range.end());
+ render_text_->SelectRange(ui::Range(start, end));
+ } else {
+ render_text_->SetCursorPosition(range.end());
+ }
+}
+
+void TextfieldViewsModel::ConfirmCompositionText() {
+ DCHECK(HasCompositionText());
+ ui::Range range = render_text_->GetCompositionRange();
+ string16 text = GetText().substr(range.start(), range.length());
+ // TODO(oshima): current behavior on ChromeOS is a bit weird and not
+ // sure exactly how this should work. Find out and fix if necessary.
+ AddOrMergeEditHistory(new InsertEdit(false, text, range.start()));
+ render_text_->SetCursorPosition(range.end());
+ ClearComposition();
+ if (delegate_)
+ delegate_->OnCompositionTextConfirmedOrCleared();
+}
+
+void TextfieldViewsModel::CancelCompositionText() {
+ DCHECK(HasCompositionText());
+ ui::Range range = render_text_->GetCompositionRange();
+ ClearComposition();
+ string16 new_text = GetText();
+ render_text_->SetText(new_text.erase(range.start(), range.length()));
+ render_text_->SetCursorPosition(range.start());
+ if (delegate_)
+ delegate_->OnCompositionTextConfirmedOrCleared();
+}
+
+void TextfieldViewsModel::ClearComposition() {
+ render_text_->SetCompositionRange(ui::Range::InvalidRange());
+}
+
+void TextfieldViewsModel::GetCompositionTextRange(ui::Range* range) const {
+ *range = ui::Range(render_text_->GetCompositionRange());
+}
+
+bool TextfieldViewsModel::HasCompositionText() const {
+ return !render_text_->GetCompositionRange().is_empty();
+}
+
+/////////////////////////////////////////////////////////////////
+// TextfieldViewsModel: private
+
+string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const {
+ DCHECK(end >= begin);
+ if (is_password_)
+ return string16(end - begin, '*');
+ return GetText().substr(begin, end - begin);
+}
+
+void TextfieldViewsModel::InsertTextInternal(const string16& text,
+ bool mergeable) {
+ if (HasCompositionText()) {
+ CancelCompositionText();
+ ExecuteAndRecordInsert(text, mergeable);
+ } else if (HasSelection()) {
+ ExecuteAndRecordReplaceSelection(mergeable ? MERGEABLE : DO_NOT_MERGE,
+ text);
+ } else {
+ ExecuteAndRecordInsert(text, mergeable);
+ }
+}
+
+void TextfieldViewsModel::ReplaceTextInternal(const string16& text,
+ bool mergeable) {
+ if (HasCompositionText()) {
+ CancelCompositionText();
+ } else if (!HasSelection()) {
+ size_t cursor = GetCursorPosition();
+ const gfx::SelectionModel& model = render_text_->selection_model();
+ // When there is no selection, the default is to replace the next grapheme
+ // with |text|. So, need to find the index of next grapheme first.
+ size_t next = render_text_->GetIndexOfNextGrapheme(cursor);
+ if (next == model.selection_end())
+ render_text_->MoveCursorTo(model);
+ else
+ render_text_->SelectRange(ui::Range(next, model.selection_end()));
+ }
+ // Edit history is recorded in InsertText.
+ InsertTextInternal(text, mergeable);
+}
+
+void TextfieldViewsModel::ClearEditHistory() {
+ STLDeleteContainerPointers(edit_history_.begin(),
+ edit_history_.end());
+ edit_history_.clear();
+ current_edit_ = edit_history_.end();
+}
+
+void TextfieldViewsModel::ClearRedoHistory() {
+ if (edit_history_.begin() == edit_history_.end())
+ return;
+ if (current_edit_ == edit_history_.end()) {
+ ClearEditHistory();
+ return;
+ }
+ EditHistory::iterator delete_start = current_edit_;
+ delete_start++;
+ STLDeleteContainerPointers(delete_start, edit_history_.end());
+ edit_history_.erase(delete_start, edit_history_.end());
+}
+
+void TextfieldViewsModel::ExecuteAndRecordDelete(size_t from,
+ size_t to,
+ bool mergeable) {
+ size_t old_text_start = std::min(from, to);
+ const string16 text = GetText().substr(old_text_start,
+ std::abs(static_cast<long>(from - to)));
+ bool backward = from > to;
+ Edit* edit = new DeleteEdit(mergeable, text, old_text_start, backward);
+ bool delete_edit = AddOrMergeEditHistory(edit);
+ edit->Redo(this);
+ if (delete_edit)
+ delete edit;
+}
+
+void TextfieldViewsModel::ExecuteAndRecordReplaceSelection(
+ MergeType merge_type, const string16& new_text) {
+ size_t new_text_start = render_text_->MinOfSelection();
+ size_t new_cursor_pos = new_text_start + new_text.length();
+ ExecuteAndRecordReplace(merge_type,
+ GetCursorPosition(),
+ new_cursor_pos,
+ new_text,
+ new_text_start);
+}
+
+void TextfieldViewsModel::ExecuteAndRecordReplace(MergeType merge_type,
+ size_t old_cursor_pos,
+ size_t new_cursor_pos,
+ const string16& new_text,
+ size_t new_text_start) {
+ size_t old_text_start = render_text_->MinOfSelection();
+ bool backward =
+ render_text_->GetSelectionStart() > render_text_->GetCursorPosition();
+ Edit* edit = new ReplaceEdit(merge_type,
+ GetSelectedText(),
+ old_cursor_pos,
+ old_text_start,
+ backward,
+ new_cursor_pos,
+ new_text,
+ new_text_start);
+ bool delete_edit = AddOrMergeEditHistory(edit);
+ edit->Redo(this);
+ if (delete_edit)
+ delete edit;
+}
+
+void TextfieldViewsModel::ExecuteAndRecordInsert(const string16& text,
+ bool mergeable) {
+ Edit* edit = new InsertEdit(mergeable, text, GetCursorPosition());
+ bool delete_edit = AddOrMergeEditHistory(edit);
+ edit->Redo(this);
+ if (delete_edit)
+ delete edit;
+}
+
+bool TextfieldViewsModel::AddOrMergeEditHistory(Edit* edit) {
+ ClearRedoHistory();
+
+ if (current_edit_ != edit_history_.end() && (*current_edit_)->Merge(edit)) {
+ // If a current edit exists and has been merged with a new edit,
+ // don't add to the history, and return true to delete |edit| after
+ // redo.
+ return true;
+ }
+ edit_history_.push_back(edit);
+ if (current_edit_ == edit_history_.end()) {
+ // If there is no redoable edit, this is the 1st edit because
+ // RedoHistory has been already deleted.
+ DCHECK_EQ(1u, edit_history_.size());
+ current_edit_ = edit_history_.begin();
+ } else {
+ current_edit_++;
+ }
+ return false;
+}
+
+void TextfieldViewsModel::ModifyText(size_t delete_from,
+ size_t delete_to,
+ const string16& new_text,
+ size_t new_text_insert_at,
+ size_t new_cursor_pos) {
+ DCHECK_LE(delete_from, delete_to);
+ string16 text = GetText();
+ ClearComposition();
+ if (delete_from != delete_to)
+ render_text_->SetText(text.erase(delete_from, delete_to - delete_from));
+ if (!new_text.empty())
+ render_text_->SetText(text.insert(new_text_insert_at, new_text));
+ render_text_->SetCursorPosition(new_cursor_pos);
+ // TODO(oshima): mac selects the text that is just undone (but gtk doesn't).
+ // This looks fine feature and we may want to do the same.
+}
+
+} // namespace views
diff --git a/ui/views/controls/textfield/textfield_views_model.h b/ui/views/controls/textfield/textfield_views_model.h
new file mode 100644
index 0000000..d13a7b6d
--- /dev/null
+++ b/ui/views/controls/textfield/textfield_views_model.h
@@ -0,0 +1,329 @@
+// 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.
+
+#ifndef UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_VIEWS_MODEL_H_
+#define UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_VIEWS_MODEL_H_
+#pragma once
+
+#include <list>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string16.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/ime/composition_text.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/render_text.h"
+#include "views/views_export.h"
+
+namespace gfx {
+class RenderText;
+} // namespace gfx
+
+namespace ui {
+class Range;
+} // namespace ui
+
+namespace views {
+
+namespace internal {
+// Internal Edit class that keeps track of edits for undo/redo.
+class Edit;
+
+// C++ doesn't allow forward decl enum, so let's define here.
+enum MergeType {
+ // The edit should not be merged with next edit. It still may
+ // be merged with an edit with MERGE_WITH_PREVIOUS.
+ DO_NOT_MERGE,
+ // The edit can be merged with next edit when possible.
+ MERGEABLE,
+ // Does the edit have to be merged with previous edit?
+ // This forces the merge even if the previous edit is marked
+ // as DO_NOT_MERGE.
+ MERGE_WITH_PREVIOUS,
+};
+
+} // namespace internal
+
+// A model that represents a text content for TextfieldViews.
+// It supports editing, selection and cursor manipulation.
+class VIEWS_EXPORT TextfieldViewsModel {
+ public:
+ // Delegate interface implemented by the textfield view class to provided
+ // additional functionalities required by the model.
+ class VIEWS_EXPORT 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();
+
+ void set_is_password(bool is_password) {
+ is_password_ = is_password;
+ }
+
+ // Edit related methods.
+
+ const string16& GetText() const;
+ // Sets the text. Returns true if the text has been modified. The
+ // current composition text will be confirmed first. Setting
+ // the same text will not add edit history because it's not user
+ // visible change nor user-initiated change. This allow a client
+ // code to set the same text multiple times without worrying about
+ // messing edit history.
+ bool SetText(const string16& text);
+
+ gfx::RenderText* render_text() { return render_text_.get(); }
+
+ // Inserts given |text| at the current cursor position.
+ // The current composition text will be cleared.
+ void InsertText(const string16& text) {
+ InsertTextInternal(text, false);
+ }
+
+ // Inserts a character at the current cursor position.
+ void InsertChar(char16 c) {
+ InsertTextInternal(string16(&c, 1), true);
+ }
+
+ // Replaces characters at the current position with characters in given text.
+ // The current composition text will be cleared.
+ void ReplaceText(const string16& text) {
+ ReplaceTextInternal(text, false);
+ }
+
+ // Replaces the char at the current position with given character.
+ void ReplaceChar(char16 c) {
+ ReplaceTextInternal(string16(&c, 1), true);
+ }
+
+ // 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.
+
+ // Returns the current cursor position.
+ size_t GetCursorPosition() const;
+
+ // Moves the cursor, see RenderText for additional details.
+ // The current composition text will be confirmed.
+ void MoveCursorLeft(gfx::BreakType break_type, bool select);
+ void MoveCursorRight(gfx::BreakType break_type, bool select);
+
+ // Moves the selection to the specified selection in |selection|.
+ // If there is composition text, it will be confirmed, which will update the
+ // selection range, and it overrides the selection_start to which the
+ // selection will move to.
+ bool MoveCursorTo(const gfx::SelectionModel& selection);
+
+ // Helper function to call MoveCursorTo on the TextfieldViewsModel.
+ bool MoveCursorTo(const gfx::Point& point, bool select);
+
+ // Selection related method
+
+ // Returns the selected text.
+ string16 GetSelectedText() const;
+
+ // Gets the selected range.
+ void GetSelectedRange(ui::Range* range) const;
+
+ // The current composition text will be confirmed. The selection starts with
+ // the range's start position, and ends with the range's end position,
+ // therefore the cursor position becomes the end position.
+ void SelectRange(const ui::Range& range);
+
+ void GetSelectionModel(gfx::SelectionModel* sel) const;
+
+ // The current composition text will be confirmed.
+ // render_text_'s selection model is set to |sel|.
+ void SelectSelectionModel(const gfx::SelectionModel& sel);
+
+ // 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 true if there is an undoable edit.
+ bool CanUndo();
+
+ // Returns true if there is an redoable edit.
+ bool CanRedo();
+
+ // Undo edit. Returns true if undo changed the text.
+ bool Undo();
+
+ // Redo edit. Returns true if redo changed the text.
+ bool Redo();
+
+ // Returns visible text. If the field is password, it returns the
+ // sequence of "*".
+ string16 GetVisibleText() const;
+
+ // Cuts the currently selected text and puts it to clipboard. Returns true
+ // if text has changed after cutting.
+ bool Cut();
+
+ // Copies the currently selected text and puts it to clipboard.
+ void Copy();
+
+ // Pastes text from the clipboard at current cursor position. Returns true
+ // if text has changed after pasting.
+ bool Paste();
+
+ // 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();
+
+ // Deletes the selected text (if any) and insert text at given
+ // position.
+ void DeleteSelectionAndInsertTextAt(
+ const string16& text, size_t position);
+
+ // 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 CancelCompositionText();
+
+ // 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;
+ friend class NativeTextfieldViewsTest;
+ friend class TextfieldViewsModelTest;
+ friend class UndoRedo_BasicTest;
+ friend class UndoRedo_CutCopyPasteTest;
+ friend class UndoRedo_ReplaceTest;
+ friend class internal::Edit;
+
+ FRIEND_TEST_ALL_PREFIXES(TextfieldViewsModelTest, UndoRedo_BasicTest);
+ FRIEND_TEST_ALL_PREFIXES(TextfieldViewsModelTest, UndoRedo_CutCopyPasteTest);
+ FRIEND_TEST_ALL_PREFIXES(TextfieldViewsModelTest, UndoRedo_ReplaceTest);
+
+ // Returns the visible text given |start| and |end|.
+ string16 GetVisibleText(size_t start, size_t end) const;
+
+ // Insert the given |text|. |mergeable| indicates if this insert
+ // operation can be merged to previous edit in the edit history.
+ void InsertTextInternal(const string16& text, bool mergeable);
+
+ // Replace the current text with the given |text|. |mergeable|
+ // indicates if this replace operation can be merged to previous
+ // edit in the edit history.
+ void ReplaceTextInternal(const string16& text, bool mergeable);
+
+ // Clears all edit history.
+ void ClearEditHistory();
+
+ // Clears redo history.
+ void ClearRedoHistory();
+
+ // Executes and records edit operations.
+ void ExecuteAndRecordDelete(size_t from, size_t to, bool mergeable);
+ void ExecuteAndRecordReplaceSelection(internal::MergeType merge_type,
+ const string16& text);
+ void ExecuteAndRecordReplace(internal::MergeType merge_type,
+ size_t old_cursor_pos,
+ size_t new_cursor_pos,
+ const string16& text,
+ size_t new_text_start);
+ void ExecuteAndRecordInsert(const string16& text, bool mergeable);
+
+ // Adds or merge |edit| into edit history. Return true if the edit
+ // has been merged and must be deleted after redo.
+ bool AddOrMergeEditHistory(internal::Edit* edit);
+
+ // Modify the text buffer in following way:
+ // 1) Delete the string from |delete_from| to |delte_to|.
+ // 2) Insert the |new_text| at the index |new_text_insert_at|.
+ // Note that the index is after deletion.
+ // 3) Move the cursor to |new_cursor_pos|.
+ void ModifyText(size_t delete_from,
+ size_t delete_to,
+ const string16& new_text,
+ size_t new_text_insert_at,
+ size_t new_cursor_pos);
+
+ void ClearComposition();
+
+ // Pointer to a TextfieldViewsModel::Delegate instance, should be provided by
+ // the View object.
+ Delegate* delegate_;
+
+ // The stylized text, cursor, selection, and the visual layout model.
+ scoped_ptr<gfx::RenderText> render_text_;
+
+ // True if the text is the password.
+ bool is_password_;
+
+ typedef std::list<internal::Edit*> EditHistory;
+ EditHistory edit_history_;
+
+ // An iterator that points to the current edit that can be undone.
+ // This iterator moves from the |end()|, meaining no edit to undo,
+ // to the last element (one before |end()|), meaning no edit to redo.
+ // There is no edit to undo (== end()) when:
+ // 1) in initial state. (nothing to undo)
+ // 2) very 1st edit is undone.
+ // 3) all edit history is removed.
+ // There is no edit to redo (== last element or no element) when:
+ // 1) in initial state. (nothing to redo)
+ // 2) new edit is added. (redo history is cleared)
+ // 3) redone all undone edits.
+ EditHistory::iterator current_edit_;
+
+ DISALLOW_COPY_AND_ASSIGN(TextfieldViewsModel);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_VIEWS_MODEL_H_
diff --git a/ui/views/controls/textfield/textfield_views_model_unittest.cc b/ui/views/controls/textfield/textfield_views_model_unittest.cc
new file mode 100644
index 0000000..1cf7d71
--- /dev/null
+++ b/ui/views/controls/textfield/textfield_views_model_unittest.cc
@@ -0,0 +1,1535 @@
+// 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.
+
+#include <vector>
+
+#include "base/auto_reset.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+#include "ui/base/range/range.h"
+#include "ui/gfx/render_text.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/controls/textfield/textfield_views_model.h"
+#include "ui/views/test/test_views_delegate.h"
+#include "ui/views/test/views_test_base.h"
+#include "views/views_delegate.h"
+
+namespace {
+
+struct WordAndCursor {
+ WordAndCursor(const wchar_t* w, size_t c)
+ : word(w),
+ cursor(c) {
+ }
+
+ const wchar_t* word;
+ size_t cursor;
+};
+
+} // namespace
+
+namespace views {
+
+class TextfieldViewsModelTest : public ViewsTestBase,
+ public TextfieldViewsModel::Delegate {
+ public:
+ TextfieldViewsModelTest()
+ : ViewsTestBase(),
+ composition_text_confirmed_or_cleared_(false) {
+ }
+
+ virtual void OnCompositionTextConfirmedOrCleared() {
+ composition_text_confirmed_or_cleared_ = true;
+ }
+
+ protected:
+ void ResetModel(TextfieldViewsModel* model) const {
+ model->SetText(ASCIIToUTF16(""));
+ model->ClearEditHistory();
+ }
+
+ bool composition_text_confirmed_or_cleared_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TextfieldViewsModelTest);
+};
+
+#define EXPECT_STR_EQ(ascii, utf16) \
+ EXPECT_EQ(ASCIIToWide(ascii), UTF16ToWide(utf16))
+TEST_F(TextfieldViewsModelTest, EditString) {
+ TextfieldViewsModel model(NULL);
+ // append two strings
+ model.Append(ASCIIToUTF16("HILL"));
+ EXPECT_STR_EQ("HILL", model.GetText());
+ model.Append(ASCIIToUTF16("WORLD"));
+ EXPECT_STR_EQ("HILLWORLD", model.GetText());
+
+ // Insert "E" to make hello
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ model.InsertChar('E');
+ EXPECT_STR_EQ("HEILLWORLD", model.GetText());
+ // Replace "I" with "L"
+ model.ReplaceChar('L');
+ EXPECT_STR_EQ("HELLLWORLD", model.GetText());
+ model.ReplaceChar('L');
+ model.ReplaceChar('O');
+ EXPECT_STR_EQ("HELLOWORLD", model.GetText());
+
+ // Delete 6th char "W", then delete 5th char O"
+ EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Delete());
+ EXPECT_STR_EQ("HELLOORLD", model.GetText());
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_EQ(4U, model.GetCursorPosition());
+ EXPECT_STR_EQ("HELLORLD", model.GetText());
+
+ // Move the cursor to start. backspace should fail.
+ model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ EXPECT_FALSE(model.Backspace());
+ EXPECT_STR_EQ("HELLORLD", model.GetText());
+ // Move the cursor to the end. delete should fail.
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ EXPECT_FALSE(model.Delete());
+ EXPECT_STR_EQ("HELLORLD", model.GetText());
+ // but backspace should work.
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_STR_EQ("HELLORL", model.GetText());
+
+ model.MoveCursorTo(gfx::SelectionModel(5));
+ model.ReplaceText(ASCIIToUTF16(" WOR"));
+ EXPECT_STR_EQ("HELLO WORL", model.GetText());
+}
+
+TEST_F(TextfieldViewsModelTest, EditString_SimpleRTL) {
+ TextfieldViewsModel model(NULL);
+ // Append two strings.
+ model.Append(WideToUTF16(L"\x05d0\x05d1\x05d2"));
+ EXPECT_EQ(WideToUTF16(L"\x05d0\x05d1\x05d2"), model.GetText());
+ model.Append(WideToUTF16(L"\x05e0\x05e1\x05e2"));
+ EXPECT_EQ(WideToUTF16(L"\x05d0\x05d1\x05d2\x05e0\x05e1\x05e2"),
+ model.GetText());
+
+ // Insert 0x05f0.
+ model.MoveCursorTo(gfx::SelectionModel(1U));
+ model.InsertChar(0x05f0);
+ EXPECT_EQ(WideToUTF16(L"\x05d0\x05f0\x05d1\x05d2\x05e0\x05e1\x05e2"),
+ model.GetText());
+
+ // Replace "\x05d1" with "\x05f1".
+ model.ReplaceChar(0x05f1);
+ EXPECT_EQ(WideToUTF16(L"\x05d0\x05f0\x5f1\x05d2\x05e0\x05e1\x05e2"),
+ model.GetText());
+
+ // Delete and backspace.
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Delete());
+ EXPECT_EQ(WideToUTF16(L"\x05d0\x05f0\x5f1\x05e0\x05e1\x05e2"),
+ model.GetText());
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_EQ(WideToUTF16(L"\x05d0\x05f0\x05e0\x05e1\x05e2"), model.GetText());
+}
+
+TEST_F(TextfieldViewsModelTest, EditString_ComplexScript) {
+ TextfieldViewsModel model(NULL);
+ // Append two Hindi strings.
+ model.Append(WideToUTF16(L"\x0915\x093f\x0915\x094d\x0915"));
+ EXPECT_EQ(WideToUTF16(L"\x0915\x093f\x0915\x094d\x0915"),
+ model.GetText());
+ model.Append(WideToUTF16(L"\x0915\x094d\x092e\x094d"));
+ EXPECT_EQ(WideToUTF16(
+ L"\x0915\x093f\x0915\x094d\x0915\x0915\x094d\x092e\x094d"),
+ model.GetText());
+
+ // Check it is not able to place cursor in middle of a grapheme.
+ // TODO(xji): temporarily disable in platform Win since the complex script
+ // characters turned into empty square due to font regression. So, not able
+ // to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+ model.MoveCursorTo(gfx::SelectionModel(1U));
+ EXPECT_EQ(0U, model.GetCursorPosition());
+#endif
+
+ model.MoveCursorTo(gfx::SelectionModel(2U));
+ EXPECT_EQ(2U, model.GetCursorPosition());
+ model.InsertChar('a');
+ EXPECT_EQ(WideToUTF16(
+ L"\x0915\x093f\x0061\x0915\x094d\x0915\x0915\x094d\x092e\x094d"),
+ model.GetText());
+
+ // ReplaceChar will replace the whole grapheme.
+ model.ReplaceChar('b');
+ // TODO(xji): temporarily disable in platform Win since the complex script
+ // characters turned into empty square due to font regression. So, not able
+ // to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+ EXPECT_EQ(WideToUTF16(
+ L"\x0915\x093f\x0061\x0062\x0915\x0915\x094d\x092e\x094d"),
+ model.GetText());
+#endif
+ EXPECT_EQ(4U, model.GetCursorPosition());
+
+ // Delete should delete the whole grapheme.
+ model.MoveCursorTo(gfx::SelectionModel(0U));
+ // TODO(xji): temporarily disable in platform Win since the complex script
+ // characters turned into empty square due to font regression. So, not able
+ // to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+ EXPECT_TRUE(model.Delete());
+ EXPECT_EQ(WideToUTF16(L"\x0061\x0062\x0915\x0915\x094d\x092e\x094d"),
+ model.GetText());
+ model.MoveCursorTo(gfx::SelectionModel(model.GetText().length()));
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_EQ(WideToUTF16(L"\x0061\x0062\x0915\x0915\x094d\x092e"),
+ model.GetText());
+#endif
+
+ // Test cursor position and deletion for Hindi Virama.
+ model.SetText(WideToUTF16(L"\x0D38\x0D4D\x0D15\x0D16\x0D2E"));
+ model.MoveCursorTo(gfx::SelectionModel(0));
+ EXPECT_EQ(0U, model.GetCursorPosition());
+
+ // TODO(xji): temporarily disable in platform Win since the complex script
+ // characters turned into empty square due to font regression. So, not able
+ // to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+ model.MoveCursorTo(gfx::SelectionModel(1));
+ EXPECT_EQ(0U, model.GetCursorPosition());
+#endif
+
+ model.MoveCursorTo(gfx::SelectionModel(2));
+ EXPECT_EQ(2U, model.GetCursorPosition());
+
+ model.MoveCursorTo(gfx::SelectionModel(3));
+ EXPECT_EQ(3U, model.GetCursorPosition());
+
+ model.MoveCursorTo(gfx::SelectionModel(2));
+
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_EQ(WideToUTF16(L"\x0D38\x0D15\x0D16\x0D2E"), model.GetText());
+
+ // Test Delete/Backspace on Hebrew with non-spacing marks.
+ // TODO(xji): temporarily disable in platform Win since the complex script
+ // characters turned into empty square due to font regression. So, not able
+ // to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+ model.SetText(WideToUTF16(L"\x05d5\x05b7\x05D9\x05B0\x05D4\x05B4\x05D9"));
+ model.MoveCursorTo(gfx::SelectionModel(0));
+ EXPECT_TRUE(model.Delete());
+ EXPECT_TRUE(model.Delete());
+ EXPECT_TRUE(model.Delete());
+ EXPECT_TRUE(model.Delete());
+ EXPECT_EQ(WideToUTF16(L""), model.GetText());
+#endif
+
+ // The first 2 characters are not strong directionality characters.
+ model.SetText(WideToUTF16(L"\x002C\x0020\x05D1\x05BC\x05B7\x05E9\x05BC"));
+#if defined(OS_WIN)
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+#else
+ model.MoveCursorLeft(gfx::LINE_BREAK, false);
+#endif
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_EQ(WideToUTF16(L"\x002C\x0020\x05D1\x05BC\x05B7\x05E9"),
+ model.GetText());
+}
+
+TEST_F(TextfieldViewsModelTest, EmptyString) {
+ TextfieldViewsModel model(NULL);
+ EXPECT_EQ(string16(), model.GetText());
+ EXPECT_EQ(string16(), model.GetSelectedText());
+ EXPECT_EQ(string16(), model.GetVisibleText());
+
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(0U, model.GetCursorPosition());
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(0U, model.GetCursorPosition());
+
+ EXPECT_EQ(string16(), model.GetSelectedText());
+
+ EXPECT_FALSE(model.Delete());
+ EXPECT_FALSE(model.Backspace());
+}
+
+TEST_F(TextfieldViewsModelTest, Selection) {
+ TextfieldViewsModel model(NULL);
+ model.Append(ASCIIToUTF16("HELLO"));
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_STR_EQ("E", model.GetSelectedText());
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_STR_EQ("EL", model.GetSelectedText());
+
+ model.MoveCursorLeft(gfx::LINE_BREAK, true);
+ EXPECT_STR_EQ("H", model.GetSelectedText());
+ model.MoveCursorRight(gfx::LINE_BREAK, true);
+ EXPECT_STR_EQ("ELLO", model.GetSelectedText());
+ model.ClearSelection();
+ EXPECT_EQ(string16(), model.GetSelectedText());
+ model.SelectAll();
+ EXPECT_STR_EQ("HELLO", model.GetSelectedText());
+ // SelectAll should select towards the end.
+ gfx::SelectionModel sel;
+ model.GetSelectionModel(&sel);
+ EXPECT_EQ(0U, sel.selection_start());
+ EXPECT_EQ(5U, sel.selection_end());
+
+ // Select and move cursor
+ model.SelectRange(ui::Range(1U, 3U));
+ EXPECT_STR_EQ("EL", model.GetSelectedText());
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, false);
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ model.SelectRange(ui::Range(1U, 3U));
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ EXPECT_EQ(3U, model.GetCursorPosition());
+
+ // Select all and move cursor
+ model.SelectAll();
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, false);
+ EXPECT_EQ(0U, model.GetCursorPosition());
+ model.SelectAll();
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ EXPECT_EQ(5U, model.GetCursorPosition());
+}
+
+TEST_F(TextfieldViewsModelTest, Selection_BidiWithNonSpacingMarks) {
+ // Selection is a logical operation. And it should work with the arrow
+ // keys doing visual movements, while the selection is logical between
+ // the (logical) start and end points. Selection is simply defined as
+ // the portion of text between the logical positions of the start and end
+ // caret positions.
+ TextfieldViewsModel model(NULL);
+ // TODO(xji): temporarily disable in platform Win since the complex script
+ // characters turned into empty square due to font regression. So, not able
+ // to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+ model.Append(WideToUTF16(
+ L"abc\x05E9\x05BC\x05C1\x05B8\x05E0\x05B8"L"def"));
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(2U, model.render_text()->GetSelectionStart());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_EQ(WideToUTF16(L"c"), model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(2U, model.render_text()->GetSelectionStart());
+ EXPECT_EQ(7U, model.GetCursorPosition());
+ EXPECT_EQ(WideToUTF16(L"c\x05E9\x05BC\x05C1\x05B8"),
+ model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(2U, model.render_text()->GetSelectionStart());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_EQ(WideToUTF16(L"c"), model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(2U, model.render_text()->GetSelectionStart());
+ EXPECT_EQ(10U, model.GetCursorPosition());
+ EXPECT_EQ(WideToUTF16(L"c\x05E9\x05BC\x05C1\x05B8\x05E0\x05B8"L"d"),
+ model.GetSelectedText());
+
+ model.ClearSelection();
+ EXPECT_EQ(string16(), model.GetSelectedText());
+ model.SelectAll();
+ EXPECT_EQ(WideToUTF16(L"abc\x05E9\x05BC\x05C1\x05B8\x05E0\x05B8"L"def"),
+ model.GetSelectedText());
+#endif
+
+ // In case of "aBc", this test shows how to select "aB" or "Bc", assume 'B' is
+ // an RTL character.
+ model.SetText(WideToUTF16(L"a\x05E9"L"b"));
+ model.MoveCursorTo(gfx::SelectionModel(0));
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"a"), model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"a"), model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"a\x05E9"L"b"), model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"b"), model.GetSelectedText());
+
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"b"), model.GetSelectedText());
+
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"a\x05E9"L"b"), model.GetSelectedText());
+
+ model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"a\x05E9"), model.GetSelectedText());
+
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ EXPECT_EQ(WideToUTF16(L"\x05E9"L"b"), model.GetSelectedText());
+
+ model.ClearSelection();
+ EXPECT_EQ(string16(), model.GetSelectedText());
+ model.SelectAll();
+ EXPECT_EQ(WideToUTF16(L"a\x05E9"L"b"), model.GetSelectedText());
+}
+
+TEST_F(TextfieldViewsModelTest, SelectionAndEdit) {
+ TextfieldViewsModel model(NULL);
+ model.Append(ASCIIToUTF16("HELLO"));
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true); // select "EL"
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_STR_EQ("HLO", model.GetText());
+
+ model.Append(ASCIIToUTF16("ILL"));
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true); // select "LO"
+ EXPECT_TRUE(model.Delete());
+ EXPECT_STR_EQ("HILL", model.GetText());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true); // select "I"
+ model.InsertChar('E');
+ EXPECT_STR_EQ("HELL", model.GetText());
+ model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true); // select "H"
+ model.ReplaceChar('B');
+ EXPECT_STR_EQ("BELL", model.GetText());
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true); // select "ELL"
+ model.ReplaceChar('E');
+ EXPECT_STR_EQ("BEE", model.GetText());
+}
+
+TEST_F(TextfieldViewsModelTest, Password) {
+ TextfieldViewsModel model(NULL);
+ model.set_is_password(true);
+ model.Append(ASCIIToUTF16("HELLO"));
+ EXPECT_STR_EQ("*****", model.GetVisibleText());
+ EXPECT_STR_EQ("HELLO", model.GetText());
+ EXPECT_TRUE(model.Delete());
+
+ EXPECT_STR_EQ("****", model.GetVisibleText());
+ EXPECT_STR_EQ("ELLO", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition());
+
+ model.SelectAll();
+ EXPECT_STR_EQ("ELLO", model.GetSelectedText());
+ EXPECT_EQ(4U, model.GetCursorPosition());
+
+ model.InsertChar('X');
+ EXPECT_STR_EQ("*", model.GetVisibleText());
+ EXPECT_STR_EQ("X", model.GetText());
+}
+
+TEST_F(TextfieldViewsModelTest, Word) {
+ TextfieldViewsModel model(NULL);
+ model.Append(
+ ASCIIToUTF16("The answer to Life, the Universe, and Everything"));
+ model.MoveCursorRight(gfx::WORD_BREAK, false);
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ model.MoveCursorRight(gfx::WORD_BREAK, false);
+ EXPECT_EQ(10U, model.GetCursorPosition());
+ model.MoveCursorRight(gfx::WORD_BREAK, false);
+ model.MoveCursorRight(gfx::WORD_BREAK, false);
+ EXPECT_EQ(18U, model.GetCursorPosition());
+
+ // Should passes the non word char ','
+ model.MoveCursorRight(gfx::WORD_BREAK, true);
+ EXPECT_EQ(23U, model.GetCursorPosition());
+ EXPECT_STR_EQ(", the", model.GetSelectedText());
+
+ // Move to the end.
+ model.MoveCursorRight(gfx::WORD_BREAK, true);
+ model.MoveCursorRight(gfx::WORD_BREAK, true);
+ model.MoveCursorRight(gfx::WORD_BREAK, true);
+ EXPECT_STR_EQ(", the Universe, and Everything", model.GetSelectedText());
+ // Should be safe to go next word at the end.
+ model.MoveCursorRight(gfx::WORD_BREAK, true);
+ EXPECT_STR_EQ(", the Universe, and Everything", model.GetSelectedText());
+ model.InsertChar('2');
+ EXPECT_EQ(19U, model.GetCursorPosition());
+
+ // Now backwards.
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, false); // leave 2.
+ model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ EXPECT_EQ(14U, model.GetCursorPosition());
+ EXPECT_STR_EQ("Life", model.GetSelectedText());
+ model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ EXPECT_STR_EQ("to Life", model.GetSelectedText());
+ model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ model.MoveCursorLeft(gfx::WORD_BREAK, true); // Select to the begining.
+ EXPECT_STR_EQ("The answer to Life", model.GetSelectedText());
+ // Should be safe to go pervious word at the begining.
+ model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ EXPECT_STR_EQ("The answer to Life", model.GetSelectedText());
+ model.ReplaceChar('4');
+ EXPECT_EQ(string16(), model.GetSelectedText());
+ EXPECT_STR_EQ("42", model.GetVisibleText());
+}
+
+TEST_F(TextfieldViewsModelTest, SetText) {
+ TextfieldViewsModel model(NULL);
+ model.Append(ASCIIToUTF16("HELLO"));
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.SetText(ASCIIToUTF16("GOODBYE"));
+ EXPECT_STR_EQ("GOODBYE", model.GetText());
+ // SetText won't reset the cursor posistion.
+ EXPECT_EQ(5U, model.GetCursorPosition());
+ model.SelectAll();
+ EXPECT_STR_EQ("GOODBYE", model.GetSelectedText());
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ EXPECT_EQ(7U, model.GetCursorPosition());
+
+ model.SetText(ASCIIToUTF16("BYE"));
+ // Setting shorter string moves the cursor to the end of the new string.
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_EQ(string16(), model.GetSelectedText());
+ model.SetText(ASCIIToUTF16(""));
+ EXPECT_EQ(0U, model.GetCursorPosition());
+}
+
+#if defined(USE_AURA) && defined(OS_LINUX)
+#define MAYBE_Clipboard DISABLED_Clipboard // http://crbug.com/97845
+#else
+#define MAYBE_Clipboard Clipboard
+#endif
+TEST_F(TextfieldViewsModelTest, MAYBE_Clipboard) {
+ ui::Clipboard* clipboard
+ = views::ViewsDelegate::views_delegate->GetClipboard();
+ string16 initial_clipboard_text = ASCIIToUTF16("initial text");
+ ui::ScopedClipboardWriter(clipboard).WriteText(initial_clipboard_text);
+
+ string16 clipboard_text;
+ TextfieldViewsModel model(NULL);
+ model.Append(ASCIIToUTF16("HELLO WORLD"));
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+
+ // Test for cut: Empty selection.
+ EXPECT_FALSE(model.Cut());
+ clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &clipboard_text);
+ EXPECT_STR_EQ(UTF16ToUTF8(initial_clipboard_text), clipboard_text);
+ EXPECT_STR_EQ("HELLO WORLD", model.GetText());
+ EXPECT_EQ(11U, model.GetCursorPosition());
+
+ // Test for cut: Non-empty selection.
+ model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ EXPECT_TRUE(model.Cut());
+ clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &clipboard_text);
+ EXPECT_STR_EQ("WORLD", clipboard_text);
+ EXPECT_STR_EQ("HELLO ", model.GetText());
+ EXPECT_EQ(6U, model.GetCursorPosition());
+
+ // Test for copy: Empty selection.
+ model.Copy();
+ clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &clipboard_text);
+ EXPECT_STR_EQ("WORLD", clipboard_text);
+ EXPECT_STR_EQ("HELLO ", model.GetText());
+ EXPECT_EQ(6U, model.GetCursorPosition());
+
+ // Test for copy: Non-empty selection.
+ model.Append(ASCIIToUTF16("HELLO WORLD"));
+ model.SelectAll();
+ model.Copy();
+ clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &clipboard_text);
+ EXPECT_STR_EQ("HELLO HELLO WORLD", clipboard_text);
+ EXPECT_STR_EQ("HELLO HELLO WORLD", model.GetText());
+ EXPECT_EQ(17U, model.GetCursorPosition());
+
+ // Test for paste.
+ model.ClearSelection();
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ EXPECT_TRUE(model.Paste());
+ clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &clipboard_text);
+ EXPECT_STR_EQ("HELLO HELLO WORLD", clipboard_text);
+ EXPECT_STR_EQ("HELLO HELLO HELLO HELLO WORLD", model.GetText());
+ EXPECT_EQ(29U, model.GetCursorPosition());
+}
+
+void SelectWordTestVerifier(TextfieldViewsModel &model,
+ const string16 &expected_selected_string, size_t expected_cursor_pos) {
+ EXPECT_EQ(expected_selected_string, model.GetSelectedText());
+ EXPECT_EQ(expected_cursor_pos, model.GetCursorPosition());
+}
+
+TEST_F(TextfieldViewsModelTest, SelectWordTest) {
+ TextfieldViewsModel model(NULL);
+ model.Append(ASCIIToUTF16(" HELLO !! WO RLD "));
+
+ // Test when cursor is at the beginning.
+ model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ model.SelectWord();
+ SelectWordTestVerifier(model, ASCIIToUTF16(" "), 2U);
+
+ // Test when cursor is at the beginning of a word.
+ gfx::SelectionModel selection(2U);
+ model.MoveCursorTo(selection);
+ model.SelectWord();
+ SelectWordTestVerifier(model, ASCIIToUTF16("HELLO"), 7U);
+
+ // Test when cursor is at the end of a word.
+ selection = gfx::SelectionModel(15U);
+ model.MoveCursorTo(selection);
+ model.SelectWord();
+ SelectWordTestVerifier(model, ASCIIToUTF16(" "), 20U);
+
+ // Test when cursor is somewhere in a non-alpha-numeric fragment.
+ for (size_t cursor_pos = 8; cursor_pos < 13U; cursor_pos++) {
+ selection = gfx::SelectionModel(cursor_pos);
+ model.MoveCursorTo(selection);
+ model.SelectWord();
+ SelectWordTestVerifier(model, ASCIIToUTF16(" !! "), 13U);
+ }
+
+ // Test when cursor is somewhere in a whitespace fragment.
+ selection = gfx::SelectionModel(17U);
+ model.MoveCursorTo(selection);
+ model.SelectWord();
+ SelectWordTestVerifier(model, ASCIIToUTF16(" "), 20U);
+
+ // Test when cursor is at the end.
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.SelectWord();
+ SelectWordTestVerifier(model, ASCIIToUTF16(" "), 24U);
+}
+
+// TODO(xji): temporarily disable in platform Win since the complex script
+// characters and Chinese characters are turned into empty square due to font
+// regression.
+#if defined(OS_LINUX)
+TEST_F(TextfieldViewsModelTest, SelectWordTest_MixScripts) {
+ TextfieldViewsModel model(NULL);
+ std::vector<WordAndCursor> word_and_cursor;
+ word_and_cursor.push_back(WordAndCursor(L"a\x05d0", 2));
+ word_and_cursor.push_back(WordAndCursor(L"a\x05d0", 2));
+ word_and_cursor.push_back(WordAndCursor(L"\x05d1\x05d2", 5));
+ word_and_cursor.push_back(WordAndCursor(L"\x05d1\x05d2", 5));
+ word_and_cursor.push_back(WordAndCursor(L" ", 3));
+ word_and_cursor.push_back(WordAndCursor(L"a\x05d0", 2));
+ word_and_cursor.push_back(WordAndCursor(L"\x0915\x094d\x0915", 9));
+ word_and_cursor.push_back(WordAndCursor(L"\x0915\x094d\x0915", 9));
+ word_and_cursor.push_back(WordAndCursor(L" ", 10));
+ word_and_cursor.push_back(WordAndCursor(L"\x4E2D\x56FD", 12));
+ word_and_cursor.push_back(WordAndCursor(L"\x4E2D\x56FD", 12));
+ word_and_cursor.push_back(WordAndCursor(L"\x82B1", 13));
+ word_and_cursor.push_back(WordAndCursor(L"\x5929", 14));
+
+ // The text consists of Ascii, Hebrew, Hindi with Virama sign, and Chinese.
+ model.SetText(WideToUTF16(L"a\x05d0 \x05d1\x05d2 \x0915\x094d\x0915 "
+ L"\x4E2D\x56FD\x82B1\x5929"));
+ for (size_t i = 0; i < word_and_cursor.size(); ++i) {
+ model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ for (size_t j = 0; j < i; ++j)
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ model.SelectWord();
+ SelectWordTestVerifier(model, WideToUTF16(word_and_cursor[i].word),
+ word_and_cursor[i].cursor);
+ }
+}
+#endif
+
+TEST_F(TextfieldViewsModelTest, RangeTest) {
+ TextfieldViewsModel model(NULL);
+ model.Append(ASCIIToUTF16("HELLO WORLD"));
+ model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ ui::Range range;
+ model.GetSelectedRange(&range);
+ EXPECT_TRUE(range.is_empty());
+ EXPECT_EQ(0U, range.start());
+ EXPECT_EQ(0U, range.end());
+
+ model.MoveCursorRight(gfx::WORD_BREAK, true);
+ model.GetSelectedRange(&range);
+ EXPECT_FALSE(range.is_empty());
+ EXPECT_FALSE(range.is_reversed());
+ EXPECT_EQ(0U, range.start());
+ EXPECT_EQ(5U, range.end());
+
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ model.GetSelectedRange(&range);
+ EXPECT_FALSE(range.is_empty());
+ EXPECT_EQ(0U, range.start());
+ EXPECT_EQ(4U, range.end());
+
+ model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ model.GetSelectedRange(&range);
+ EXPECT_TRUE(range.is_empty());
+ EXPECT_EQ(0U, range.start());
+ EXPECT_EQ(0U, range.end());
+
+ // now from the end.
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.GetSelectedRange(&range);
+ EXPECT_TRUE(range.is_empty());
+ EXPECT_EQ(11U, range.start());
+ EXPECT_EQ(11U, range.end());
+
+ model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ model.GetSelectedRange(&range);
+ EXPECT_FALSE(range.is_empty());
+ EXPECT_TRUE(range.is_reversed());
+ EXPECT_EQ(11U, range.start());
+ EXPECT_EQ(6U, range.end());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ model.GetSelectedRange(&range);
+ EXPECT_FALSE(range.is_empty());
+ EXPECT_TRUE(range.is_reversed());
+ EXPECT_EQ(11U, range.start());
+ EXPECT_EQ(7U, range.end());
+
+ model.MoveCursorRight(gfx::WORD_BREAK, true);
+ model.GetSelectedRange(&range);
+ EXPECT_TRUE(range.is_empty());
+ EXPECT_EQ(11U, range.start());
+ EXPECT_EQ(11U, range.end());
+
+ // Select All
+ model.MoveCursorLeft(gfx::LINE_BREAK, true);
+ model.GetSelectedRange(&range);
+ EXPECT_FALSE(range.is_empty());
+ EXPECT_TRUE(range.is_reversed());
+ EXPECT_EQ(11U, range.start());
+ EXPECT_EQ(0U, range.end());
+}
+
+TEST_F(TextfieldViewsModelTest, SelectRangeTest) {
+ TextfieldViewsModel model(NULL);
+ model.Append(ASCIIToUTF16("HELLO WORLD"));
+ ui::Range range(0, 6);
+ EXPECT_FALSE(range.is_reversed());
+ model.SelectRange(range);
+ EXPECT_STR_EQ("HELLO ", model.GetSelectedText());
+
+ range = ui::Range(6, 1);
+ EXPECT_TRUE(range.is_reversed());
+ model.SelectRange(range);
+ EXPECT_STR_EQ("ELLO ", model.GetSelectedText());
+
+ range = ui::Range(2, 1000);
+ EXPECT_FALSE(range.is_reversed());
+ model.SelectRange(range);
+ EXPECT_STR_EQ("LLO WORLD", model.GetSelectedText());
+
+ range = ui::Range(1000, 3);
+ EXPECT_TRUE(range.is_reversed());
+ model.SelectRange(range);
+ EXPECT_STR_EQ("LO WORLD", model.GetSelectedText());
+
+ range = ui::Range(0, 0);
+ EXPECT_TRUE(range.is_empty());
+ model.SelectRange(range);
+ EXPECT_TRUE(model.GetSelectedText().empty());
+
+ range = ui::Range(3, 3);
+ EXPECT_TRUE(range.is_empty());
+ model.SelectRange(range);
+ EXPECT_TRUE(model.GetSelectedText().empty());
+
+ range = ui::Range(1000, 100);
+ EXPECT_FALSE(range.is_empty());
+ model.SelectRange(range);
+ EXPECT_TRUE(model.GetSelectedText().empty());
+
+ range = ui::Range(1000, 1000);
+ EXPECT_TRUE(range.is_empty());
+ model.SelectRange(range);
+ EXPECT_TRUE(model.GetSelectedText().empty());
+}
+
+TEST_F(TextfieldViewsModelTest, SelectionModelTest) {
+ TextfieldViewsModel model(NULL);
+ model.Append(ASCIIToUTF16("HELLO WORLD"));
+ model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ gfx::SelectionModel sel;
+ model.GetSelectionModel(&sel);
+ EXPECT_EQ(sel.selection_start(), sel.selection_end());
+ EXPECT_EQ(0U, sel.selection_start());
+ EXPECT_EQ(0U, sel.selection_end());
+
+ model.MoveCursorRight(gfx::WORD_BREAK, true);
+ model.GetSelectionModel(&sel);
+ EXPECT_NE(sel.selection_start(), sel.selection_end());
+ EXPECT_LE(sel.selection_start(), sel.selection_end());
+ EXPECT_EQ(0U, sel.selection_start());
+ EXPECT_EQ(5U, sel.selection_end());
+
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ model.GetSelectionModel(&sel);
+ EXPECT_NE(sel.selection_start(), sel.selection_end());
+ EXPECT_EQ(0U, sel.selection_start());
+ EXPECT_EQ(4U, sel.selection_end());
+
+ model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ model.GetSelectionModel(&sel);
+ EXPECT_EQ(sel.selection_start(), sel.selection_end());
+ EXPECT_EQ(0U, sel.selection_start());
+ EXPECT_EQ(0U, sel.selection_end());
+
+ // now from the end.
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.GetSelectionModel(&sel);
+ EXPECT_EQ(sel.selection_start(), sel.selection_end());
+ EXPECT_EQ(11U, sel.selection_start());
+ EXPECT_EQ(11U, sel.selection_end());
+
+ model.MoveCursorLeft(gfx::WORD_BREAK, true);
+ model.GetSelectionModel(&sel);
+ EXPECT_NE(sel.selection_start(), sel.selection_end());
+ EXPECT_GT(sel.selection_start(), sel.selection_end());
+ EXPECT_EQ(11U, sel.selection_start());
+ EXPECT_EQ(6U, sel.selection_end());
+
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, true);
+ model.GetSelectionModel(&sel);
+ EXPECT_NE(sel.selection_start(), sel.selection_end());
+ EXPECT_GT(sel.selection_start(), sel.selection_end());
+ EXPECT_EQ(11U, sel.selection_start());
+ EXPECT_EQ(7U, sel.selection_end());
+
+ model.MoveCursorRight(gfx::WORD_BREAK, true);
+ model.GetSelectionModel(&sel);
+ EXPECT_EQ(sel.selection_start(), sel.selection_end());
+ EXPECT_EQ(11U, sel.selection_start());
+ EXPECT_EQ(11U, sel.selection_end());
+
+ // Select All
+ model.MoveCursorLeft(gfx::LINE_BREAK, true);
+ model.GetSelectionModel(&sel);
+ EXPECT_NE(sel.selection_start(), sel.selection_end());
+ EXPECT_GT(sel.selection_start(), sel.selection_end());
+ EXPECT_EQ(11U, sel.selection_start());
+ EXPECT_EQ(0U, sel.selection_end());
+}
+
+TEST_F(TextfieldViewsModelTest, SelectSelectionModelTest) {
+ TextfieldViewsModel model(NULL);
+ model.Append(ASCIIToUTF16("HELLO WORLD"));
+ model.SelectSelectionModel(gfx::SelectionModel(0, 6, 5,
+ gfx::SelectionModel::TRAILING));
+ EXPECT_STR_EQ("HELLO ", model.GetSelectedText());
+
+ model.SelectSelectionModel(gfx::SelectionModel(6, 1, 1,
+ gfx::SelectionModel::LEADING));
+ EXPECT_STR_EQ("ELLO ", model.GetSelectedText());
+
+ model.SelectSelectionModel(gfx::SelectionModel(2, 1000, 999,
+ gfx::SelectionModel::TRAILING));
+ EXPECT_STR_EQ("LLO WORLD", model.GetSelectedText());
+
+ model.SelectSelectionModel(gfx::SelectionModel(1000, 3, 3,
+ gfx::SelectionModel::LEADING));
+ EXPECT_STR_EQ("LO WORLD", model.GetSelectedText());
+
+ model.SelectSelectionModel(gfx::SelectionModel(0, 0, 0,
+ gfx::SelectionModel::LEADING));
+ EXPECT_TRUE(model.GetSelectedText().empty());
+
+ model.SelectSelectionModel(gfx::SelectionModel(3, 3, 3,
+ gfx::SelectionModel::LEADING));
+ EXPECT_TRUE(model.GetSelectedText().empty());
+
+ model.SelectSelectionModel(gfx::SelectionModel(1000, 100, 100,
+ gfx::SelectionModel::LEADING));
+ EXPECT_TRUE(model.GetSelectedText().empty());
+
+ model.SelectSelectionModel(gfx::SelectionModel(1000, 1000, 1000,
+ gfx::SelectionModel::TRAILING));
+ 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.GetCursorPosition());
+
+ 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());
+ EXPECT_STR_EQ("1234567890", model.GetText());
+
+ model.GetCompositionTextRange(&range);
+ EXPECT_EQ(5U, range.start());
+ EXPECT_EQ(8U, range.end());
+ // composition text
+ EXPECT_STR_EQ("456", model.GetTextFromRange(ui::Range(3, 6)));
+
+ gfx::SelectionModel selection;
+ model.GetSelectionModel(&selection);
+ EXPECT_EQ(7U, selection.selection_start());
+ EXPECT_EQ(8U, selection.selection_end());
+ EXPECT_STR_EQ("8", model.GetSelectedText());
+
+ EXPECT_FALSE(composition_text_confirmed_or_cleared_);
+ model.CancelCompositionText();
+ 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.GetCursorPosition());
+
+ model.SetCompositionText(composition);
+ EXPECT_STR_EQ("1234567890", model.GetText());
+ EXPECT_TRUE(model.SetText(ASCIIToUTF16("1234567890")));
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+
+ model.SetCompositionText(composition);
+ EXPECT_STR_EQ("1234567890678", model.GetText());
+
+ model.InsertText(UTF8ToUTF16("-"));
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("1234567890-", model.GetText());
+ EXPECT_FALSE(model.HasCompositionText());
+ EXPECT_FALSE(model.HasSelection());
+
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, true);
+ EXPECT_STR_EQ("-", model.GetSelectedText());
+ model.SetCompositionText(composition);
+ EXPECT_STR_EQ("1234567890678", model.GetText());
+
+ model.ReplaceText(UTF8ToUTF16("-"));
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("1234567890-", model.GetText());
+ 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.GetText());
+
+ model.SetCompositionText(composition);
+ model.Delete();
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("1234567890-678-", model.GetText());
+
+ model.SetCompositionText(composition);
+ model.Backspace();
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("1234567890-678-", model.GetText());
+
+ model.SetText(string16());
+ model.SetCompositionText(composition);
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, false);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("678", model.GetText());
+ EXPECT_EQ(2U, model.GetCursorPosition());
+
+ model.SetCompositionText(composition);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("676788", model.GetText());
+ EXPECT_EQ(6U, model.GetCursorPosition());
+
+ model.SetCompositionText(composition);
+ model.MoveCursorLeft(gfx::WORD_BREAK, false);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("676788678", model.GetText());
+
+ model.SetText(string16());
+ model.SetCompositionText(composition);
+ model.MoveCursorRight(gfx::WORD_BREAK, false);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+
+ model.SetCompositionText(composition);
+ model.MoveCursorLeft(gfx::LINE_BREAK, true);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("678678", model.GetText());
+
+ model.SetCompositionText(composition);
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("678", model.GetText());
+
+ model.SetCompositionText(composition);
+ gfx::SelectionModel sel(model.render_text()->GetSelectionStart(),
+ 0, 0, gfx::SelectionModel::LEADING);
+ model.MoveCursorTo(sel);
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("678678", model.GetText());
+
+ 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.GetText());
+
+ model.SetCompositionText(composition);
+ model.SelectAll();
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("678", model.GetText());
+
+ model.SetCompositionText(composition);
+ model.SelectWord();
+ EXPECT_TRUE(composition_text_confirmed_or_cleared_);
+ composition_text_confirmed_or_cleared_ = false;
+ EXPECT_STR_EQ("678", model.GetText());
+
+ 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_);
+}
+
+TEST_F(TextfieldViewsModelTest, UndoRedo_BasicTest) {
+ TextfieldViewsModel model(NULL);
+ model.InsertChar('a');
+ EXPECT_FALSE(model.Redo()); // nothing to redo
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("a", model.GetText());
+
+ // Continuous inserts are treated as one edit.
+ model.InsertChar('b');
+ model.InsertChar('c');
+ EXPECT_STR_EQ("abc", model.GetText());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("a", model.GetText());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition());
+
+ // Undoing further shouldn't change the text.
+ EXPECT_FALSE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_FALSE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition());
+
+ // Redoing to the latest text.
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("a", model.GetText());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("abc", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+
+ // Backspace ===============================
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_STR_EQ("ab", model.GetText());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("abc", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ab", model.GetText());
+ EXPECT_EQ(2U, model.GetCursorPosition());
+ // Continous backspaces are treated as one edit.
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_STR_EQ("", model.GetText());
+ // Extra backspace shouldn't affect the history.
+ EXPECT_FALSE(model.Backspace());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ab", model.GetText());
+ EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("abc", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("a", model.GetText());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+
+ // Clear history
+ model.ClearEditHistory();
+ EXPECT_FALSE(model.Undo());
+ EXPECT_FALSE(model.Redo());
+ EXPECT_STR_EQ("a", model.GetText());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+
+ // Delete ===============================
+ model.SetText(ASCIIToUTF16("ABCDE"));
+ model.ClearEditHistory();
+ gfx::SelectionModel sel(2);
+ model.MoveCursorTo(sel);
+ EXPECT_TRUE(model.Delete());
+ EXPECT_STR_EQ("ABDE", model.GetText());
+ model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ EXPECT_TRUE(model.Delete());
+ EXPECT_STR_EQ("BDE", model.GetText());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ABDE", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ABDE", model.GetText());
+ EXPECT_EQ(2U, model.GetCursorPosition());
+ // Continous deletes are treated as one edit.
+ EXPECT_TRUE(model.Delete());
+ EXPECT_TRUE(model.Delete());
+ EXPECT_STR_EQ("AB", model.GetText());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ABDE", model.GetText());
+ EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("AB", model.GetText());
+ EXPECT_EQ(2U, model.GetCursorPosition());
+}
+
+TEST_F(TextfieldViewsModelTest, UndoRedo_SetText) {
+ // This is to test the undo/redo behavior of omnibox.
+ TextfieldViewsModel model(NULL);
+ model.InsertChar('w');
+ EXPECT_STR_EQ("w", model.GetText());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ model.SetText(ASCIIToUTF16("www.google.com"));
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_STR_EQ("www.google.com", model.GetText());
+ model.SelectRange(ui::Range(14, 1));
+ model.InsertChar('w');
+ EXPECT_STR_EQ("ww", model.GetText());
+ model.SetText(ASCIIToUTF16("www.google.com"));
+ model.SelectRange(ui::Range(14, 2));
+ model.InsertChar('w');
+ EXPECT_STR_EQ("www", model.GetText());
+ model.SetText(ASCIIToUTF16("www.google.com"));
+ model.SelectRange(ui::Range(14, 3));
+ model.InsertChar('.');
+ EXPECT_STR_EQ("www.", model.GetText());
+ model.SetText(ASCIIToUTF16("www.google.com"));
+ model.SelectRange(ui::Range(14, 4));
+ model.InsertChar('y');
+ EXPECT_STR_EQ("www.y", model.GetText());
+ model.SetText(ASCIIToUTF16("www.youtube.com"));
+ EXPECT_STR_EQ("www.youtube.com", model.GetText());
+ EXPECT_EQ(5U, model.GetCursorPosition());
+
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("www.google.com", model.GetText());
+ EXPECT_EQ(4U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("www.google.com", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("www.google.com", model.GetText());
+ EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("www.google.com", model.GetText());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_FALSE(model.Undo());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("www.google.com", model.GetText());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("www.google.com", model.GetText());
+ EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("www.google.com", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("www.google.com", model.GetText());
+ EXPECT_EQ(4U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("www.youtube.com", model.GetText());
+ EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_FALSE(model.Redo());
+}
+
+#if defined(USE_AURA) && defined(OS_LINUX)
+// This can be re-enabled when aura on linux has clipboard support.
+// http://crbug.com/97845
+#define MAYBE_UndoRedo_CutCopyPasteTest DISABLED_UndoRedo_CutCopyPasteTest
+#else
+#define MAYBE_UndoRedo_CutCopyPasteTest UndoRedo_CutCopyPasteTest
+#endif
+TEST_F(TextfieldViewsModelTest, MAYBE_UndoRedo_CutCopyPasteTest) {
+ TextfieldViewsModel model(NULL);
+ model.SetText(ASCIIToUTF16("ABCDE"));
+ EXPECT_FALSE(model.Redo()); // nothing to redo
+ // Cut
+ model.SelectRange(ui::Range(1, 3));
+ model.Cut();
+ EXPECT_STR_EQ("ADE", model.GetText());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_FALSE(model.Undo()); // no more undo
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ADE", model.GetText());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_FALSE(model.Redo()); // no more redo
+ EXPECT_STR_EQ("ADE", model.GetText());
+
+ model.Paste();
+ model.Paste();
+ model.Paste();
+ EXPECT_STR_EQ("ABCBCBCDE", model.GetText());
+ EXPECT_EQ(7U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ABCBCDE", model.GetText());
+ EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ADE", model.GetText());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_FALSE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ABCDE", model.GetText()); // Redoing SetText
+ EXPECT_EQ(0U, model.GetCursorPosition());
+
+ // Redo
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ADE", model.GetText());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ABCBCDE", model.GetText());
+ EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ABCBCBCDE", model.GetText());
+ EXPECT_EQ(7U, model.GetCursorPosition());
+ EXPECT_FALSE(model.Redo());
+
+ // with SelectRange
+ model.SelectRange(ui::Range(1, 3));
+ EXPECT_TRUE(model.Cut());
+ EXPECT_STR_EQ("ABCBCDE", model.GetText());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ model.SelectRange(ui::Range(1, 1));
+ EXPECT_FALSE(model.Cut());
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ EXPECT_TRUE(model.Paste());
+ EXPECT_STR_EQ("ABCBCDEBC", model.GetText());
+ EXPECT_EQ(9U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ABCBCDE", model.GetText());
+ EXPECT_EQ(7U, model.GetCursorPosition());
+ // empty cut shouldn't create an edit.
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ABCBCBCDE", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+
+ // Copy
+ ResetModel(&model);
+ model.SetText(ASCIIToUTF16("12345"));
+ EXPECT_STR_EQ("12345", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition());
+ model.SelectRange(ui::Range(1, 3));
+ model.Copy(); // Copy "23"
+ EXPECT_STR_EQ("12345", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ model.Paste(); // Paste "23" into "23".
+ EXPECT_STR_EQ("12345", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ model.Paste();
+ EXPECT_STR_EQ("1232345", model.GetText());
+ EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("12345", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ // TODO(oshima): We need to change the return type from bool to enum.
+ EXPECT_FALSE(model.Undo()); // No text change.
+ EXPECT_STR_EQ("12345", model.GetText());
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_FALSE(model.Undo());
+ // Redo
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("12345", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("12345", model.GetText()); // For 1st paste
+ EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("1232345", model.GetText());
+ EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_FALSE(model.Redo());
+ EXPECT_STR_EQ("1232345", model.GetText());
+
+ // Test using SelectRange
+ model.SelectRange(ui::Range(1, 3));
+ model.Copy();
+ EXPECT_STR_EQ("1232345", model.GetText());
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ EXPECT_TRUE(model.Paste());
+ EXPECT_STR_EQ("123234523", model.GetText());
+ EXPECT_EQ(9U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("1232345", model.GetText());
+ EXPECT_EQ(7U, model.GetCursorPosition());
+}
+
+TEST_F(TextfieldViewsModelTest, UndoRedo_CursorTest) {
+ TextfieldViewsModel model(NULL);
+ model.InsertChar('a');
+ model.MoveCursorLeft(gfx::CHARACTER_BREAK, false);
+ model.MoveCursorRight(gfx::CHARACTER_BREAK, false);
+ model.InsertChar('b');
+ // Moving the cursor shouldn't create a new edit.
+ EXPECT_STR_EQ("ab", model.GetText());
+ EXPECT_FALSE(model.Redo());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_FALSE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ab", model.GetText());
+ EXPECT_EQ(2U, model.GetCursorPosition());
+ EXPECT_FALSE(model.Redo());
+}
+
+void RunInsertReplaceTest(TextfieldViewsModel& model) {
+ gfx::SelectionModel sel;
+ model.GetSelectionModel(&sel);
+ bool reverse = (sel.selection_start() > sel.selection_end());
+
+ model.InsertChar('1');
+ model.InsertChar('2');
+ model.InsertChar('3');
+ EXPECT_STR_EQ("a123d", model.GetText());
+ EXPECT_EQ(4U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("abcd", model.GetText());
+ EXPECT_EQ(reverse ? 1U : 3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_FALSE(model.Undo());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("abcd", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition()); // By SetText
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("a123d", model.GetText());
+ EXPECT_EQ(4U, model.GetCursorPosition());
+ EXPECT_FALSE(model.Redo());
+}
+
+void RunOverwriteReplaceTest(TextfieldViewsModel& model) {
+ gfx::SelectionModel sel;
+ model.GetSelectionModel(&sel);
+ bool reverse = (sel.selection_start() > sel.selection_end());
+
+ model.ReplaceChar('1');
+ model.ReplaceChar('2');
+ model.ReplaceChar('3');
+ model.ReplaceChar('4');
+ EXPECT_STR_EQ("a1234", model.GetText());
+ EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("abcd", model.GetText());
+ EXPECT_EQ(reverse ? 1U : 3U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_FALSE(model.Undo());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("abcd", model.GetText());
+ EXPECT_EQ(0U, model.GetCursorPosition());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("a1234", model.GetText());
+ EXPECT_EQ(5U, model.GetCursorPosition());
+ EXPECT_FALSE(model.Redo());
+}
+
+TEST_F(TextfieldViewsModelTest, UndoRedo_ReplaceTest) {
+ // By Cursor
+ {
+ SCOPED_TRACE("forward & insert by cursor");
+ TextfieldViewsModel model(NULL);
+ model.SetText(ASCIIToUTF16("abcd"));
+ model.SelectRange(ui::Range(1, 3));
+ RunInsertReplaceTest(model);
+ }
+ {
+ SCOPED_TRACE("backward & insert by cursor");
+ TextfieldViewsModel model(NULL);
+ model.SetText(ASCIIToUTF16("abcd"));
+ model.SelectRange(ui::Range(3, 1));
+ RunInsertReplaceTest(model);
+ }
+ {
+ SCOPED_TRACE("forward & overwrite by cursor");
+ TextfieldViewsModel model(NULL);
+ model.SetText(ASCIIToUTF16("abcd"));
+ model.SelectRange(ui::Range(1, 3));
+ RunOverwriteReplaceTest(model);
+ }
+ {
+ SCOPED_TRACE("backward & overwrite by cursor");
+ TextfieldViewsModel model(NULL);
+ model.SetText(ASCIIToUTF16("abcd"));
+ model.SelectRange(ui::Range(3, 1));
+ RunOverwriteReplaceTest(model);
+ }
+ // By SelectRange API
+ {
+ SCOPED_TRACE("forward & insert by SelectRange");
+ TextfieldViewsModel model(NULL);
+ model.SetText(ASCIIToUTF16("abcd"));
+ model.SelectRange(ui::Range(1, 3));
+ RunInsertReplaceTest(model);
+ }
+ {
+ SCOPED_TRACE("backward & insert by SelectRange");
+ TextfieldViewsModel model(NULL);
+ model.SetText(ASCIIToUTF16("abcd"));
+ model.SelectRange(ui::Range(3, 1));
+ RunInsertReplaceTest(model);
+ }
+ {
+ SCOPED_TRACE("forward & overwrite by SelectRange");
+ TextfieldViewsModel model(NULL);
+ model.SetText(ASCIIToUTF16("abcd"));
+ model.SelectRange(ui::Range(1, 3));
+ RunOverwriteReplaceTest(model);
+ }
+ {
+ SCOPED_TRACE("backward & overwrite by SelectRange");
+ TextfieldViewsModel model(NULL);
+ model.SetText(ASCIIToUTF16("abcd"));
+ model.SelectRange(ui::Range(3, 1));
+ RunOverwriteReplaceTest(model);
+ }
+}
+
+TEST_F(TextfieldViewsModelTest, UndoRedo_CompositionText) {
+ TextfieldViewsModel model(NULL);
+
+ ui::CompositionText composition;
+ composition.text = ASCIIToUTF16("abc");
+ composition.underlines.push_back(ui::CompositionUnderline(0, 3, 0, false));
+ composition.selection = ui::Range(2, 3);
+
+ model.SetText(ASCIIToUTF16("ABCDE"));
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.InsertChar('x');
+ EXPECT_STR_EQ("ABCDEx", model.GetText());
+ EXPECT_TRUE(model.Undo()); // set composition should forget undone edit.
+ model.SetCompositionText(composition);
+ EXPECT_TRUE(model.HasCompositionText());
+ EXPECT_TRUE(model.HasSelection());
+ EXPECT_STR_EQ("ABCDEabc", model.GetText());
+
+ // Accepting composition
+ model.ConfirmCompositionText();
+ EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("", model.GetText());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_FALSE(model.Redo());
+
+ // Canceling composition
+ model.MoveCursorLeft(gfx::LINE_BREAK, false);
+ model.SetCompositionText(composition);
+ EXPECT_STR_EQ("abcABCDEabc", model.GetText());
+ model.CancelCompositionText();
+ EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_FALSE(model.Redo());
+ EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_FALSE(model.Redo());
+
+ // SetText with the same text as the result.
+ ResetModel(&model);
+ model.SetText(ASCIIToUTF16("ABCDE"));
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.SetCompositionText(composition);
+ EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ model.SetText(ASCIIToUTF16("ABCDEabc"));
+ EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ EXPECT_FALSE(model.Redo());
+
+ // SetText with the different text than the result should not
+ // remember composition text.
+ ResetModel(&model);
+ model.SetText(ASCIIToUTF16("ABCDE"));
+ model.MoveCursorRight(gfx::LINE_BREAK, false);
+ model.SetCompositionText(composition);
+ EXPECT_STR_EQ("ABCDEabc", model.GetText());
+ model.SetText(ASCIIToUTF16("1234"));
+ EXPECT_STR_EQ("1234", model.GetText());
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("ABCDE", model.GetText());
+ EXPECT_TRUE(model.Redo());
+ EXPECT_STR_EQ("1234", model.GetText());
+ EXPECT_FALSE(model.Redo());
+
+ // TODO(oshima): We need MockInputMethod to test the behavior with IME.
+}
+
+} // namespace views
diff --git a/ui/views/examples/textfield_example.cc b/ui/views/examples/textfield_example.cc
index d4ad38e..3a9a7d6 100644
--- a/ui/views/examples/textfield_example.cc
+++ b/ui/views/examples/textfield_example.cc
@@ -7,9 +7,9 @@
#include "base/utf_string_conversions.h"
#include "ui/base/range/range.h"
#include "ui/gfx/render_text.h"
+#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/grid_layout.h"
#include "views/controls/label.h"
-#include "views/controls/textfield/textfield.h"
#include "views/view.h"
namespace examples {
diff --git a/ui/views/examples/textfield_example.h b/ui/views/examples/textfield_example.h
index f3e1d74..36d43cd 100644
--- a/ui/views/examples/textfield_example.h
+++ b/ui/views/examples/textfield_example.h
@@ -11,9 +11,9 @@
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/string16.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
#include "ui/views/examples/example_base.h"
#include "views/controls/button/text_button.h"
-#include "views/controls/textfield/textfield_controller.h"
namespace examples {
diff --git a/ui/views/focus/focus_manager_unittest.cc b/ui/views/focus/focus_manager_unittest.cc
index af4ea62..e17d8f1 100644
--- a/ui/views/focus/focus_manager_unittest.cc
+++ b/ui/views/focus/focus_manager_unittest.cc
@@ -5,12 +5,12 @@
#include "base/utf_string_conversions.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/focus/accelerator_handler.h"
#include "ui/views/focus/focus_manager_factory.h"
#include "ui/views/focus/focus_manager_test.h"
#include "ui/views/widget/widget.h"
#include "views/controls/button/text_button.h"
-#include "views/controls/textfield/textfield.h"
#if !defined(USE_AURA)
#include "ui/views/controls/tabbed_pane/native_tabbed_pane_wrapper.h"
diff --git a/ui/views/focus/focus_traversal_unittest.cc b/ui/views/focus/focus_traversal_unittest.cc
index 5583291..02151d7 100644
--- a/ui/views/focus/focus_traversal_unittest.cc
+++ b/ui/views/focus/focus_traversal_unittest.cc
@@ -9,6 +9,7 @@
#include "ui/base/models/combobox_model.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/native/native_view_host.h"
+#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/focus/focus_manager_test.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h"
@@ -18,7 +19,6 @@
#include "views/controls/label.h"
#include "views/controls/link.h"
#include "views/controls/scroll_view.h"
-#include "views/controls/textfield/textfield.h"
#if !defined(USE_AURA)
#include "ui/views/controls/tabbed_pane/tabbed_pane.h"
diff --git a/ui/views/touchui/touch_selection_controller_impl_unittest.cc b/ui/views/touchui/touch_selection_controller_impl_unittest.cc
index 64c9ab1..23423b8 100644
--- a/ui/views/touchui/touch_selection_controller_impl_unittest.cc
+++ b/ui/views/touchui/touch_selection_controller_impl_unittest.cc
@@ -6,12 +6,12 @@
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/render_text.h"
+#include "ui/views/controls/textfield/native_textfield_views.h"
+#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/touchui/touch_selection_controller.h"
#include "ui/views/touchui/touch_selection_controller_impl.h"
#include "ui/views/widget/widget.h"
-#include "views/controls/textfield/native_textfield_views.h"
-#include "views/controls/textfield/textfield.h"
namespace views {
diff --git a/ui/views/widget/native_widget_gtk.cc b/ui/views/widget/native_widget_gtk.cc
index c744216..6192516 100644
--- a/ui/views/widget/native_widget_gtk.cc
+++ b/ui/views/widget/native_widget_gtk.cc
@@ -34,16 +34,16 @@
#include "ui/gfx/path.h"
#include "ui/gfx/screen.h"
#include "ui/views/bubble/bubble_delegate.h"
+#include "ui/views/controls/textfield/native_textfield_views.h"
#include "ui/views/focus/view_storage.h"
#include "ui/views/ime/input_method_gtk.h"
-#include "views/controls/textfield/native_textfield_views.h"
-#include "views/views_delegate.h"
#include "ui/views/widget/drop_target_gtk.h"
#include "ui/views/widget/gtk_views_fixed.h"
#include "ui/views/widget/gtk_views_window.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/tooltip_manager_gtk.h"
#include "ui/views/widget/widget_delegate.h"
+#include "views/views_delegate.h"
#if defined(HAVE_IBUS)
#include "ui/views/ime/input_method_ibus.h"
diff --git a/ui/views/widget/native_widget_win.cc b/ui/views/widget/native_widget_win.cc
index d49b5fe..8c07a86 100644
--- a/ui/views/widget/native_widget_win.cc
+++ b/ui/views/widget/native_widget_win.cc
@@ -33,6 +33,7 @@
#include "ui/gfx/path.h"
#include "ui/gfx/screen.h"
#include "ui/views/accessibility/native_view_accessibility_win.h"
+#include "ui/views/controls/textfield/native_textfield_views.h"
#include "ui/views/focus/accelerator_handler.h"
#include "ui/views/focus/view_storage.h"
#include "ui/views/ime/input_method_win.h"
@@ -45,7 +46,6 @@
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/native_frame_view.h"
#include "views/controls/native_control_win.h"
-#include "views/controls/textfield/native_textfield_views.h"
#include "views/views_delegate.h"
#pragma comment(lib, "dwmapi.lib")