summaryrefslogtreecommitdiffstats
path: root/views/controls/textfield/native_textfield_views.cc
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/controls/textfield/native_textfield_views.cc
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/controls/textfield/native_textfield_views.cc')
-rw-r--r--views/controls/textfield/native_textfield_views.cc698
1 files changed, 698 insertions, 0 deletions
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