Chromium Code Reviews| Index: views/controls/textfield/native_textfield_view.cc |
| diff --git a/views/controls/textfield/native_textfield_view.cc b/views/controls/textfield/native_textfield_view.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..5296191cbb3cdc33419d73ad5f8497d4f4d715c3 |
| --- /dev/null |
| +++ b/views/controls/textfield/native_textfield_view.cc |
| @@ -0,0 +1,702 @@ |
| +// 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_view.h" |
| + |
| +#include <algorithm> |
| + |
| +#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_view_model.h" |
| +#include "views/event.h" |
| + |
| +namespace { |
| + |
| +// A global flag to switch the Textfield wrapper to TextfieldView. |
| +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; |
| + |
| +} // namespace |
| + |
| +namespace views { |
| + |
| +const char NativeTextfieldView::kViewClassName[] = "views/NativeTextfieldView"; |
| + |
| +NativeTextfieldView::NativeTextfieldView(Textfield* parent) |
| + : textfield_(parent), |
| + model_(new TextfieldViewModel()), |
| + text_border_(new TextfieldBorder()), |
| + text_offset_(0), |
| + insert_(true), |
| + cursor_(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); |
| +} |
| + |
| +NativeTextfieldView::~NativeTextfieldView() { |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// NativeTextfieldView, View overrides: |
| + |
| +bool NativeTextfieldView::SkipDefaultKeyEventProcessing( |
| + const views::KeyEvent& e) { |
| + return false; |
|
sky
2010/12/15 20:31:27
This is the default return value. Did you mean to
oshima
2010/12/16 01:15:19
No, I was going to remove this, but forgot to do s
|
| +} |
| + |
| +bool NativeTextfieldView::OnMousePressed(const views::MouseEvent& e) { |
| + RequestFocus(); |
| + size_t pos = FindCursorPosition(e.location()); |
| + if (model_->MoveCursorTo(pos, false)) { |
|
sky
2010/12/15 20:31:27
What about a double click?
oshima
2010/12/16 01:15:19
Not yet supported. I'll implement it in follow up
|
| + UpdateCursorBoundsAndTextOffset(); |
| + SchedulePaint(); |
| + } |
| + return true; |
| +} |
| + |
| +bool NativeTextfieldView::OnMouseDragged(const views::MouseEvent& e) { |
| + size_t pos = FindCursorPosition(e.location()); |
| + if (model_->MoveCursorTo(pos, true)) { |
| + UpdateCursorBoundsAndTextOffset(); |
| + SchedulePaint(); |
| + } |
| + return true; |
| +} |
| + |
| +void NativeTextfieldView::OnMouseReleased(const views::MouseEvent& e, |
| + bool canceled) { |
| +} |
| + |
| +bool NativeTextfieldView::OnKeyPressed(const views::KeyEvent& e) { |
| + Textfield::Controller* controller = textfield_->GetController(); |
| + bool handled = false; |
| + if (controller) { |
| + Textfield::Keystroke ks(&e); |
| + handled = controller->HandleKeystroke(textfield_, ks); |
| + } |
| + handled = handled || HandleKeyEvent(e); |
| + return handled; |
| +} |
| + |
| +bool NativeTextfieldView::OnKeyReleased(const views::KeyEvent& e) { |
| + return true; |
| +} |
| + |
| +void NativeTextfieldView::Paint(gfx::Canvas* canvas) { |
| + text_border_->set_has_focus(HasFocus()); |
| + PaintBackground(canvas); |
| + PaintTextAndCursor(canvas); |
| + if (textfield_->draw_border()) |
| + PaintBorder(canvas); |
| +} |
| + |
| +void NativeTextfieldView::WillGainFocus() { |
| +} |
| + |
| +void NativeTextfieldView::DidGainFocus() { |
| + cursor_ = true; |
| + SchedulePaint(); |
| + // Start blinking cursor. |
| + MessageLoop::current()->PostDelayedTask( |
| + FROM_HERE, |
| + cursor_timer_.NewRunnableMethod(&NativeTextfieldView::UpdateCursor), |
| + 800); |
| +} |
| + |
| +void NativeTextfieldView::WillLoseFocus() { |
| + // Stop blinking cursor. |
| + cursor_timer_.RevokeAll(); |
| + if (cursor_) { |
| + cursor_ = false; |
| + RepaintCursor(); |
| + } |
| +} |
| + |
| +void NativeTextfieldView::DidChangeBounds(const gfx::Rect& previous, |
| + const gfx::Rect& current) { |
| + UpdateCursorBoundsAndTextOffset(); |
| +} |
| + |
| + |
| +///////////////////////////////////////////////////////////////// |
| +// NativeTextfieldView, NativeTextifieldWrapper overrides: |
| + |
| +string16 NativeTextfieldView::GetText() const { |
| + return model_->text(); |
| +} |
| + |
| +void NativeTextfieldView::UpdateText() { |
| + bool changed = model_->SetText(textfield_->text()); |
| + UpdateCursorBoundsAndTextOffset(); |
| + SchedulePaint(); |
| + if (changed) { |
| + Textfield::Controller* controller = textfield_->GetController(); |
| + if (controller) |
| + controller->ContentsChanged(textfield_, GetText()); |
| + } |
| +} |
| + |
| +void NativeTextfieldView::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 NativeTextfieldView::GetSelectedText() const { |
| + return model_->GetSelectedText(); |
| +} |
| + |
| +void NativeTextfieldView::SelectAll() { |
| + model_->SelectAll(); |
| + SchedulePaint(); |
| +} |
| + |
| +void NativeTextfieldView::ClearSelection() { |
| + model_->ClearSelection(); |
| + SchedulePaint(); |
| +} |
| + |
| +void NativeTextfieldView::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 NativeTextfieldView::UpdateTextColor() { |
| + SchedulePaint(); |
| +} |
| + |
| +void NativeTextfieldView::UpdateBackgroundColor() { |
| + // TODO(oshima): Background has to match the border's shape. |
| + set_background( |
| + Background::CreateSolidBackground(textfield_->background_color())); |
| + SchedulePaint(); |
| +} |
| + |
| +void NativeTextfieldView::UpdateReadOnly() { |
| + SchedulePaint(); |
| +} |
| + |
| +void NativeTextfieldView::UpdateFont() { |
| + UpdateCursorBoundsAndTextOffset(); |
| +} |
| + |
| +void NativeTextfieldView::UpdateIsPassword() { |
| + model_->set_is_password(textfield_->IsPassword()); |
| + UpdateCursorBoundsAndTextOffset(); |
| + SchedulePaint(); |
| +} |
| + |
| +void NativeTextfieldView::UpdateEnabled() { |
| + SchedulePaint(); |
| +} |
| + |
| +bool NativeTextfieldView::IsPassword() { |
| + // looks unnecessary. should we remove? |
| + NOTREACHED(); |
| + return false; |
| +} |
| + |
| +gfx::Insets NativeTextfieldView::CalculateInsets() { |
| + return GetInsets(); |
| +} |
| + |
| +void NativeTextfieldView::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 NativeTextfieldView::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 NativeTextfieldView::SetFocus() { |
| + RequestFocus(); |
| +} |
| + |
| +View* NativeTextfieldView::GetView() { |
| + return this; |
| +} |
| + |
| +gfx::NativeView NativeTextfieldView::GetTestingHandle() const { |
| + NOTREACHED(); |
| + return NULL; |
| +} |
| + |
| +bool NativeTextfieldView::IsIMEComposing() const { |
| + return false; |
| +} |
| + |
| +// static |
| +bool NativeTextfieldView::IsTextfieldViewEnabled() { |
| + return textfield_view_enabled; |
| +} |
| + |
| +// static |
| +void NativeTextfieldView::SetEnableTextfieldView(bool enabled) { |
| + textfield_view_enabled = enabled; |
| +} |
| + |
| + |
| +/////////////////////////////////////////////////////////////////////////////// |
| +// NativeTextfieldView private: |
| + |
| +gfx::Font NativeTextfieldView::GetFont() const { |
| + return textfield_->font(); |
| +} |
| + |
| +SkColor NativeTextfieldView::GetTextColor() const { |
| + return textfield_->text_color(); |
| +} |
| + |
| + |
| +void NativeTextfieldView::UpdateCursor() { |
| + cursor_ = !cursor_; |
| + RepaintCursor(); |
| + MessageLoop::current()->PostDelayedTask( |
| + FROM_HERE, |
| + cursor_timer_.NewRunnableMethod(&NativeTextfieldView::UpdateCursor), |
| + cursor_ ? 800 : 500); |
| +} |
| + |
| +void NativeTextfieldView::RepaintCursor() { |
| + gfx::Rect r = cursor_bounds_; |
| + r.Inset(-1, -1, -1, -1); |
| + SchedulePaint(r, false); |
| +} |
| + |
| +void NativeTextfieldView::UpdateCursorBoundsAndTextOffset() { |
| + if (bounds().IsEmpty()) return; |
|
sky
2010/12/15 20:31:27
nit: don't do single ifs like this.
oshima
2010/12/16 01:15:19
Done.
|
| + |
| + gfx::Insets insets = GetInsets(); |
| + |
| + int width = bounds().width() - insets.width(); |
| + |
| + // TODO(oshima): bidi |
| + gfx::Font font = GetFont(); |
|
sky
2010/12/15 20:31:27
const gfx::Font&
oshima
2010/12/16 01:15:19
Done.
|
| + 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(); |
|
sky
2010/12/15 20:31:27
Wouldn't this trigger scrolling on every cursor ch
oshima
2010/12/16 01:15:19
This is executed only if the cursor moves out of t
|
| + } 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 NativeTextfieldView::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. |
| + TextfieldViewModel::TextFragments fragments; |
| + model_->PopulateFragments(&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 (TextfieldViewModel::TextFragments::const_iterator iter = |
| + fragments.begin(); |
| + iter != fragments.end(); |
| + iter++) { |
| + string16 text = model_->GetVisibleText((*iter).begin, (*iter).end); |
|
sky
2010/12/15 20:31:27
iter->begin, iter->end
oshima
2010/12/16 01:15:19
It does not work. * is an operator that returns Te
|
| + // 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) { |
|
sky
2010/12/15 20:31:27
iter->selected
oshima
2010/12/16 01:15:19
same here.
|
| + 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() && cursor_) { |
| + // 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 NativeTextfieldView::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 NativeTextfieldView::GetPrintableChar(const KeyEvent& key_event) { |
| + // TODO(oshima): IME, i18n support. |
| + app::KeyboardCode key_code = key_event.GetKeyCode(); |
| + bool shift = false; |
| + if ((key_code >= app::VKEY_0 && key_code <= app::VKEY_9) || |
| + (key_code >= app::VKEY_OEM_1 && key_code <= app::VKEY_OEM_8)) { |
| + shift = key_event.IsShiftDown(); |
| + } else if (key_code >= app::VKEY_A && key_code <= app::VKEY_Z) { |
| +#if defined(TOUCH_UI) |
| + shift = (key_event.IsLockDown() && !key_event.IsShiftDown()) || |
| + (!key_event.IsLockDown() && key_event.IsShiftDown()); |
| +#else |
| + shift = key_event.IsShiftDown(); |
| +#endif |
| + } |
| + if (key_event.IsCapsLockDown()) |
| + shift = !shift; |
| + return GetPrintableChar(key_code, shift); |
| +} |
| + |
| +char16 NativeTextfieldView::GetPrintableChar(app::KeyboardCode key_code, |
|
rjkroege
2010/12/15 21:23:31
Could this merge with keyboard_code_conversion_x.c
oshima
2010/12/16 01:15:19
That's xevent -> keyboard code right? what we need
|
| + bool shift) { |
| + // TODO(oshima): We should have a utility function |
| + // under app to convert a KeyboardCode to a printable character. |
| + 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 NativeTextfieldView::FindCursorPosition(const gfx::Point& point) const { |
| + // TODO(oshima): BIDI |
| + 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(); |
| + 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. |
| + while (std::abs(static_cast<long>(right_pos - left_pos) > 1)) { |
|
rjkroege
2010/12/15 21:23:31
AFAIK the binary search approach won't necessarily
oshima
2010/12/16 01:15:19
Thank you for the info. Updated the comment above.
|
| + 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 { |
|
sky
2010/12/15 20:31:27
What about == ?
oshima
2010/12/16 01:15:19
Added shortcut for == case.
|
| + right = pivot; |
| + right_pos = pivot_pos; |
| + } |
| + } |
| + return left_pos; |
| +} |
| + |
| +/////////////////////////////////////////////////////////////////////////////// |
| +// NativeTextfieldWrapper: |
| + |
| +// static |
| +NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper( |
| + Textfield* field) { |
| + if (NativeTextfieldView::IsTextfieldViewEnabled()) { |
| + return new NativeTextfieldView(field); |
| + } else { |
| + return new NativeTextfieldGtk(field); |
| + } |
| +} |
| + |
| +/////////////////////////////////////////////////////////////////////////////// |
| +// |
| +// TextifieldBorder |
| +// |
| +/////////////////////////////////////////////////////////////////////////////// |
| + |
| +NativeTextfieldView::TextfieldBorder::TextfieldBorder() |
| + : insets_(4, 4, 4, 4) { |
| +} |
| + |
| +void NativeTextfieldView::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_ ? 3 : 1); |
| + |
| + canvas->AsCanvasSkia()->drawPath(path, paint); |
| +} |
| + |
| +void NativeTextfieldView::TextfieldBorder::GetInsets(gfx::Insets* insets) const |
| +{ |
| + *insets = insets_; |
| +} |
| + |
| +void NativeTextfieldView::TextfieldBorder::SetInsets(int top, |
| + int left, |
| + int bottom, |
| + int right) { |
| + insets_.Set(top, left, bottom, right); |
| +} |
| + |
| +} // namespace views |