summaryrefslogtreecommitdiffstats
path: root/views
diff options
context:
space:
mode:
authoroshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-12-17 07:48:55 +0000
committeroshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-12-17 07:48:55 +0000
commitd98e7ceee03e22fbf3f93a9b4488128843ae59a1 (patch)
treeb07941d88f64a8a56783c55ba59bb18c6806a864 /views
parent0c81ba665bab0e37ff9b82fdfd71d136044d5c96 (diff)
downloadchromium_src-d98e7ceee03e22fbf3f93a9b4488128843ae59a1.zip
chromium_src-d98e7ceee03e22fbf3f93a9b4488128843ae59a1.tar.gz
chromium_src-d98e7ceee03e22fbf3f93a9b4488128843ae59a1.tar.bz2
no native implementation of Textfield.
This is based on the original CL http://codereview.chromium.org/3142008. The key difference is * This uses Textfield framework and NativeTextfieldView implements NativeTextfieldWrapper. This allows us to swap the implementation without recompling the tree and can start testing on bots. * Changed the name of the model to TextfieldViewModel as TextfieldModel may be confusing as other Textfield implementations are not using it. I also changed to use string16 instead of gap buffer as it's enough for single line text. We can update the model to use GapBuffer when necessary. * Changed to use string16 as that's what chrome codebase should use. * Added a switch to turn on TextfieldView. I also filled a couple of features such as: * selection by key * mouse actions (move cursor, selection) * used WordIterator, which is i18n compatible, to move cursor by word * blinking cursor This is only for linux based build due to KeyStroke difference. I'm going to move some of test utlity function in chrome/browser/automation/ui_controls to app/test and will add more test once the migration is done. BUG=none TEST=new unit tests are added : NativeTestfieldViewTest and TextfieldViewModelTest. Review URL: http://codereview.chromium.org/5857002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@69523 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views')
-rw-r--r--views/controls/textfield/native_textfield_gtk.cc15
-rw-r--r--views/controls/textfield/native_textfield_views.cc698
-rw-r--r--views/controls/textfield/native_textfield_views.h172
-rw-r--r--views/controls/textfield/native_textfield_views_unittest.cc226
-rw-r--r--views/controls/textfield/textfield.cc9
-rw-r--r--views/controls/textfield/textfield.h14
-rw-r--r--views/controls/textfield/textfield_views_model.cc247
-rw-r--r--views/controls/textfield/textfield_views_model.h158
-rw-r--r--views/controls/textfield/textfield_views_model_unittest.cc285
-rw-r--r--views/event.h9
-rw-r--r--views/event_gtk.cc8
-rw-r--r--views/event_x.cc3
-rw-r--r--views/views.gyp14
-rw-r--r--views/widget/root_view.cc11
14 files changed, 1838 insertions, 31 deletions
diff --git a/views/controls/textfield/native_textfield_gtk.cc b/views/controls/textfield/native_textfield_gtk.cc
index e1c000c..2878b3c 100644
--- a/views/controls/textfield/native_textfield_gtk.cc
+++ b/views/controls/textfield/native_textfield_gtk.cc
@@ -356,7 +356,8 @@ gboolean NativeTextfieldGtk::OnKeyPressEventHandler(
gboolean NativeTextfieldGtk::OnKeyPressEvent(GdkEventKey* event) {
Textfield::Controller* controller = textfield_->GetController();
if (controller) {
- Textfield::Keystroke ks(event);
+ KeyEvent key_event(event);
+ Textfield::Keystroke ks(&key_event);
return controller->HandleKeystroke(textfield_, ks);
}
return false;
@@ -379,7 +380,8 @@ gboolean NativeTextfieldGtk::OnActivate() {
Textfield::Controller* controller = textfield_->GetController();
if (controller) {
- Textfield::Keystroke ks(key_event);
+ KeyEvent views_key_event(key_event);
+ Textfield::Keystroke ks(&views_key_event);
handled = controller->HandleKeystroke(textfield_, ks);
}
@@ -444,13 +446,4 @@ void NativeTextfieldGtk::NativeControlCreated(GtkWidget* widget) {
g_signal_connect(widget, "activate", G_CALLBACK(OnActivateHandler), this);
}
-////////////////////////////////////////////////////////////////////////////////
-// NativeTextfieldWrapper, public:
-
-// static
-NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper(
- Textfield* field) {
- return new NativeTextfieldGtk(field);
-}
-
} // namespace views
diff --git a/views/controls/textfield/native_textfield_views.cc b/views/controls/textfield/native_textfield_views.cc
new file mode 100644
index 0000000..b7c01f8
--- /dev/null
+++ b/views/controls/textfield/native_textfield_views.cc
@@ -0,0 +1,698 @@
+// Copyright (c) 2010 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 "views/controls/textfield/native_textfield_views.h"
+
+#include <algorithm>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/utf_string_conversions.h"
+#include "gfx/canvas.h"
+#include "gfx/canvas_skia.h"
+#include "gfx/insets.h"
+#include "views/background.h"
+#include "views/border.h"
+#include "views/controls/textfield/native_textfield_gtk.h"
+#include "views/controls/textfield/textfield.h"
+#include "views/controls/textfield/textfield_views_model.h"
+#include "views/event.h"
+
+namespace {
+
+// A global flag to switch the Textfield wrapper to TextfieldViews.
+bool textfield_view_enabled = false;
+
+// Color setttings for text, border, backgrounds and cursor.
+// These are tentative, and should be derived from theme, system
+// settings and current settings.
+const SkColor kSelectedTextColor = SK_ColorWHITE;
+const SkColor kReadonlyTextColor = SK_ColorDKGRAY;
+const SkColor kFocusedSelectionColor = SK_ColorBLUE;
+const SkColor kUnfocusedSelectionColor = SK_ColorLTGRAY;
+const SkColor kFocusedBorderColor = SK_ColorCYAN;
+const SkColor kDefaultBorderColor = SK_ColorGRAY;
+const SkColor kCursorColor = SK_ColorBLACK;
+
+// Parameters to control cursor blinking.
+const int kCursorVisibleTimeMs = 800;
+const int kCursorInvisibleTimeMs = 500;
+
+// A switch to enable NativeTextfieldViews;
+const char kEnableViewsBasedTextfieldSwitch[] = "enable-textfield-view";
+} // namespace
+
+namespace views {
+
+const char NativeTextfieldViews::kViewClassName[] =
+ "views/NativeTextfieldViews";
+
+NativeTextfieldViews::NativeTextfieldViews(Textfield* parent)
+ : textfield_(parent),
+ model_(new TextfieldViewsModel()),
+ text_border_(new TextfieldBorder()),
+ text_offset_(0),
+ insert_(true),
+ is_cursor_visible_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(cursor_timer_(this)) {
+ SetFocusable(true);
+ set_border(text_border_);
+
+ // Multiline is not supported.
+ DCHECK_NE(parent->style(), Textfield::STYLE_MULTILINE);
+ // Lowercase is not supported.
+ DCHECK_NE(parent->style(), Textfield::STYLE_LOWERCASE);
+}
+
+NativeTextfieldViews::~NativeTextfieldViews() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldViews, View overrides:
+
+bool NativeTextfieldViews::OnMousePressed(const views::MouseEvent& e) {
+ RequestFocus();
+ size_t pos = FindCursorPosition(e.location());
+ if (model_->MoveCursorTo(pos, false)) {
+ UpdateCursorBoundsAndTextOffset();
+ SchedulePaint();
+ }
+ return true;
+}
+
+bool NativeTextfieldViews::OnMouseDragged(const views::MouseEvent& e) {
+ size_t pos = FindCursorPosition(e.location());
+ if (model_->MoveCursorTo(pos, true)) {
+ UpdateCursorBoundsAndTextOffset();
+ SchedulePaint();
+ }
+ return true;
+}
+
+void NativeTextfieldViews::OnMouseReleased(const views::MouseEvent& e,
+ bool canceled) {
+}
+
+bool NativeTextfieldViews::OnKeyPressed(const views::KeyEvent& e) {
+ Textfield::Controller* controller = textfield_->GetController();
+ bool handled = false;
+ if (controller) {
+ Textfield::Keystroke ks(&e);
+ handled = controller->HandleKeystroke(textfield_, ks);
+ }
+ return handled || HandleKeyEvent(e);
+}
+
+bool NativeTextfieldViews::OnKeyReleased(const views::KeyEvent& e) {
+ return true;
+}
+
+void NativeTextfieldViews::Paint(gfx::Canvas* canvas) {
+ text_border_->set_has_focus(HasFocus());
+ PaintBackground(canvas);
+ PaintTextAndCursor(canvas);
+ if (textfield_->draw_border())
+ PaintBorder(canvas);
+}
+
+void NativeTextfieldViews::WillGainFocus() {
+}
+
+void NativeTextfieldViews::DidGainFocus() {
+ is_cursor_visible_ = true;
+ SchedulePaint();
+ // Start blinking cursor.
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ cursor_timer_.NewRunnableMethod(&NativeTextfieldViews::UpdateCursor),
+ kCursorVisibleTimeMs);
+}
+
+void NativeTextfieldViews::WillLoseFocus() {
+ // Stop blinking cursor.
+ cursor_timer_.RevokeAll();
+ if (is_cursor_visible_) {
+ is_cursor_visible_ = false;
+ RepaintCursor();
+ }
+}
+
+void NativeTextfieldViews::DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current) {
+ UpdateCursorBoundsAndTextOffset();
+}
+
+/////////////////////////////////////////////////////////////////
+// NativeTextfieldViews, NativeTextifieldWrapper overrides:
+
+string16 NativeTextfieldViews::GetText() const {
+ return model_->text();
+}
+
+void NativeTextfieldViews::UpdateText() {
+ bool changed = model_->SetText(textfield_->text());
+ UpdateCursorBoundsAndTextOffset();
+ SchedulePaint();
+ if (changed) {
+ Textfield::Controller* controller = textfield_->GetController();
+ if (controller)
+ controller->ContentsChanged(textfield_, GetText());
+ }
+}
+
+void NativeTextfieldViews::AppendText(const string16& text) {
+ if (text.empty())
+ return;
+ model_->Append(text);
+ UpdateCursorBoundsAndTextOffset();
+ SchedulePaint();
+
+ Textfield::Controller* controller = textfield_->GetController();
+ if (controller)
+ controller->ContentsChanged(textfield_, GetText());
+}
+
+string16 NativeTextfieldViews::GetSelectedText() const {
+ return model_->GetSelectedText();
+}
+
+void NativeTextfieldViews::SelectAll() {
+ model_->SelectAll();
+ SchedulePaint();
+}
+
+void NativeTextfieldViews::ClearSelection() {
+ model_->ClearSelection();
+ SchedulePaint();
+}
+
+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() {
+ SchedulePaint();
+}
+
+void NativeTextfieldViews::UpdateFont() {
+ UpdateCursorBoundsAndTextOffset();
+}
+
+void NativeTextfieldViews::UpdateIsPassword() {
+ model_->set_is_password(textfield_->IsPassword());
+ UpdateCursorBoundsAndTextOffset();
+ SchedulePaint();
+}
+
+void NativeTextfieldViews::UpdateEnabled() {
+ SchedulePaint();
+}
+
+bool NativeTextfieldViews::IsPassword() {
+ // looks unnecessary. should we remove?
+ NOTREACHED();
+ return false;
+}
+
+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);
+ UpdateCursorBoundsAndTextOffset();
+}
+
+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());
+ UpdateCursorBoundsAndTextOffset();
+}
+
+void NativeTextfieldViews::SetFocus() {
+ RequestFocus();
+}
+
+View* NativeTextfieldViews::GetView() {
+ return this;
+}
+
+gfx::NativeView NativeTextfieldViews::GetTestingHandle() const {
+ NOTREACHED();
+ return NULL;
+}
+
+bool NativeTextfieldViews::IsIMEComposing() const {
+ return false;
+}
+
+// static
+bool NativeTextfieldViews::IsTextfieldViewsEnabled() {
+#if defined(TOUCH_UI)
+ return true;
+#else
+ return textfield_view_enabled ||
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ kEnableViewsBasedTextfieldSwitch);
+#endif
+}
+
+// static
+void NativeTextfieldViews::SetEnableTextfieldViews(bool enabled) {
+ textfield_view_enabled = enabled;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldViews private:
+
+const gfx::Font& NativeTextfieldViews::GetFont() const {
+ return textfield_->font();
+}
+
+SkColor NativeTextfieldViews::GetTextColor() const {
+ return textfield_->text_color();
+}
+
+void NativeTextfieldViews::UpdateCursor() {
+ is_cursor_visible_ = !is_cursor_visible_;
+ RepaintCursor();
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ cursor_timer_.NewRunnableMethod(&NativeTextfieldViews::UpdateCursor),
+ is_cursor_visible_ ? kCursorVisibleTimeMs : kCursorInvisibleTimeMs);
+}
+
+void NativeTextfieldViews::RepaintCursor() {
+ gfx::Rect r = cursor_bounds_;
+ r.Inset(-1, -1, -1, -1);
+ SchedulePaint(r, false);
+}
+
+void NativeTextfieldViews::UpdateCursorBoundsAndTextOffset() {
+ if (bounds().IsEmpty())
+ return;
+
+ gfx::Insets insets = GetInsets();
+
+ int width = bounds().width() - insets.width();
+
+ // TODO(oshima): bidi
+ const gfx::Font& font = GetFont();
+ int full_width = font.GetStringWidth(UTF16ToWide(model_->GetVisibleText()));
+ cursor_bounds_ = model_->GetCursorBounds(font);
+ cursor_bounds_.set_y(cursor_bounds_.y() + insets.top());
+
+ int x_right = text_offset_ + cursor_bounds_.right();
+ int x_left = text_offset_ + cursor_bounds_.x();
+
+ if (full_width < width) {
+ // Show all text whenever the text fits to the size.
+ text_offset_ = 0;
+ } else if (x_right > width) {
+ // when the cursor overflows to the right
+ text_offset_ = width - cursor_bounds_.right();
+ } else if (x_left < 0) {
+ // when the cursor overflows to the left
+ text_offset_ = -cursor_bounds_.x();
+ } else if(full_width > width && text_offset_ + full_width < width) {
+ // when the cursor moves within the textfield with the text
+ // longer than the field.
+ text_offset_ = width - full_width;
+ } else {
+ // move cursor freely.
+ }
+ // shift cursor bounds to fit insets.
+ cursor_bounds_.set_x(cursor_bounds_.x() + text_offset_ + insets.left());
+}
+
+void NativeTextfieldViews::PaintTextAndCursor(gfx::Canvas* canvas) {
+ gfx::Insets insets = GetInsets();
+
+ canvas->Save();
+ canvas->ClipRectInt(insets.left(), insets.top(),
+ width() - insets.width(), height() - insets.height());
+
+ // TODO(oshima): bidi support
+ // TODO(varunjain): re-implement this so only that dirty text is painted.
+ TextfieldViewsModel::TextFragments fragments;
+ model_->GetFragments(&fragments);
+ int x_offset = text_offset_ + insets.left();
+ int y = insets.top();
+ int text_height = height() - insets.height();
+ SkColor selection_color =
+ HasFocus() ? kFocusedSelectionColor : kUnfocusedSelectionColor;
+ SkColor text_color =
+ textfield_->read_only() ? kReadonlyTextColor : GetTextColor();
+
+ for (TextfieldViewsModel::TextFragments::const_iterator iter =
+ fragments.begin();
+ iter != fragments.end();
+ iter++) {
+ string16 text = model_->GetVisibleText((*iter).begin, (*iter).end);
+ // TODO(oshima): This does not give the accurate position due to
+ // kerning. Figure out how webkit does this with skia.
+ int width = GetFont().GetStringWidth(UTF16ToWide(text));
+
+ if ((*iter).selected) {
+ canvas->FillRectInt(selection_color, x_offset, y, width, text_height);
+ canvas->DrawStringInt(
+ UTF16ToWide(text), GetFont(), kSelectedTextColor,
+ x_offset, y, width, text_height);
+ } else {
+ canvas->DrawStringInt(
+ UTF16ToWide(text), GetFont(), text_color,
+ x_offset, y, width, text_height);
+ }
+ x_offset += width;
+ }
+ canvas->Restore();
+
+ if (textfield_->IsEnabled() && is_cursor_visible_ &&
+ !model_->HasSelection()) {
+ // Paint Cursor. Replace cursor is drawn as rectangle for now.
+ canvas->DrawRectInt(kCursorColor,
+ cursor_bounds_.x(),
+ cursor_bounds_.y(),
+ insert_ ? 0 : cursor_bounds_.width(),
+ cursor_bounds_.height());
+ }
+}
+
+bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) {
+ // TODO(oshima): handle IME.
+ if (key_event.GetType() == views::Event::ET_KEY_PRESSED) {
+ app::KeyboardCode key_code = key_event.GetKeyCode();
+ // TODO(oshima): shift-tab does not work. Figure out why and fix.
+ if (key_code == app::VKEY_TAB)
+ return false;
+ bool selection = key_event.IsShiftDown();
+ bool control = key_event.IsControlDown();
+ bool text_changed = false;
+ bool cursor_changed = false;
+ switch (key_code) {
+ case app::VKEY_A:
+ if (control) {
+ model_->SelectAll();
+ cursor_changed = true;
+ }
+ break;
+ case app::VKEY_RIGHT:
+ control ? model_->MoveCursorToNextWord(selection)
+ : model_->MoveCursorRight(selection);
+ cursor_changed = true;
+ break;
+ case app::VKEY_LEFT:
+ control ? model_->MoveCursorToPreviousWord(selection)
+ : model_->MoveCursorLeft(selection);
+ cursor_changed = true;
+ break;
+ case app::VKEY_END:
+ model_->MoveCursorToEnd(selection);
+ cursor_changed = true;
+ break;
+ case app::VKEY_HOME:
+ model_->MoveCursorToStart(selection);
+ cursor_changed = true;
+ break;
+ case app::VKEY_BACK:
+ text_changed = model_->Backspace();
+ cursor_changed = true;
+ break;
+ case app::VKEY_DELETE:
+ text_changed = model_->Delete();
+ break;
+ case app::VKEY_INSERT:
+ insert_ = !insert_;
+ cursor_changed = true;
+ break;
+ default:
+ break;
+ }
+ char16 print_char = GetPrintableChar(key_event);
+ if (!control && print_char) {
+ if (insert_)
+ model_->Insert(print_char);
+ else
+ model_->Replace(print_char);
+ text_changed = true;
+ }
+ if (text_changed) {
+ textfield_->SyncText();
+ Textfield::Controller* controller = textfield_->GetController();
+ if (controller)
+ controller->ContentsChanged(textfield_, GetText());
+ }
+ if (text_changed || cursor_changed) {
+ UpdateCursorBoundsAndTextOffset();
+ SchedulePaint();
+ }
+ }
+ return false;
+}
+
+char16 NativeTextfieldViews::GetPrintableChar(const KeyEvent& key_event) {
+ // TODO(oshima): IME, i18n support.
+ // This only works for UCS-2 characters.
+ app::KeyboardCode key_code = key_event.GetKeyCode();
+ bool shift = key_event.IsShiftDown() ^ key_event.IsCapsLockDown();
+ // TODO(oshima): We should have a utility function
+ // under app to convert a KeyboardCode to a printable character,
+ // probably in keyboard_code_conversion{.h, _x
+ switch (key_code) {
+ case app::VKEY_NUMPAD0:
+ return '0';
+ case app::VKEY_NUMPAD1:
+ return '1';
+ case app::VKEY_NUMPAD2:
+ return '2';
+ case app::VKEY_NUMPAD3:
+ return '3';
+ case app::VKEY_NUMPAD4:
+ return '4';
+ case app::VKEY_NUMPAD5:
+ return '5';
+ case app::VKEY_NUMPAD6:
+ return '6';
+ case app::VKEY_NUMPAD7:
+ return '7';
+ case app::VKEY_NUMPAD8:
+ return '8';
+ case app::VKEY_NUMPAD9:
+ return '9';
+ case app::VKEY_MULTIPLY:
+ return '*';
+ case app::VKEY_ADD:
+ return '+';
+ case app::VKEY_SUBTRACT:
+ return '-';
+ case app::VKEY_DECIMAL:
+ return '.';
+ case app::VKEY_DIVIDE:
+ return '/';
+ case app::VKEY_SPACE:
+ return ' ';
+ case app::VKEY_0:
+ return shift ? ')' : '0';
+ case app::VKEY_1:
+ return shift ? '!' : '1';
+ case app::VKEY_2:
+ return shift ? '@' : '2';
+ case app::VKEY_3:
+ return shift ? '#' : '3';
+ case app::VKEY_4:
+ return shift ? '$' : '4';
+ case app::VKEY_5:
+ return shift ? '%' : '5';
+ case app::VKEY_6:
+ return shift ? '^' : '6';
+ case app::VKEY_7:
+ return shift ? '&' : '7';
+ case app::VKEY_8:
+ return shift ? '*' : '8';
+ case app::VKEY_9:
+ return shift ? '(' : '9';
+
+ case app::VKEY_A:
+ case app::VKEY_B:
+ case app::VKEY_C:
+ case app::VKEY_D:
+ case app::VKEY_E:
+ case app::VKEY_F:
+ case app::VKEY_G:
+ case app::VKEY_H:
+ case app::VKEY_I:
+ case app::VKEY_J:
+ case app::VKEY_K:
+ case app::VKEY_L:
+ case app::VKEY_M:
+ case app::VKEY_N:
+ case app::VKEY_O:
+ case app::VKEY_P:
+ case app::VKEY_Q:
+ case app::VKEY_R:
+ case app::VKEY_S:
+ case app::VKEY_T:
+ case app::VKEY_U:
+ case app::VKEY_V:
+ case app::VKEY_W:
+ case app::VKEY_X:
+ case app::VKEY_Y:
+ case app::VKEY_Z:
+ return (shift ? 'A' : 'a') + (key_code - app::VKEY_A);
+ case app::VKEY_OEM_1:
+ return shift ? ':' : ';';
+ case app::VKEY_OEM_PLUS:
+ return shift ? '+' : '=';
+ case app::VKEY_OEM_COMMA:
+ return shift ? '<' : ',';
+ case app::VKEY_OEM_MINUS:
+ return shift ? '_' : '-';
+ case app::VKEY_OEM_PERIOD:
+ return shift ? '>' : '.';
+ case app::VKEY_OEM_2:
+ return shift ? '?' : '/';
+ case app::VKEY_OEM_3:
+ return shift ? '~' : '`';
+ case app::VKEY_OEM_4:
+ return shift ? '}' : ']';
+ case app::VKEY_OEM_5:
+ return shift ? '|' : '\\';
+ case app::VKEY_OEM_6:
+ return shift ? '{' : '[';
+ case app::VKEY_OEM_7:
+ return shift ? '"' : '\'';
+ default:
+ return 0;
+ }
+}
+
+size_t NativeTextfieldViews::FindCursorPosition(const gfx::Point& point) const {
+ // TODO(oshima): BIDI/i18n support.
+ gfx::Font font = GetFont();
+ gfx::Insets insets = GetInsets();
+ std::wstring text = UTF16ToWide(model_->GetVisibleText());
+ int left = 0;
+ int left_pos = 0;
+ int right = font.GetStringWidth(text);
+ int right_pos = text.length();
+
+ int x = point.x() - insets.left() - text_offset_;
+ if (x <= left) return left_pos;
+ if (x >= right) return right_pos;
+ // binary searching the cursor position.
+ // TODO(oshima): use the center of character instead of edge.
+ // Binary search may not work for language like arabic.
+ while (std::abs(static_cast<long>(right_pos - left_pos) > 1)) {
+ int pivot_pos = left_pos + (right_pos - left_pos) / 2;
+ int pivot = font.GetStringWidth(text.substr(0, pivot_pos));
+ if (pivot < x) {
+ left = pivot;
+ left_pos = pivot_pos;
+ } else if (pivot == x) {
+ return pivot_pos;
+ } else {
+ right = pivot;
+ right_pos = pivot_pos;
+ }
+ }
+ return left_pos;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// NativeTextfieldWrapper:
+
+// static
+NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper(
+ Textfield* field) {
+ if (NativeTextfieldViews::IsTextfieldViewsEnabled()) {
+ return new NativeTextfieldViews(field);
+ } else {
+ return new NativeTextfieldGtk(field);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// TextifieldBorder
+//
+///////////////////////////////////////////////////////////////////////////////
+
+NativeTextfieldViews::TextfieldBorder::TextfieldBorder()
+ : has_focus_(false),
+ insets_(4, 4, 4, 4) {
+}
+
+void NativeTextfieldViews::TextfieldBorder::Paint(
+ const View& view, gfx::Canvas* canvas) const {
+ SkRect rect;
+ rect.set(SkIntToScalar(0), SkIntToScalar(0),
+ SkIntToScalar(view.width()), SkIntToScalar(view.height()));
+ SkScalar corners[8] = {
+ // top-left
+ insets_.left(),
+ insets_.top(),
+ // top-right
+ insets_.right(),
+ insets_.top(),
+ // bottom-right
+ insets_.right(),
+ insets_.bottom(),
+ // bottom-left
+ insets_.left(),
+ insets_.bottom(),
+ };
+ SkPath path;
+ path.addRoundRect(rect, corners);
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setFlags(SkPaint::kAntiAlias_Flag);
+ // TODO(oshima): Copy what WebKit does for focused border.
+ paint.setColor(has_focus_ ? kFocusedBorderColor : kDefaultBorderColor);
+ paint.setStrokeWidth(has_focus_ ? 2 : 1);
+
+ canvas->AsCanvasSkia()->drawPath(path, paint);
+}
+
+void NativeTextfieldViews::TextfieldBorder::GetInsets(gfx::Insets* insets) const
+{
+ *insets = insets_;
+}
+
+void NativeTextfieldViews::TextfieldBorder::SetInsets(int top,
+ int left,
+ int bottom,
+ int right) {
+ insets_.Set(top, left, bottom, right);
+}
+
+} // namespace views
diff --git a/views/controls/textfield/native_textfield_views.h b/views/controls/textfield/native_textfield_views.h
new file mode 100644
index 0000000..cfb47be
--- /dev/null
+++ b/views/controls/textfield/native_textfield_views.h
@@ -0,0 +1,172 @@
+// Copyright (c) 2010 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 VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_VIEWS_H_
+#define VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_VIEWS_H_
+#pragma once
+
+#include "base/string16.h"
+#include "base/task.h"
+#include "gfx/font.h"
+#include "views/border.h"
+#include "views/controls/textfield/native_textfield_wrapper.h"
+#include "views/view.h"
+
+namespace gfx {
+class Canvas;
+} // namespace
+
+namespace views {
+
+class KeyEvent;
+class TextfieldViewsModel;
+
+// A views/skia only implementation of NativeTextfieldWrapper.
+// No platform specific code is used.
+// Following features are not yet supported.
+// * BIDI
+// * Clipboard (Cut & Paste).
+// * Context Menu.
+// * IME/i18n support.
+// * X selection (only if we want to support).
+// * STYLE_MULTILINE, STYLE_LOWERCASE text. (These are not used in
+// chromeos, so we may not need them)
+// * Double click to select word, and triple click to select all.
+class NativeTextfieldViews : public views::View,
+ public NativeTextfieldWrapper {
+ public:
+ explicit NativeTextfieldViews(Textfield* parent);
+ ~NativeTextfieldViews();
+
+ // views::View overrides:
+ virtual bool OnMousePressed(const views::MouseEvent& e);
+ virtual bool OnMouseDragged(const views::MouseEvent& e);
+ virtual void OnMouseReleased(const views::MouseEvent& e, bool canceled);
+ virtual bool OnKeyPressed(const views::KeyEvent& e);
+ virtual bool OnKeyReleased(const views::KeyEvent& e);
+ virtual void Paint(gfx::Canvas* canvas);
+ virtual void WillGainFocus();
+ virtual void DidGainFocus();
+ virtual void WillLoseFocus();
+ virtual void DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current);
+
+ // NativeTextfieldWrapper overrides:
+ virtual string16 GetText() const;
+ virtual void UpdateText();
+ virtual void AppendText(const string16& text);
+ virtual string16 GetSelectedText() const;
+ virtual void SelectAll();
+ virtual void ClearSelection();
+ virtual void UpdateBorder();
+ virtual void UpdateTextColor();
+ virtual void UpdateBackgroundColor();
+ virtual void UpdateReadOnly();
+ virtual void UpdateFont();
+ virtual void UpdateIsPassword();
+ virtual void UpdateEnabled();
+ virtual bool IsPassword();
+ virtual gfx::Insets CalculateInsets();
+ virtual void UpdateHorizontalMargins();
+ virtual void UpdateVerticalMargins();
+ virtual void SetFocus();
+ virtual View* GetView();
+ virtual gfx::NativeView GetTestingHandle() const;
+ virtual bool IsIMEComposing() const;
+
+ // class name of internal
+ static const char kViewClassName[];
+
+ // Returns true when
+ // 1) built with GYP_DEFIENS="touchui=1"
+ // 2) enabled by SetEnableTextfieldViews(true)
+ // 3) enabled by the command line flag "--enable-textfield-view".
+ static bool IsTextfieldViewsEnabled();
+ // Enable/Disable TextfieldViews implementation for Textfield.
+ static void SetEnableTextfieldViews(bool enabled);
+
+ private:
+ friend class NativeTextfieldViewsTest;
+
+ // A Border class to draw focus border for the text field.
+ class TextfieldBorder : public Border {
+ public:
+ TextfieldBorder();
+
+ // Border implementation.
+ virtual void Paint(const View& view, gfx::Canvas* canvas) const;
+ virtual void GetInsets(gfx::Insets* insets) const;
+
+ // Sets the insets of the border.
+ void SetInsets(int top, int left, int bottom, int right);
+
+ // Sets the focus state.
+ void set_has_focus(bool has_focus) {
+ has_focus_ = has_focus;
+ }
+
+ private:
+ bool has_focus_;
+ gfx::Insets insets_;
+
+ DISALLOW_COPY_AND_ASSIGN(TextfieldBorder);
+ };
+
+ // Returns the Textfield's font.
+ const gfx::Font& GetFont() const;
+
+ // Returns the Textfield's text color.
+ SkColor GetTextColor() 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();
+
+ void PaintTextAndCursor(gfx::Canvas* canvas);
+
+ // Handle the keyevent.
+ bool HandleKeyEvent(const KeyEvent& key_event);
+
+ // Utility function. Gets the character corresponding to a keyevent.
+ // Returns 0 if the character is not printable.
+ char16 GetPrintableChar(const KeyEvent& key_event);
+
+ // Find a cusor position for given |point| in this views coordinates.
+ size_t FindCursorPosition(const gfx::Point& point) const;
+
+ // 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_.
+ TextfieldBorder* text_border_;
+
+ // The x offset for the text to be drawn, without insets;
+ int text_offset_;
+
+ // Cursor's bounds in the textfield's coordinates.
+ gfx::Rect cursor_bounds_;
+
+ // True if the textfield is in insert mode.
+ bool insert_;
+
+ // The drawing state of cursor. True to draw.
+ bool is_cursor_visible_;
+
+ // A runnable method factory for callback to update the cursor.
+ ScopedRunnableMethodFactory<NativeTextfieldViews> cursor_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeTextfieldViews);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_VIEWS_H_
diff --git a/views/controls/textfield/native_textfield_views_unittest.cc b/views/controls/textfield/native_textfield_views_unittest.cc
new file mode 100644
index 0000000..ed69e54
--- /dev/null
+++ b/views/controls/textfield/native_textfield_views_unittest.cc
@@ -0,0 +1,226 @@
+// Copyright (c) 2010 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 "app/keyboard_codes.h"
+#include "base/message_loop.h"
+#include "base/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "views/controls/textfield/native_textfield_views.h"
+#include "views/controls/textfield/textfield.h"
+#include "views/controls/textfield/textfield_views_model.h"
+#include "views/event.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+#define EXPECT_STR_EQ(ascii, utf16) \
+ EXPECT_EQ(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 app/test/.
+class NativeTextfieldViewsTest : public ::testing::Test,
+ public Textfield::Controller {
+ public:
+ NativeTextfieldViewsTest()
+ : widget_(NULL),
+ textfield_(NULL),
+ textfield_view_(NULL),
+ model_(NULL),
+ message_loop_(MessageLoop::TYPE_UI) {
+ }
+
+ // ::testing::Test overrides.
+ virtual void SetUp() {
+ NativeTextfieldViews::SetEnableTextfieldViews(true);
+ }
+
+ virtual void TearDown() {
+ NativeTextfieldViews::SetEnableTextfieldViews(false);
+ if (widget_)
+ widget_->Close();
+ }
+
+ // Textfield::Controller implementation:
+ virtual void ContentsChanged(Textfield* sender,
+ const string16& new_contents){
+ last_contents_ = new_contents;
+ }
+
+ virtual bool HandleKeystroke(Textfield* sender,
+ const Textfield::Keystroke& keystroke) {
+ // TODO(oshima): figure out how to test the keystroke.
+ return false;
+ }
+
+ void InitTextfield(Textfield::StyleFlags style) {
+ ASSERT_FALSE(textfield_);
+ textfield_ = new Textfield(style);
+ textfield_->SetController(this);
+ widget_ = Widget::CreatePopupWidget(
+ Widget::NotTransparent,
+ Widget::AcceptEvents,
+ Widget::DeleteOnDestroy,
+ Widget::DontMirrorOriginInRTL);
+ widget_->Init(NULL, gfx::Rect());
+ widget_->SetContentsView(textfield_);
+ textfield_view_
+ = static_cast<NativeTextfieldViews*>(textfield_->native_wrapper());
+ DCHECK(textfield_view_);
+ model_ = textfield_view_->model_.get();
+ }
+
+ void SendKeyEventToTextfieldViews(app::KeyboardCode key_code,
+ bool shift,
+ bool control) {
+ int flags = (shift ? KeyEvent::EF_SHIFT_DOWN : 0) |
+ (control ? KeyEvent::EF_CONTROL_DOWN : 0);
+ KeyEvent event(KeyEvent::ET_KEY_PRESSED, key_code, flags, 1, 0);
+ textfield_view_->OnKeyPressed(event);
+ }
+
+ void SendKeyEventToTextfieldViews(app::KeyboardCode key_code) {
+ SendKeyEventToTextfieldViews(key_code, false, false);
+ }
+
+ protected:
+ // We need widget to populate wrapper class.
+ Widget* widget_;
+
+ Textfield* textfield_;
+ NativeTextfieldViews* textfield_view_;
+ TextfieldViewsModel* model_;
+
+ // A fake message loop for view's drawing events.
+ MessageLoop message_loop_;
+
+ // The string from Controller::ContentsChanged callback.
+ string16 last_contents_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NativeTextfieldViewsTest);
+};
+
+TEST_F(NativeTextfieldViewsTest, ModelChangesTeset) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(ASCIIToUTF16("this is"));
+
+ EXPECT_STR_EQ("this is", model_->text());
+ EXPECT_STR_EQ("this is", last_contents_);
+ last_contents_.clear();
+
+ textfield_->AppendText(ASCIIToUTF16(" a test"));
+ EXPECT_STR_EQ("this is a test", model_->text());
+ EXPECT_STR_EQ("this is a test", last_contents_);
+ last_contents_.clear();
+
+ // Cases where the callback should not be called.
+ textfield_->SetText(ASCIIToUTF16("this is a test"));
+ EXPECT_STR_EQ("this is a test", model_->text());
+ EXPECT_EQ(string16(), last_contents_);
+
+ textfield_->AppendText(string16());
+ EXPECT_STR_EQ("this is a test", model_->text());
+ EXPECT_EQ(string16(), last_contents_);
+
+ EXPECT_EQ(string16(), textfield_->GetSelectedText());
+ textfield_->SelectAll();
+ EXPECT_STR_EQ("this is a test", textfield_->GetSelectedText());
+ EXPECT_EQ(string16(), last_contents_);
+}
+
+TEST_F(NativeTextfieldViewsTest, KeyTest) {
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ SendKeyEventToTextfieldViews(app::VKEY_C, true, false);
+ EXPECT_STR_EQ("C", textfield_->text());
+ EXPECT_STR_EQ("C", last_contents_);
+ last_contents_.clear();
+
+ SendKeyEventToTextfieldViews(app::VKEY_R, false, false);
+ EXPECT_STR_EQ("Cr", textfield_->text());
+ EXPECT_STR_EQ("Cr", last_contents_);
+}
+
+TEST_F(NativeTextfieldViewsTest, ControlAndSelectTest) {
+ // Insert a test string in a textfield.
+ InitTextfield(Textfield::STYLE_DEFAULT);
+ textfield_->SetText(ASCIIToUTF16("one two three"));
+ SendKeyEventToTextfieldViews(app::VKEY_RIGHT,
+ true /* shift */, false /* control */);
+ SendKeyEventToTextfieldViews(app::VKEY_RIGHT, true, false);
+ SendKeyEventToTextfieldViews(app::VKEY_RIGHT, true, false);
+
+ EXPECT_STR_EQ("one", textfield_->GetSelectedText());
+
+ // Test word select.
+ SendKeyEventToTextfieldViews(app::VKEY_RIGHT, true, true);
+ EXPECT_STR_EQ("one two", textfield_->GetSelectedText());
+ SendKeyEventToTextfieldViews(app::VKEY_RIGHT, true, true);
+ EXPECT_STR_EQ("one two three", textfield_->GetSelectedText());
+ SendKeyEventToTextfieldViews(app::VKEY_LEFT, true, true);
+ EXPECT_STR_EQ("one two ", textfield_->GetSelectedText());
+ SendKeyEventToTextfieldViews(app::VKEY_LEFT, true, true);
+ EXPECT_STR_EQ("one ", textfield_->GetSelectedText());
+
+ // Replace the selected text.
+ SendKeyEventToTextfieldViews(app::VKEY_Z, true, false);
+ SendKeyEventToTextfieldViews(app::VKEY_E, true, false);
+ SendKeyEventToTextfieldViews(app::VKEY_R, true, false);
+ SendKeyEventToTextfieldViews(app::VKEY_O, true, false);
+ SendKeyEventToTextfieldViews(app::VKEY_SPACE, false, false);
+ EXPECT_STR_EQ("ZERO two three", textfield_->text());
+
+ SendKeyEventToTextfieldViews(app::VKEY_END, true, false);
+ EXPECT_STR_EQ("two three", textfield_->GetSelectedText());
+ SendKeyEventToTextfieldViews(app::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];
+ app::KeyboardCode code =
+ c == ' ' ? app::VKEY_SPACE :
+ static_cast<app::KeyboardCode>(app::VKEY_A + c - 'a');
+ SendKeyEventToTextfieldViews(code);
+ }
+ EXPECT_STR_EQ(test_str, textfield_->text());
+
+ // Move the cursor around.
+ for (int i = 0; i < 6; i++) {
+ SendKeyEventToTextfieldViews(app::VKEY_LEFT);
+ }
+ SendKeyEventToTextfieldViews(app::VKEY_RIGHT);
+
+ // Delete using backspace and check resulting string.
+ SendKeyEventToTextfieldViews(app::VKEY_BACK);
+ EXPECT_STR_EQ("this is test", textfield_->text());
+
+ // Delete using delete key and check resulting string.
+ for (int i = 0; i < 5; i++) {
+ SendKeyEventToTextfieldViews(app::VKEY_DELETE);
+ }
+ EXPECT_STR_EQ("this is ", textfield_->text());
+
+ // Select all and replace with "k".
+ textfield_->SelectAll();
+ SendKeyEventToTextfieldViews(app::VKEY_K);
+ EXPECT_STR_EQ("k", textfield_->text());
+}
+
+TEST_F(NativeTextfieldViewsTest, PasswordTest) {
+ InitTextfield(Textfield::STYLE_PASSWORD);
+ textfield_->SetText(ASCIIToUTF16("my password"));
+ // Just to make sure the text() and callback returns
+ // the actual text instead of "*".
+ EXPECT_STR_EQ("my password", textfield_->text());
+ EXPECT_STR_EQ("my password", last_contents_);
+}
+
+} // namespace views
diff --git a/views/controls/textfield/textfield.cc b/views/controls/textfield/textfield.cc
index 81c74d2..e92794d 100644
--- a/views/controls/textfield/textfield.cc
+++ b/views/controls/textfield/textfield.cc
@@ -358,8 +358,7 @@ app::KeyboardCode Textfield::Keystroke::GetKeyboardCode() const {
#if defined(OS_WIN)
return static_cast<app::KeyboardCode>(key_);
#else
- return static_cast<app::KeyboardCode>(
- app::WindowsKeyCodeForGdkKeyCode(event_.keyval));
+ return event_->GetKeyCode();
#endif
}
@@ -373,13 +372,11 @@ bool Textfield::Keystroke::IsShiftHeld() const {
}
#else
bool Textfield::Keystroke::IsControlHeld() const {
- return (event_.state & gtk_accelerator_get_default_mod_mask()) ==
- GDK_CONTROL_MASK;
+ return event_->IsControlDown();
}
bool Textfield::Keystroke::IsShiftHeld() const {
- return (event_.state & gtk_accelerator_get_default_mod_mask()) ==
- GDK_SHIFT_MASK;
+ return event_->IsShiftDown();
}
#endif
diff --git a/views/controls/textfield/textfield.h b/views/controls/textfield/textfield.h
index d171eb5..9dd1ee9 100644
--- a/views/controls/textfield/textfield.h
+++ b/views/controls/textfield/textfield.h
@@ -60,10 +60,11 @@ class Textfield : public View {
int repeat_count() const { return repeat_count_; }
unsigned int flags() const { return flags_; }
#else
- explicit Keystroke(GdkEventKey* event)
- : event_(*event) {
+ explicit Keystroke(const KeyEvent* event)
+ : event_(event) {
}
- const GdkEventKey* event() const { return &event_; }
+ const KeyEvent& key_event() const { return *event_;};
+ const GdkEventKey* event() const { return event_->native_event(); }
#endif
app::KeyboardCode GetKeyboardCode() const;
bool IsControlHeld() const;
@@ -76,7 +77,7 @@ class Textfield : public View {
int repeat_count_;
unsigned int flags_;
#else
- GdkEventKey event_;
+ const KeyEvent* event_;
#endif
DISALLOW_COPY_AND_ASSIGN(Keystroke);
@@ -169,7 +170,7 @@ class Textfield : public View {
void UseDefaultBackgroundColor();
// Gets/Sets the font used when rendering the text within the Textfield.
- gfx::Font font() const { return font_; }
+ 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
@@ -230,6 +231,9 @@ class Textfield : public View {
gfx::NativeView GetTestingHandle() const {
return native_wrapper_ ? native_wrapper_->GetTestingHandle() : NULL;
}
+ NativeTextfieldWrapper* native_wrapper() const {
+ return native_wrapper_;
+ }
#endif
// Overridden from View:
diff --git a/views/controls/textfield/textfield_views_model.cc b/views/controls/textfield/textfield_views_model.cc
new file mode 100644
index 0000000..21783ec
--- /dev/null
+++ b/views/controls/textfield/textfield_views_model.cc
@@ -0,0 +1,247 @@
+// Copyright (c) 2010 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 "views/controls/textfield/textfield_views_model.h"
+
+#include <algorithm>
+
+#include "base/i18n/break_iterator.h"
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "gfx/font.h"
+
+namespace views {
+
+TextfieldViewsModel::TextfieldViewsModel()
+ : cursor_pos_(0),
+ selection_begin_(0),
+ is_password_(false) {
+}
+
+TextfieldViewsModel::~TextfieldViewsModel() {
+}
+
+void TextfieldViewsModel::GetFragments(TextFragments* fragments) const {
+ DCHECK(fragments);
+ fragments->clear();
+ if (HasSelection()) {
+ int begin = std::min(selection_begin_, cursor_pos_);
+ int end = std::max(selection_begin_, cursor_pos_);
+ if (begin != 0) {
+ fragments->push_back(TextFragment(0, begin, false));
+ }
+ fragments->push_back(TextFragment(begin, end, true));
+ int len = text_.length();
+ if (end != len) {
+ fragments->push_back(TextFragment(end, len, false));
+ }
+ } else {
+ fragments->push_back(TextFragment(0, text_.length(), false));
+ }
+}
+
+bool TextfieldViewsModel::SetText(const string16& text) {
+ bool changed = text_ != text;
+ if (changed) {
+ text_ = text;
+ if (cursor_pos_ > text.length()) {
+ cursor_pos_ = text.length();
+ }
+ }
+ ClearSelection();
+ return changed;
+}
+
+void TextfieldViewsModel::Insert(char16 c) {
+ if (HasSelection())
+ DeleteSelection();
+ text_.insert(cursor_pos_, 1, c);
+ cursor_pos_++;
+ ClearSelection();
+}
+
+void TextfieldViewsModel::Replace(char16 c) {
+ if (!HasSelection())
+ Delete();
+ Insert(c);
+}
+
+void TextfieldViewsModel::Append(const string16& text) {
+ text_ += text;
+}
+
+bool TextfieldViewsModel::Delete() {
+ if (HasSelection()) {
+ DeleteSelection();
+ return true;
+ } else if (text_.length() > cursor_pos_) {
+ text_.erase(cursor_pos_, 1);
+ return true;
+ }
+ return false;
+}
+
+bool TextfieldViewsModel::Backspace() {
+ if (HasSelection()) {
+ DeleteSelection();
+ return true;
+ } else if (cursor_pos_ > 0) {
+ cursor_pos_--;
+ text_.erase(cursor_pos_, 1);
+ ClearSelection();
+ return true;
+ }
+ return false;
+}
+
+void TextfieldViewsModel::MoveCursorLeft(bool select) {
+ // TODO(oshima): support BIDI
+ if (select) {
+ if (cursor_pos_ > 0)
+ cursor_pos_--;
+ } else {
+ if (HasSelection())
+ cursor_pos_ = std::min(cursor_pos_, selection_begin_);
+ else if (cursor_pos_ > 0)
+ cursor_pos_--;
+ ClearSelection();
+ }
+}
+
+void TextfieldViewsModel::MoveCursorRight(bool select) {
+ // TODO(oshima): support BIDI
+ if (select) {
+ cursor_pos_ = std::min(text_.length(), cursor_pos_ + 1);
+ } else {
+ if (HasSelection())
+ cursor_pos_ = std::max(cursor_pos_, selection_begin_);
+ else
+ cursor_pos_ = std::min(text_.length(), cursor_pos_ + 1);
+ ClearSelection();
+ }
+}
+
+void TextfieldViewsModel::MoveCursorToPreviousWord(bool select) {
+ // Notes: We always iterate words from the begining.
+ // This is probably fast enough for our usage, but we may
+ // want to modify WordIterator so that it can start from the
+ // middle of string and advance backwards.
+ base::BreakIterator iter(&text_, base::BreakIterator::BREAK_WORD);
+ bool success = iter.Init();
+ DCHECK(success);
+ if (!success)
+ return;
+ int prev = 0;
+ while (iter.Advance()) {
+ if (iter.IsWord()) {
+ size_t begin = iter.pos() - iter.GetString().length();
+ if (begin == cursor_pos_) {
+ // The cursor is at the beginning of a word.
+ // Move to previous word.
+ cursor_pos_ = prev;
+ } else if(iter.pos() >= cursor_pos_) {
+ // The cursor is in the middle or at the end of a word.
+ // Move to the top of current word.
+ cursor_pos_ = begin;
+ } else {
+ prev = iter.pos() - iter.GetString().length();
+ continue;
+ }
+ if (!select)
+ ClearSelection();
+ break;
+ }
+ }
+}
+
+void TextfieldViewsModel::MoveCursorToNextWord(bool select) {
+ base::BreakIterator iter(&text_, base::BreakIterator::BREAK_WORD);
+ bool success = iter.Init();
+ DCHECK(success);
+ if (!success)
+ return;
+ while (iter.Advance()) {
+ if (iter.IsWord() && iter.pos() > cursor_pos_) {
+ cursor_pos_ = iter.pos();
+ if (!select)
+ ClearSelection();
+ break;
+ }
+ }
+}
+
+void TextfieldViewsModel::MoveCursorToStart(bool select) {
+ cursor_pos_ = 0;
+ if (!select)
+ ClearSelection();
+}
+
+void TextfieldViewsModel::MoveCursorToEnd(bool select) {
+ cursor_pos_ = text_.length();
+ if (!select)
+ ClearSelection();
+}
+
+bool TextfieldViewsModel::MoveCursorTo(size_t pos, bool select) {
+ bool cursor_changed = false;
+ if (cursor_pos_ != pos) {
+ cursor_pos_ = pos;
+ cursor_changed = true;
+ }
+ if (!select)
+ ClearSelection();
+ return cursor_changed;
+}
+
+gfx::Rect TextfieldViewsModel::GetCursorBounds(const gfx::Font& font) const {
+ string16 text = GetVisibleText();
+ int x = font.GetStringWidth(UTF16ToWide(text.substr(0U, cursor_pos_)));
+ int h = font.GetHeight();
+ DCHECK(x >= 0);
+ if (text.length() == cursor_pos_) {
+ return gfx::Rect(x, 0, 0, h);
+ } else {
+ int x_end =
+ font.GetStringWidth(UTF16ToWide(text.substr(0U, cursor_pos_ + 1U)));
+ return gfx::Rect(x, 0, x_end - x, h);
+ }
+}
+
+string16 TextfieldViewsModel::GetSelectedText() const {
+ return text_.substr(
+ std::min(cursor_pos_, selection_begin_),
+ std::abs(static_cast<long>(cursor_pos_ - selection_begin_)));
+}
+
+void TextfieldViewsModel::SelectAll() {
+ cursor_pos_ = 0;
+ selection_begin_ = text_.length();
+}
+
+void TextfieldViewsModel::ClearSelection() {
+ selection_begin_ = cursor_pos_;
+}
+
+bool TextfieldViewsModel::HasSelection() const {
+ return selection_begin_ != cursor_pos_;
+}
+
+void TextfieldViewsModel::DeleteSelection() {
+ DCHECK(HasSelection());
+ size_t n = std::abs(static_cast<long>(cursor_pos_ - selection_begin_));
+ size_t begin = std::min(cursor_pos_, selection_begin_);
+ text_.erase(begin, n);
+ cursor_pos_ = begin;
+ ClearSelection();
+}
+
+string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const {
+ DCHECK(end >= begin);
+ if (is_password_) {
+ return string16(end - begin, '*');
+ }
+ return text_.substr(begin, end - begin);
+}
+
+} // namespace views
diff --git a/views/controls/textfield/textfield_views_model.h b/views/controls/textfield/textfield_views_model.h
new file mode 100644
index 0000000..219a8c7
--- /dev/null
+++ b/views/controls/textfield/textfield_views_model.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2010 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 VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_VIEWS_MODEL_H_
+#define VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_VIEWS_MODEL_H_
+#pragma once
+
+#include <vector>
+
+#include "base/string16.h"
+#include "gfx/rect.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace gfx {
+class Font;
+} // namespace gfx
+
+namespace views {
+
+// A model that represents a text content for TextfieldViews.
+// It supports editing, selection and cursor manipulation.
+class TextfieldViewsModel {
+ public:
+ TextfieldViewsModel();
+ virtual ~TextfieldViewsModel();
+
+ // Text fragment info. Used to draw selected text.
+ // We may replace this with TextAttribute class
+ // in the future to support multi-color text
+ // for omnibox.
+ struct TextFragment {
+ TextFragment(size_t b, size_t e, bool s)
+ : begin(b), end(e), selected(s) {
+ }
+ // The begin and end position of text fragment.
+ size_t begin, end;
+ // True if the text is selected.
+ bool selected;
+ };
+ typedef std::vector<TextFragment> TextFragments;
+
+ // Gets the text element info.
+ void GetFragments(TextFragments* elements) const;
+
+ void set_is_password(bool is_password) {
+ is_password_ = is_password;
+ }
+ const string16& text() const { return text_; }
+
+ // Edit related methods.
+
+ // Sest the text. Returns true if the text has been modified.
+ bool SetText(const string16& text);
+
+ // Inserts a character at the current cursor position.
+ void Insert(char16 c);
+
+ // Replaces the char at the current position with given character.
+ void Replace(char16 c);
+
+ // Appends the text.
+ 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.
+ 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.
+ bool Backspace();
+
+ // Cursor related methods.
+
+ // Returns the current cursor position.
+ size_t cursor_pos() const { return cursor_pos_; }
+
+ // Moves the cursor left by one position (as if, the user has pressed the left
+ // arrow key). If |select| is true, it updates the selection accordingly.
+ void MoveCursorLeft(bool select);
+
+ // Moves the cursor right by one position (as if, the user has pressed the
+ // right arrow key). If |select| is true, it updates the selection
+ // accordingly.
+ void MoveCursorRight(bool select);
+
+ // Moves the cursor left by one word (word boundry is defined by space).
+ // If |select| is true, it updates the selection accordingly.
+ void MoveCursorToPreviousWord(bool select);
+
+ // Moves the cursor right by one word (word boundry is defined by space).
+ // If |select| is true, it updates the selection accordingly.
+ void MoveCursorToNextWord(bool select);
+
+ // Moves the cursor to start of the textfield contents.
+ // If |select| is true, it updates the selection accordingly.
+ void MoveCursorToStart(bool select);
+
+ // Moves the cursor to end of the textfield contents.
+ // If |select| is true, it updates the selection accordingly.
+ void MoveCursorToEnd(bool select);
+
+ // Moves the cursor to the specified |position|.
+ // If |select| is true, it updates the selection accordingly.
+ bool MoveCursorTo(size_t position, bool select);
+
+ // Returns the bounds of character at the current cursor.
+ gfx::Rect GetCursorBounds(const gfx::Font& font) const;
+
+ // Selection related method
+
+ // Returns the selected text.
+ string16 GetSelectedText() const;
+
+ // Selects all text.
+ void SelectAll();
+
+ // Clears selection.
+ void ClearSelection();
+
+ // Returns visible text. If the field is password, it returns the
+ // sequence of "*".
+ string16 GetVisibleText() const {
+ return GetVisibleText(0U, text_.length());
+ }
+
+ private:
+ friend class NativeTextfieldViews;
+
+ // Tells if any text is selected.
+ bool HasSelection() const;
+
+ // Deletes the selected text.
+ void DeleteSelection();
+
+ // Returns the visible text given |start| and |end|.
+ string16 GetVisibleText(size_t start, size_t end) const;
+
+ // The text in utf16 format.
+ string16 text_;
+
+ // Current cursor position.
+ size_t cursor_pos_;
+
+ // Selection range.
+ size_t selection_begin_;
+
+ // True if the text is the password.
+ bool is_password_;
+
+ DISALLOW_COPY_AND_ASSIGN(TextfieldViewsModel);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_VIEWS_MODEL_H_
diff --git a/views/controls/textfield/textfield_views_model_unittest.cc b/views/controls/textfield/textfield_views_model_unittest.cc
new file mode 100644
index 0000000..fa09efa
--- /dev/null
+++ b/views/controls/textfield/textfield_views_model_unittest.cc
@@ -0,0 +1,285 @@
+// Copyright (c) 2010 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/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "views/controls/textfield/textfield_views_model.h"
+
+namespace views {
+
+#define EXPECT_STR_EQ(ascii, utf16) \
+ EXPECT_EQ(ASCIIToWide(ascii), UTF16ToWide(utf16))
+
+TEST(TextfieldViewsModelTest, EditString) {
+ TextfieldViewsModel model;
+ // append two strings
+ model.Append(ASCIIToUTF16("HILL"));
+ EXPECT_STR_EQ("HILL", model.text());
+ model.Append(ASCIIToUTF16("WORLD"));
+ EXPECT_STR_EQ("HILLWORLD", model.text());
+
+ // Insert "E" to make hello
+ model.MoveCursorRight(false);
+ model.Insert('E');
+ EXPECT_STR_EQ("HEILLWORLD", model.text());
+ // Replace "I" with "L"
+ model.Replace('L');
+ EXPECT_STR_EQ("HELLLWORLD", model.text());
+ model.Replace('L');
+ model.Replace('O');
+ EXPECT_STR_EQ("HELLOWORLD", model.text());
+
+ // Delete 6th char "W", then delete 5th char O"
+ EXPECT_EQ(5U, model.cursor_pos());
+ EXPECT_TRUE(model.Delete());
+ EXPECT_STR_EQ("HELLOORLD", model.text());
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_EQ(4U, model.cursor_pos());
+ EXPECT_STR_EQ("HELLORLD", model.text());
+
+ // Move the cursor to start. backspace should fail.
+ model.MoveCursorToStart(false);
+ EXPECT_FALSE(model.Backspace());
+ EXPECT_STR_EQ("HELLORLD", model.text());
+ // Move the cursor to the end. delete should fail.
+ model.MoveCursorToEnd(false);
+ EXPECT_FALSE(model.Delete());
+ EXPECT_STR_EQ("HELLORLD", model.text());
+ // but backspace should work.
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_STR_EQ("HELLORL", model.text());
+}
+
+TEST(TextfieldViewsModelTest, EmptyString) {
+ TextfieldViewsModel model;
+ EXPECT_EQ(string16(), model.text());
+ EXPECT_EQ(string16(), model.GetSelectedText());
+ EXPECT_EQ(string16(), model.GetVisibleText());
+
+ model.MoveCursorLeft(true);
+ EXPECT_EQ(0U, model.cursor_pos());
+ model.MoveCursorRight(true);
+ EXPECT_EQ(0U, model.cursor_pos());
+
+ EXPECT_EQ(string16(), model.GetSelectedText());
+
+ EXPECT_FALSE(model.Delete());
+ EXPECT_FALSE(model.Backspace());
+}
+
+TEST(TextfieldViewsModelTest, Selection) {
+ TextfieldViewsModel model;
+ model.Append(ASCIIToUTF16("HELLO"));
+ model.MoveCursorRight(false);
+ model.MoveCursorRight(true);
+ EXPECT_STR_EQ("E", model.GetSelectedText());
+ model.MoveCursorRight(true);
+ EXPECT_STR_EQ("EL", model.GetSelectedText());
+
+ model.MoveCursorToStart(true);
+ EXPECT_STR_EQ("H", model.GetSelectedText());
+ model.MoveCursorToEnd(true);
+ EXPECT_STR_EQ("ELLO", model.GetSelectedText());
+ model.ClearSelection();
+ EXPECT_EQ(string16(), model.GetSelectedText());
+ model.SelectAll();
+ EXPECT_STR_EQ("HELLO", model.GetSelectedText());
+
+ // Select and move cursor
+ model.MoveCursorTo(1U, false);
+ model.MoveCursorTo(3U, true);
+ EXPECT_STR_EQ("EL", model.GetSelectedText());
+ model.MoveCursorLeft(false);
+ EXPECT_EQ(1U, model.cursor_pos());
+ model.MoveCursorTo(1U, false);
+ model.MoveCursorTo(3U, true);
+ model.MoveCursorRight(false);
+ EXPECT_EQ(3U, model.cursor_pos());
+
+ // Select all and move cursor
+ model.SelectAll();
+ model.MoveCursorLeft(false);
+ EXPECT_EQ(0U, model.cursor_pos());
+ model.SelectAll();
+ model.MoveCursorRight(false);
+ EXPECT_EQ(5U, model.cursor_pos());
+}
+
+TEST(TextfieldViewsModelTest, SelectionAndEdit) {
+ TextfieldViewsModel model;
+ model.Append(ASCIIToUTF16("HELLO"));
+ model.MoveCursorRight(false);
+ model.MoveCursorRight(true);
+ model.MoveCursorRight(true); // select "EL"
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_STR_EQ("HLO", model.text());
+
+ model.Append(ASCIIToUTF16("ILL"));
+ model.MoveCursorRight(true);
+ model.MoveCursorRight(true); // select "LO"
+ EXPECT_TRUE(model.Delete());
+ EXPECT_STR_EQ("HILL", model.text());
+ EXPECT_EQ(1U, model.cursor_pos());
+ model.MoveCursorRight(true); // select "I"
+ model.Insert('E');
+ EXPECT_STR_EQ("HELL", model.text());
+ model.MoveCursorToStart(false);
+ model.MoveCursorRight(true); // select "H"
+ model.Replace('B');
+ EXPECT_STR_EQ("BELL", model.text());
+ model.MoveCursorToEnd(false);
+ model.MoveCursorLeft(true);
+ model.MoveCursorLeft(true); // select ">LL"
+ model.Replace('E');
+ EXPECT_STR_EQ("BEE", model.text());
+}
+
+TEST(TextfieldViewsModelTest, Password) {
+ TextfieldViewsModel model;
+ model.set_is_password(true);
+ model.Append(ASCIIToUTF16("HELLO"));
+ EXPECT_STR_EQ("*****", model.GetVisibleText());
+ EXPECT_STR_EQ("HELLO", model.text());
+ EXPECT_TRUE(model.Delete());
+
+ EXPECT_STR_EQ("****", model.GetVisibleText());
+ EXPECT_STR_EQ("ELLO", model.text());
+ EXPECT_EQ(0U, model.cursor_pos());
+
+ model.SelectAll();
+ EXPECT_STR_EQ("ELLO", model.GetSelectedText());
+ EXPECT_EQ(0U, model.cursor_pos());
+
+ model.Insert('X');
+ EXPECT_STR_EQ("*", model.GetVisibleText());
+ EXPECT_STR_EQ("X", model.text());
+}
+
+TEST(TextfieldViewsModelTest, Word) {
+ TextfieldViewsModel model;
+ model.Append(
+ ASCIIToUTF16("The answer to Life, the Universe, and Everything"));
+ model.MoveCursorToNextWord(false);
+ EXPECT_EQ(3U, model.cursor_pos());
+ model.MoveCursorToNextWord(false);
+ EXPECT_EQ(10U, model.cursor_pos());
+ model.MoveCursorToNextWord(false);
+ model.MoveCursorToNextWord(false);
+ EXPECT_EQ(18U, model.cursor_pos());
+
+ // Should passes the non word char ','
+ model.MoveCursorToNextWord(true);
+ EXPECT_EQ(23U, model.cursor_pos());
+ EXPECT_STR_EQ(", the", model.GetSelectedText());
+
+ // Move to the end.
+ model.MoveCursorToNextWord(true);
+ model.MoveCursorToNextWord(true);
+ model.MoveCursorToNextWord(true);
+ EXPECT_STR_EQ(", the Universe, and Everything", model.GetSelectedText());
+ // Should be safe to go next word at the end.
+ model.MoveCursorToNextWord(true);
+ EXPECT_STR_EQ(", the Universe, and Everything", model.GetSelectedText());
+ model.Insert('2');
+ EXPECT_EQ(19U, model.cursor_pos());
+
+ // Now backwards.
+ model.MoveCursorLeft(false); // leave 2.
+ model.MoveCursorToPreviousWord(true);
+ EXPECT_EQ(14U, model.cursor_pos());
+ EXPECT_STR_EQ("Life", model.GetSelectedText());
+ model.MoveCursorToPreviousWord(true);
+ EXPECT_STR_EQ("to Life", model.GetSelectedText());
+ model.MoveCursorToPreviousWord(true);
+ model.MoveCursorToPreviousWord(true);
+ model.MoveCursorToPreviousWord(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.MoveCursorToPreviousWord(true);
+ EXPECT_STR_EQ("The answer to Life", model.GetSelectedText());
+ model.Replace('4');
+ EXPECT_EQ(string16(), model.GetSelectedText());
+ EXPECT_STR_EQ("42", model.GetVisibleText());
+}
+
+TEST(TextfieldViewsModelTest, TextFragment) {
+ TextfieldViewsModel model;
+ TextfieldViewsModel::TextFragments fragments;
+ // Empty string
+ model.GetFragments(&fragments);
+ EXPECT_EQ(1U, fragments.size());
+ fragments.clear();
+ EXPECT_EQ(0U, fragments[0].begin);
+ EXPECT_EQ(0U, fragments[0].end);
+ EXPECT_FALSE(fragments[0].selected);
+
+ // Some string
+ model.Append(ASCIIToUTF16("Hello world"));
+ model.GetFragments(&fragments);
+ EXPECT_EQ(1U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].begin);
+ EXPECT_EQ(11U, fragments[0].end);
+ EXPECT_FALSE(fragments[0].selected);
+
+ // Select 1st word
+ model.MoveCursorToNextWord(true);
+ model.GetFragments(&fragments);
+ EXPECT_EQ(2U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].begin);
+ EXPECT_EQ(5U, fragments[0].end);
+ EXPECT_TRUE(fragments[0].selected);
+ EXPECT_EQ(5U, fragments[1].begin);
+ EXPECT_EQ(11U, fragments[1].end);
+ EXPECT_FALSE(fragments[1].selected);
+
+ // Select empty string
+ model.ClearSelection();
+ model.MoveCursorRight(true);
+ model.GetFragments(&fragments);
+ EXPECT_EQ(3U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].begin);
+ EXPECT_EQ(5U, fragments[0].end);
+ EXPECT_FALSE(fragments[0].selected);
+ EXPECT_EQ(5U, fragments[1].begin);
+ EXPECT_EQ(6U, fragments[1].end);
+ EXPECT_TRUE(fragments[1].selected);
+
+ EXPECT_EQ(6U, fragments[2].begin);
+ EXPECT_EQ(11U, fragments[2].end);
+ EXPECT_FALSE(fragments[2].selected);
+
+ // Select to the end.
+ model.MoveCursorToEnd(true);
+ model.GetFragments(&fragments);
+ EXPECT_EQ(2U, fragments.size());
+ EXPECT_EQ(0U, fragments[0].begin);
+ EXPECT_EQ(5U, fragments[0].end);
+ EXPECT_FALSE(fragments[0].selected);
+ EXPECT_EQ(5U, fragments[1].begin);
+ EXPECT_EQ(11U, fragments[1].end);
+ EXPECT_TRUE(fragments[1].selected);
+}
+
+TEST(TextfieldViewsModelTest, SetText) {
+ TextfieldViewsModel model;
+ model.Append(ASCIIToUTF16("HELLO"));
+ model.MoveCursorToEnd(false);
+ model.SetText(ASCIIToUTF16("GOODBYE"));
+ EXPECT_STR_EQ("GOODBYE", model.text());
+ EXPECT_EQ(5U, model.cursor_pos());
+ model.SelectAll();
+ EXPECT_STR_EQ("GOODBYE", model.GetSelectedText());
+ // Selection move the current pos to the begining.
+ EXPECT_EQ(0U, model.cursor_pos());
+ model.MoveCursorToEnd(false);
+ EXPECT_EQ(7U, model.cursor_pos());
+
+ model.SetText(ASCIIToUTF16("BYE"));
+ EXPECT_EQ(3U, model.cursor_pos());
+ EXPECT_EQ(string16(), model.GetSelectedText());
+ model.SetText(ASCIIToUTF16(""));
+ EXPECT_EQ(0U, model.cursor_pos());
+}
+
+} // namespace views
diff --git a/views/event.h b/views/event.h
index 1ec666d..f878e2d 100644
--- a/views/event.h
+++ b/views/event.h
@@ -13,6 +13,7 @@
#if defined(OS_LINUX)
typedef struct _GdkEventKey GdkEventKey;
#endif
+
#if defined(TOUCH_UI)
typedef union _XEvent XEvent;
#endif
@@ -332,7 +333,9 @@ class KeyEvent : public Event {
int repeat_count,
int message_flags);
#if defined(OS_LINUX)
- explicit KeyEvent(GdkEventKey* event);
+ explicit KeyEvent(const GdkEventKey* event);
+
+ const GdkEventKey* native_event() const { return native_event_; }
#endif
#if defined(TOUCH_UI)
@@ -366,7 +369,9 @@ class KeyEvent : public Event {
app::KeyboardCode key_code_;
int repeat_count_;
int message_flags_;
-
+#if defined(OS_LINUX)
+ const GdkEventKey* native_event_;
+#endif
DISALLOW_COPY_AND_ASSIGN(KeyEvent);
};
diff --git a/views/event_gtk.cc b/views/event_gtk.cc
index cae426d..5e29ad9 100644
--- a/views/event_gtk.cc
+++ b/views/event_gtk.cc
@@ -10,14 +10,18 @@
namespace views {
-KeyEvent::KeyEvent(GdkEventKey* event)
+KeyEvent::KeyEvent(const GdkEventKey* event)
: Event(event->type == GDK_KEY_PRESS ?
Event::ET_KEY_PRESSED : Event::ET_KEY_RELEASED,
GetFlagsFromGdkState(event->state)),
// TODO(erg): All these values are iffy.
key_code_(app::WindowsKeyCodeForGdkKeyCode(event->keyval)),
repeat_count_(0),
- message_flags_(0) {
+ message_flags_(0)
+#if !defined(TOUCH_UI)
+ , native_event_(event)
+#endif
+{
}
// static
diff --git a/views/event_x.cc b/views/event_x.cc
index 774aa7f..bddeb71 100644
--- a/views/event_x.cc
+++ b/views/event_x.cc
@@ -205,7 +205,8 @@ KeyEvent::KeyEvent(XEvent* xev)
GetEventFlagsFromXState(xev->xkey.state)),
key_code_(app::KeyboardCodeFromXKeyEvent(xev)),
repeat_count_(0),
- message_flags_(0) {
+ message_flags_(0),
+ native_event_(NULL) {
}
MouseEvent::MouseEvent(XEvent* xev)
diff --git a/views/views.gyp b/views/views.gyp
index 5cb7b55..35d12a8 100644
--- a/views/views.gyp
+++ b/views/views.gyp
@@ -223,11 +223,15 @@
'controls/textfield/gtk_views_textview.h',
'controls/textfield/textfield.cc',
'controls/textfield/textfield.h',
+ 'controls/textfield/textfield_views_model.cc',
+ 'controls/textfield/textfield_views_model.h',
'controls/textfield/native_textfield_gtk.cc',
'controls/textfield/native_textfield_gtk.h',
'controls/textfield/native_textfield_win.cc',
'controls/textfield/native_textfield_win.h',
'controls/textfield/native_textfield_wrapper.h',
+ 'controls/textfield/native_textfield_views.cc',
+ 'controls/textfield/native_textfield_views.h',
'controls/throbber.cc',
'controls/throbber.h',
'controls/tree/tree_view.cc',
@@ -392,6 +396,10 @@
'controls/slider/slider.cc',
'controls/slider/slider.h',
'controls/slider/native_slider_wrapper.h',
+ 'controls/textfield/native_textfield_views.cc',
+ 'controls/textfield/native_textfield_views.h',
+ 'controls/textfield/textfield_views_model.cc',
+ 'controls/textfield/textfield_views_model.h',
],
'include_dirs': [
'<(DEPTH)/third_party/wtl/include',
@@ -424,6 +432,8 @@
'controls/progress_bar_unittest.cc',
'controls/tabbed_pane/tabbed_pane_unittest.cc',
'controls/table/table_view_unittest.cc',
+ 'controls/textfield/native_textfield_views_unittest.cc',
+ 'controls/textfield/textfield_views_model_unittest.cc',
'focus/accelerator_handler_gtk_unittest.cc',
'focus/focus_manager_unittest.cc',
'grid_layout_unittest.cc',
@@ -454,6 +464,10 @@
# unrelated things like v8, sqlite nss...).
'../chrome/app/locales/locales.gyp:en-US',
],
+ 'sources!': [
+ 'controls/textfield/native_textfield_views_unittest.cc',
+ 'controls/textfield/textfield_views_model_unittest.cc',
+ ],
'link_settings': {
'libraries': [
'-limm32.lib',
diff --git a/views/widget/root_view.cc b/views/widget/root_view.cc
index a0a752c..82f32ba 100644
--- a/views/widget/root_view.cc
+++ b/views/widget/root_view.cc
@@ -22,6 +22,7 @@
#if defined(OS_LINUX)
#include "views/widget/widget_gtk.h"
+#include "views/controls/textfield/native_textfield_views.h"
#endif // defined(OS_LINUX)
namespace views {
@@ -617,11 +618,13 @@ View* RootView::GetFocusedView() {
View* view = focus_manager->GetFocusedView();
if (view && (view->GetRootView() == this))
return view;
-#if defined(TOUCH_UI)
- // hack to deal with two root views in touch
- // should be fixed by eliminating one of them
- if (view)
+
+#if defined(OS_LINUX)
+ if (view && NativeTextfieldViews::IsTextfieldViewsEnabled()) {
+ // hack to deal with two root views.
+ // should be fixed by eliminating one of them
return view;
+ }
#endif
return NULL;
}