| Index: views/controls/textfield/native_textfield_views.cc
|
| diff --git a/views/controls/textfield/native_textfield_views.cc b/views/controls/textfield/native_textfield_views.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b7c01f81c9abe4f86348799546cde8ee7ec7941e
|
| --- /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
|
|
|