 Chromium Code Reviews
 Chromium Code Reviews Issue 5857002:
  no native implementation of Textfield.  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/src
    
  
    Issue 5857002:
  no native implementation of Textfield.  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/src| OLD | NEW | 
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "views/controls/textfield/native_textfield_view.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/logging.h" | |
| 10 #include "base/message_loop.h" | |
| 11 #include "base/utf_string_conversions.h" | |
| 12 #include "gfx/canvas.h" | |
| 13 #include "gfx/canvas_skia.h" | |
| 14 #include "gfx/insets.h" | |
| 15 #include "views/background.h" | |
| 16 #include "views/border.h" | |
| 17 #include "views/controls/textfield/native_textfield_gtk.h" | |
| 18 #include "views/controls/textfield/textfield.h" | |
| 19 #include "views/controls/textfield/textfield_view_model.h" | |
| 20 #include "views/event.h" | |
| 21 | |
| 22 namespace { | |
| 23 | |
| 24 // A global flag to switch the Textfield wrapper to TextfieldView. | |
| 25 bool textfield_view_enabled = false; | |
| 26 | |
| 27 // Color setttings for text, border, backgrounds and cursor. | |
| 28 // These are tentative, and should be derived from theme, system | |
| 29 // settings and current settings. | |
| 30 const SkColor kSelectedTextColor = SK_ColorWHITE; | |
| 31 const SkColor kReadonlyTextColor = SK_ColorDKGRAY; | |
| 32 const SkColor kFocusedSelectionColor = SK_ColorBLUE; | |
| 33 const SkColor kUnfocusedSelectionColor = SK_ColorLTGRAY; | |
| 34 const SkColor kFocusedBorderColor = SK_ColorCYAN; | |
| 35 const SkColor kDefaultBorderColor = SK_ColorGRAY; | |
| 36 const SkColor kCursorColor = SK_ColorBLACK; | |
| 37 | |
| 38 } // namespace | |
| 39 | |
| 40 namespace views { | |
| 41 | |
| 42 const char NativeTextfieldView::kViewClassName[] = "views/NativeTextfieldView"; | |
| 43 | |
| 44 NativeTextfieldView::NativeTextfieldView(Textfield* parent) | |
| 45 : textfield_(parent), | |
| 46 model_(new TextfieldViewModel()), | |
| 47 text_border_(new TextfieldBorder()), | |
| 48 text_offset_(0), | |
| 49 insert_(true), | |
| 50 cursor_(false), | |
| 51 ALLOW_THIS_IN_INITIALIZER_LIST(cursor_timer_(this)) { | |
| 52 SetFocusable(true); | |
| 53 set_border(text_border_); | |
| 54 | |
| 55 // Multiline is not supported. | |
| 56 DCHECK_NE(parent->style(), Textfield::STYLE_MULTILINE); | |
| 57 // Lowercase is not supported. | |
| 58 DCHECK_NE(parent->style(), Textfield::STYLE_LOWERCASE); | |
| 59 } | |
| 60 | |
| 61 NativeTextfieldView::~NativeTextfieldView() { | |
| 62 } | |
| 63 | |
| 64 //////////////////////////////////////////////////////////////////////////////// | |
| 65 // NativeTextfieldView, View overrides: | |
| 66 | |
| 67 bool NativeTextfieldView::SkipDefaultKeyEventProcessing( | |
| 68 const views::KeyEvent& e) { | |
| 69 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
 | |
| 70 } | |
| 71 | |
| 72 bool NativeTextfieldView::OnMousePressed(const views::MouseEvent& e) { | |
| 73 RequestFocus(); | |
| 74 size_t pos = FindCursorPosition(e.location()); | |
| 75 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
 | |
| 76 UpdateCursorBoundsAndTextOffset(); | |
| 77 SchedulePaint(); | |
| 78 } | |
| 79 return true; | |
| 80 } | |
| 81 | |
| 82 bool NativeTextfieldView::OnMouseDragged(const views::MouseEvent& e) { | |
| 83 size_t pos = FindCursorPosition(e.location()); | |
| 84 if (model_->MoveCursorTo(pos, true)) { | |
| 85 UpdateCursorBoundsAndTextOffset(); | |
| 86 SchedulePaint(); | |
| 87 } | |
| 88 return true; | |
| 89 } | |
| 90 | |
| 91 void NativeTextfieldView::OnMouseReleased(const views::MouseEvent& e, | |
| 92 bool canceled) { | |
| 93 } | |
| 94 | |
| 95 bool NativeTextfieldView::OnKeyPressed(const views::KeyEvent& e) { | |
| 96 Textfield::Controller* controller = textfield_->GetController(); | |
| 97 bool handled = false; | |
| 98 if (controller) { | |
| 99 Textfield::Keystroke ks(&e); | |
| 100 handled = controller->HandleKeystroke(textfield_, ks); | |
| 101 } | |
| 102 handled = handled || HandleKeyEvent(e); | |
| 103 return handled; | |
| 104 } | |
| 105 | |
| 106 bool NativeTextfieldView::OnKeyReleased(const views::KeyEvent& e) { | |
| 107 return true; | |
| 108 } | |
| 109 | |
| 110 void NativeTextfieldView::Paint(gfx::Canvas* canvas) { | |
| 111 text_border_->set_has_focus(HasFocus()); | |
| 112 PaintBackground(canvas); | |
| 113 PaintTextAndCursor(canvas); | |
| 114 if (textfield_->draw_border()) | |
| 115 PaintBorder(canvas); | |
| 116 } | |
| 117 | |
| 118 void NativeTextfieldView::WillGainFocus() { | |
| 119 } | |
| 120 | |
| 121 void NativeTextfieldView::DidGainFocus() { | |
| 122 cursor_ = true; | |
| 123 SchedulePaint(); | |
| 124 // Start blinking cursor. | |
| 125 MessageLoop::current()->PostDelayedTask( | |
| 126 FROM_HERE, | |
| 127 cursor_timer_.NewRunnableMethod(&NativeTextfieldView::UpdateCursor), | |
| 128 800); | |
| 129 } | |
| 130 | |
| 131 void NativeTextfieldView::WillLoseFocus() { | |
| 132 // Stop blinking cursor. | |
| 133 cursor_timer_.RevokeAll(); | |
| 134 if (cursor_) { | |
| 135 cursor_ = false; | |
| 136 RepaintCursor(); | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 void NativeTextfieldView::DidChangeBounds(const gfx::Rect& previous, | |
| 141 const gfx::Rect& current) { | |
| 142 UpdateCursorBoundsAndTextOffset(); | |
| 143 } | |
| 144 | |
| 145 | |
| 146 ///////////////////////////////////////////////////////////////// | |
| 147 // NativeTextfieldView, NativeTextifieldWrapper overrides: | |
| 148 | |
| 149 string16 NativeTextfieldView::GetText() const { | |
| 150 return model_->text(); | |
| 151 } | |
| 152 | |
| 153 void NativeTextfieldView::UpdateText() { | |
| 154 bool changed = model_->SetText(textfield_->text()); | |
| 155 UpdateCursorBoundsAndTextOffset(); | |
| 156 SchedulePaint(); | |
| 157 if (changed) { | |
| 158 Textfield::Controller* controller = textfield_->GetController(); | |
| 159 if (controller) | |
| 160 controller->ContentsChanged(textfield_, GetText()); | |
| 161 } | |
| 162 } | |
| 163 | |
| 164 void NativeTextfieldView::AppendText(const string16& text) { | |
| 165 if (text.empty()) | |
| 166 return; | |
| 167 model_->Append(text); | |
| 168 UpdateCursorBoundsAndTextOffset(); | |
| 169 SchedulePaint(); | |
| 170 | |
| 171 Textfield::Controller* controller = textfield_->GetController(); | |
| 172 if (controller) | |
| 173 controller->ContentsChanged(textfield_, GetText()); | |
| 174 } | |
| 175 | |
| 176 string16 NativeTextfieldView::GetSelectedText() const { | |
| 177 return model_->GetSelectedText(); | |
| 178 } | |
| 179 | |
| 180 void NativeTextfieldView::SelectAll() { | |
| 181 model_->SelectAll(); | |
| 182 SchedulePaint(); | |
| 183 } | |
| 184 | |
| 185 void NativeTextfieldView::ClearSelection() { | |
| 186 model_->ClearSelection(); | |
| 187 SchedulePaint(); | |
| 188 } | |
| 189 | |
| 190 void NativeTextfieldView::UpdateBorder() { | |
| 191 if (textfield_->draw_border()) { | |
| 192 gfx::Insets insets = GetInsets(); | |
| 193 textfield_->SetHorizontalMargins(insets.left(), insets.right()); | |
| 194 textfield_->SetVerticalMargins(insets.top(), insets.bottom()); | |
| 195 } else { | |
| 196 textfield_->SetHorizontalMargins(0, 0); | |
| 197 textfield_->SetVerticalMargins(0, 0); | |
| 198 } | |
| 199 } | |
| 200 | |
| 201 void NativeTextfieldView::UpdateTextColor() { | |
| 202 SchedulePaint(); | |
| 203 } | |
| 204 | |
| 205 void NativeTextfieldView::UpdateBackgroundColor() { | |
| 206 // TODO(oshima): Background has to match the border's shape. | |
| 207 set_background( | |
| 208 Background::CreateSolidBackground(textfield_->background_color())); | |
| 209 SchedulePaint(); | |
| 210 } | |
| 211 | |
| 212 void NativeTextfieldView::UpdateReadOnly() { | |
| 213 SchedulePaint(); | |
| 214 } | |
| 215 | |
| 216 void NativeTextfieldView::UpdateFont() { | |
| 217 UpdateCursorBoundsAndTextOffset(); | |
| 218 } | |
| 219 | |
| 220 void NativeTextfieldView::UpdateIsPassword() { | |
| 221 model_->set_is_password(textfield_->IsPassword()); | |
| 222 UpdateCursorBoundsAndTextOffset(); | |
| 223 SchedulePaint(); | |
| 224 } | |
| 225 | |
| 226 void NativeTextfieldView::UpdateEnabled() { | |
| 227 SchedulePaint(); | |
| 228 } | |
| 229 | |
| 230 bool NativeTextfieldView::IsPassword() { | |
| 231 // looks unnecessary. should we remove? | |
| 232 NOTREACHED(); | |
| 233 return false; | |
| 234 } | |
| 235 | |
| 236 gfx::Insets NativeTextfieldView::CalculateInsets() { | |
| 237 return GetInsets(); | |
| 238 } | |
| 239 | |
| 240 void NativeTextfieldView::UpdateHorizontalMargins() { | |
| 241 int left, right; | |
| 242 if (!textfield_->GetHorizontalMargins(&left, &right)) | |
| 243 return; | |
| 244 gfx::Insets inset = GetInsets(); | |
| 245 | |
| 246 text_border_->SetInsets(inset.top(), left, inset.bottom(), right); | |
| 247 UpdateCursorBoundsAndTextOffset(); | |
| 248 } | |
| 249 | |
| 250 void NativeTextfieldView::UpdateVerticalMargins() { | |
| 251 int top, bottom; | |
| 252 if (!textfield_->GetVerticalMargins(&top, &bottom)) | |
| 253 return; | |
| 254 gfx::Insets inset = GetInsets(); | |
| 255 | |
| 256 text_border_->SetInsets(top, inset.left(), bottom, inset.right()); | |
| 257 UpdateCursorBoundsAndTextOffset(); | |
| 258 } | |
| 259 | |
| 260 void NativeTextfieldView::SetFocus() { | |
| 261 RequestFocus(); | |
| 262 } | |
| 263 | |
| 264 View* NativeTextfieldView::GetView() { | |
| 265 return this; | |
| 266 } | |
| 267 | |
| 268 gfx::NativeView NativeTextfieldView::GetTestingHandle() const { | |
| 269 NOTREACHED(); | |
| 270 return NULL; | |
| 271 } | |
| 272 | |
| 273 bool NativeTextfieldView::IsIMEComposing() const { | |
| 274 return false; | |
| 275 } | |
| 276 | |
| 277 // static | |
| 278 bool NativeTextfieldView::IsTextfieldViewEnabled() { | |
| 279 return textfield_view_enabled; | |
| 280 } | |
| 281 | |
| 282 // static | |
| 283 void NativeTextfieldView::SetEnableTextfieldView(bool enabled) { | |
| 284 textfield_view_enabled = enabled; | |
| 285 } | |
| 286 | |
| 287 | |
| 288 /////////////////////////////////////////////////////////////////////////////// | |
| 289 // NativeTextfieldView private: | |
| 290 | |
| 291 gfx::Font NativeTextfieldView::GetFont() const { | |
| 292 return textfield_->font(); | |
| 293 } | |
| 294 | |
| 295 SkColor NativeTextfieldView::GetTextColor() const { | |
| 296 return textfield_->text_color(); | |
| 297 } | |
| 298 | |
| 299 | |
| 300 void NativeTextfieldView::UpdateCursor() { | |
| 301 cursor_ = !cursor_; | |
| 302 RepaintCursor(); | |
| 303 MessageLoop::current()->PostDelayedTask( | |
| 304 FROM_HERE, | |
| 305 cursor_timer_.NewRunnableMethod(&NativeTextfieldView::UpdateCursor), | |
| 306 cursor_ ? 800 : 500); | |
| 307 } | |
| 308 | |
| 309 void NativeTextfieldView::RepaintCursor() { | |
| 310 gfx::Rect r = cursor_bounds_; | |
| 311 r.Inset(-1, -1, -1, -1); | |
| 312 SchedulePaint(r, false); | |
| 313 } | |
| 314 | |
| 315 void NativeTextfieldView::UpdateCursorBoundsAndTextOffset() { | |
| 316 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.
 | |
| 317 | |
| 318 gfx::Insets insets = GetInsets(); | |
| 319 | |
| 320 int width = bounds().width() - insets.width(); | |
| 321 | |
| 322 // TODO(oshima): bidi | |
| 323 gfx::Font font = GetFont(); | |
| 
sky
2010/12/15 20:31:27
const gfx::Font&
 
oshima
2010/12/16 01:15:19
Done.
 | |
| 324 int full_width = font.GetStringWidth(UTF16ToWide(model_->GetVisibleText())); | |
| 325 cursor_bounds_ = model_->GetCursorBounds(font); | |
| 326 cursor_bounds_.set_y(cursor_bounds_.y() + insets.top()); | |
| 327 | |
| 328 int x_right = text_offset_ + cursor_bounds_.right(); | |
| 329 int x_left = text_offset_ + cursor_bounds_.x(); | |
| 330 | |
| 331 if (full_width < width) { | |
| 332 // Show all text whenever the text fits to the size. | |
| 333 text_offset_ = 0; | |
| 334 } else if (x_right > width) { | |
| 335 // when the cursor overflows to the right | |
| 336 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
 | |
| 337 } else if (x_left < 0) { | |
| 338 // when the cursor overflows to the left | |
| 339 text_offset_ = -cursor_bounds_.x(); | |
| 340 } else if(full_width > width && text_offset_ + full_width < width) { | |
| 341 // when the cursor moves within the textfield with the text | |
| 342 // longer than the field. | |
| 343 text_offset_ = width - full_width; | |
| 344 } else { | |
| 345 // move cursor freely. | |
| 346 } | |
| 347 // shift cursor bounds to fit insets. | |
| 348 cursor_bounds_.set_x(cursor_bounds_.x() + text_offset_ + insets.left()); | |
| 349 } | |
| 350 | |
| 351 void NativeTextfieldView::PaintTextAndCursor(gfx::Canvas* canvas) { | |
| 352 gfx::Insets insets = GetInsets(); | |
| 353 | |
| 354 canvas->Save(); | |
| 355 canvas->ClipRectInt(insets.left(), insets.top(), | |
| 356 width() - insets.width(), height() - insets.height()); | |
| 357 | |
| 358 // TODO(oshima): bidi support | |
| 359 // TODO(varunjain): re-implement this so only that dirty text is painted. | |
| 360 TextfieldViewModel::TextFragments fragments; | |
| 361 model_->PopulateFragments(&fragments); | |
| 362 int x_offset = text_offset_ + insets.left(); | |
| 363 int y = insets.top(); | |
| 364 int text_height = height() - insets.height(); | |
| 365 SkColor selection_color = | |
| 366 HasFocus() ? kFocusedSelectionColor : kUnfocusedSelectionColor; | |
| 367 SkColor text_color = | |
| 368 textfield_->read_only() ? kReadonlyTextColor : GetTextColor(); | |
| 369 | |
| 370 for (TextfieldViewModel::TextFragments::const_iterator iter = | |
| 371 fragments.begin(); | |
| 372 iter != fragments.end(); | |
| 373 iter++) { | |
| 374 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
 | |
| 375 // TODO(oshima): This does not give the accurate position due to | |
| 376 // kerning. Figure out how webkit does this with skia. | |
| 377 int width = GetFont().GetStringWidth(UTF16ToWide(text)); | |
| 378 | |
| 379 if ((*iter).selected) { | |
| 
sky
2010/12/15 20:31:27
iter->selected
 
oshima
2010/12/16 01:15:19
same here.
 | |
| 380 canvas->FillRectInt(selection_color, x_offset, y, width, text_height); | |
| 381 canvas->DrawStringInt( | |
| 382 UTF16ToWide(text), GetFont(), kSelectedTextColor, | |
| 383 x_offset, y, width, text_height); | |
| 384 } else { | |
| 385 canvas->DrawStringInt( | |
| 386 UTF16ToWide(text), GetFont(), text_color, | |
| 387 x_offset, y, width, text_height); | |
| 388 } | |
| 389 x_offset += width; | |
| 390 } | |
| 391 canvas->Restore(); | |
| 392 | |
| 393 if (textfield_->IsEnabled() && cursor_) { | |
| 394 // Paint Cursor. Replace cursor is drawn as rectangle for now. | |
| 395 canvas->DrawRectInt(kCursorColor, | |
| 396 cursor_bounds_.x(), | |
| 397 cursor_bounds_.y(), | |
| 398 insert_ ? 0 : cursor_bounds_.width(), | |
| 399 cursor_bounds_.height()); | |
| 400 } | |
| 401 } | |
| 402 | |
| 403 bool NativeTextfieldView::HandleKeyEvent(const KeyEvent& key_event) { | |
| 404 // TODO(oshima): handle IME. | |
| 405 if (key_event.GetType() == views::Event::ET_KEY_PRESSED) { | |
| 406 app::KeyboardCode key_code = key_event.GetKeyCode(); | |
| 407 // TODO(oshima): shift-tab does not work. Figure out why and fix. | |
| 408 if (key_code == app::VKEY_TAB) | |
| 409 return false; | |
| 410 bool selection = key_event.IsShiftDown(); | |
| 411 bool control = key_event.IsControlDown(); | |
| 412 bool text_changed = false; | |
| 413 bool cursor_changed = false; | |
| 414 switch (key_code) { | |
| 415 case app::VKEY_A: | |
| 416 if (control) { | |
| 417 model_->SelectAll(); | |
| 418 cursor_changed = true; | |
| 419 } | |
| 420 break; | |
| 421 case app::VKEY_RIGHT: | |
| 422 control ? model_->MoveCursorToNextWord(selection) | |
| 423 : model_->MoveCursorRight(selection); | |
| 424 cursor_changed = true; | |
| 425 break; | |
| 426 case app::VKEY_LEFT: | |
| 427 control ? model_->MoveCursorToPreviousWord(selection) | |
| 428 : model_->MoveCursorLeft(selection); | |
| 429 cursor_changed = true; | |
| 430 break; | |
| 431 case app::VKEY_END: | |
| 432 model_->MoveCursorToEnd(selection); | |
| 433 cursor_changed = true; | |
| 434 break; | |
| 435 case app::VKEY_HOME: | |
| 436 model_->MoveCursorToStart(selection); | |
| 437 cursor_changed = true; | |
| 438 break; | |
| 439 case app::VKEY_BACK: | |
| 440 text_changed = model_->Backspace(); | |
| 441 cursor_changed = true; | |
| 442 break; | |
| 443 case app::VKEY_DELETE: | |
| 444 text_changed = model_->Delete(); | |
| 445 break; | |
| 446 case app::VKEY_INSERT: | |
| 447 insert_ = !insert_; | |
| 448 cursor_changed = true; | |
| 449 break; | |
| 450 default: | |
| 451 break; | |
| 452 } | |
| 453 char16 print_char = GetPrintableChar(key_event); | |
| 454 if (!control && print_char) { | |
| 455 if (insert_) | |
| 456 model_->Insert(print_char); | |
| 457 else | |
| 458 model_->Replace(print_char); | |
| 459 text_changed = true; | |
| 460 } | |
| 461 if (text_changed) { | |
| 462 textfield_->SyncText(); | |
| 463 Textfield::Controller* controller = textfield_->GetController(); | |
| 464 if (controller) | |
| 465 controller->ContentsChanged(textfield_, GetText()); | |
| 466 } | |
| 467 if (text_changed || cursor_changed) { | |
| 468 UpdateCursorBoundsAndTextOffset(); | |
| 469 SchedulePaint(); | |
| 470 } | |
| 471 } | |
| 472 return false; | |
| 473 } | |
| 474 | |
| 475 char16 NativeTextfieldView::GetPrintableChar(const KeyEvent& key_event) { | |
| 476 // TODO(oshima): IME, i18n support. | |
| 477 app::KeyboardCode key_code = key_event.GetKeyCode(); | |
| 478 bool shift = false; | |
| 479 if ((key_code >= app::VKEY_0 && key_code <= app::VKEY_9) || | |
| 480 (key_code >= app::VKEY_OEM_1 && key_code <= app::VKEY_OEM_8)) { | |
| 481 shift = key_event.IsShiftDown(); | |
| 482 } else if (key_code >= app::VKEY_A && key_code <= app::VKEY_Z) { | |
| 483 #if defined(TOUCH_UI) | |
| 484 shift = (key_event.IsLockDown() && !key_event.IsShiftDown()) || | |
| 485 (!key_event.IsLockDown() && key_event.IsShiftDown()); | |
| 486 #else | |
| 487 shift = key_event.IsShiftDown(); | |
| 488 #endif | |
| 489 } | |
| 490 if (key_event.IsCapsLockDown()) | |
| 491 shift = !shift; | |
| 492 return GetPrintableChar(key_code, shift); | |
| 493 } | |
| 494 | |
| 495 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
 | |
| 496 bool shift) { | |
| 497 // TODO(oshima): We should have a utility function | |
| 498 // under app to convert a KeyboardCode to a printable character. | |
| 499 switch (key_code) { | |
| 500 case app::VKEY_NUMPAD0: | |
| 501 return '0'; | |
| 502 case app::VKEY_NUMPAD1: | |
| 503 return '1'; | |
| 504 case app::VKEY_NUMPAD2: | |
| 505 return '2'; | |
| 506 case app::VKEY_NUMPAD3: | |
| 507 return '3'; | |
| 508 case app::VKEY_NUMPAD4: | |
| 509 return '4'; | |
| 510 case app::VKEY_NUMPAD5: | |
| 511 return '5'; | |
| 512 case app::VKEY_NUMPAD6: | |
| 513 return '6'; | |
| 514 case app::VKEY_NUMPAD7: | |
| 515 return '7'; | |
| 516 case app::VKEY_NUMPAD8: | |
| 517 return '8'; | |
| 518 case app::VKEY_NUMPAD9: | |
| 519 return '9'; | |
| 520 case app::VKEY_MULTIPLY: | |
| 521 return '*'; | |
| 522 case app::VKEY_ADD: | |
| 523 return '+'; | |
| 524 case app::VKEY_SUBTRACT: | |
| 525 return '-'; | |
| 526 case app::VKEY_DECIMAL: | |
| 527 return '.'; | |
| 528 case app::VKEY_DIVIDE: | |
| 529 return '/'; | |
| 530 case app::VKEY_SPACE: | |
| 531 return ' '; | |
| 532 case app::VKEY_0: | |
| 533 return shift ? ')' : '0'; | |
| 534 case app::VKEY_1: | |
| 535 return shift ? '!' : '1'; | |
| 536 case app::VKEY_2: | |
| 537 return shift ? '@' : '2'; | |
| 538 case app::VKEY_3: | |
| 539 return shift ? '#' : '3'; | |
| 540 case app::VKEY_4: | |
| 541 return shift ? '$' : '4'; | |
| 542 case app::VKEY_5: | |
| 543 return shift ? '%' : '5'; | |
| 544 case app::VKEY_6: | |
| 545 return shift ? '^' : '6'; | |
| 546 case app::VKEY_7: | |
| 547 return shift ? '&' : '7'; | |
| 548 case app::VKEY_8: | |
| 549 return shift ? '*' : '8'; | |
| 550 case app::VKEY_9: | |
| 551 return shift ? '(' : '9'; | |
| 552 | |
| 553 case app::VKEY_A: | |
| 554 case app::VKEY_B: | |
| 555 case app::VKEY_C: | |
| 556 case app::VKEY_D: | |
| 557 case app::VKEY_E: | |
| 558 case app::VKEY_F: | |
| 559 case app::VKEY_G: | |
| 560 case app::VKEY_H: | |
| 561 case app::VKEY_I: | |
| 562 case app::VKEY_J: | |
| 563 case app::VKEY_K: | |
| 564 case app::VKEY_L: | |
| 565 case app::VKEY_M: | |
| 566 case app::VKEY_N: | |
| 567 case app::VKEY_O: | |
| 568 case app::VKEY_P: | |
| 569 case app::VKEY_Q: | |
| 570 case app::VKEY_R: | |
| 571 case app::VKEY_S: | |
| 572 case app::VKEY_T: | |
| 573 case app::VKEY_U: | |
| 574 case app::VKEY_V: | |
| 575 case app::VKEY_W: | |
| 576 case app::VKEY_X: | |
| 577 case app::VKEY_Y: | |
| 578 case app::VKEY_Z: | |
| 579 return (shift ? 'A' : 'a') + (key_code - app::VKEY_A); | |
| 580 case app::VKEY_OEM_1: | |
| 581 return shift ? ':' : ';'; | |
| 582 case app::VKEY_OEM_PLUS: | |
| 583 return shift ? '+' : '='; | |
| 584 case app::VKEY_OEM_COMMA: | |
| 585 return shift ? '<' : ','; | |
| 586 case app::VKEY_OEM_MINUS: | |
| 587 return shift ? '_' : '-'; | |
| 588 case app::VKEY_OEM_PERIOD: | |
| 589 return shift ? '>' : '.'; | |
| 590 case app::VKEY_OEM_2: | |
| 591 return shift ? '?' : '/'; | |
| 592 case app::VKEY_OEM_3: | |
| 593 return shift ? '~' : '`'; | |
| 594 case app::VKEY_OEM_4: | |
| 595 return shift ? '}' : ']'; | |
| 596 case app::VKEY_OEM_5: | |
| 597 return shift ? '|' : '\\'; | |
| 598 case app::VKEY_OEM_6: | |
| 599 return shift ? '{' : '['; | |
| 600 case app::VKEY_OEM_7: | |
| 601 return shift ? '"' : '\''; | |
| 602 default: | |
| 603 return 0; | |
| 604 } | |
| 605 } | |
| 606 | |
| 607 size_t NativeTextfieldView::FindCursorPosition(const gfx::Point& point) const { | |
| 608 // TODO(oshima): BIDI | |
| 609 gfx::Font font = GetFont(); | |
| 610 gfx::Insets insets = GetInsets(); | |
| 611 std::wstring text = UTF16ToWide(model_->GetVisibleText()); | |
| 612 int left = 0; | |
| 613 int left_pos = 0; | |
| 614 int right = font.GetStringWidth(text); | |
| 615 int right_pos = text.length(); | |
| 616 | |
| 617 int x = point.x() - insets.left(); | |
| 618 if (x <= left) return left_pos; | |
| 619 if (x >= right) return right_pos; | |
| 620 // binary searching the cursor position. | |
| 621 // TODO(oshima): use the center of character instead of edge. | |
| 622 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.
 | |
| 623 int pivot_pos = left_pos + (right_pos - left_pos) / 2; | |
| 624 int pivot = font.GetStringWidth(text.substr(0, pivot_pos)); | |
| 625 if (pivot < x) { | |
| 626 left = pivot; | |
| 627 left_pos = pivot_pos; | |
| 628 } else { | |
| 
sky
2010/12/15 20:31:27
What about == ?
 
oshima
2010/12/16 01:15:19
Added shortcut for == case.
 | |
| 629 right = pivot; | |
| 630 right_pos = pivot_pos; | |
| 631 } | |
| 632 } | |
| 633 return left_pos; | |
| 634 } | |
| 635 | |
| 636 /////////////////////////////////////////////////////////////////////////////// | |
| 637 // NativeTextfieldWrapper: | |
| 638 | |
| 639 // static | |
| 640 NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper( | |
| 641 Textfield* field) { | |
| 642 if (NativeTextfieldView::IsTextfieldViewEnabled()) { | |
| 643 return new NativeTextfieldView(field); | |
| 644 } else { | |
| 645 return new NativeTextfieldGtk(field); | |
| 646 } | |
| 647 } | |
| 648 | |
| 649 /////////////////////////////////////////////////////////////////////////////// | |
| 650 // | |
| 651 // TextifieldBorder | |
| 652 // | |
| 653 /////////////////////////////////////////////////////////////////////////////// | |
| 654 | |
| 655 NativeTextfieldView::TextfieldBorder::TextfieldBorder() | |
| 656 : insets_(4, 4, 4, 4) { | |
| 657 } | |
| 658 | |
| 659 void NativeTextfieldView::TextfieldBorder::Paint( | |
| 660 const View& view, gfx::Canvas* canvas) const { | |
| 661 SkRect rect; | |
| 662 rect.set(SkIntToScalar(0), SkIntToScalar(0), | |
| 663 SkIntToScalar(view.width()), SkIntToScalar(view.height())); | |
| 664 SkScalar corners[8] = { | |
| 665 // top-left | |
| 666 insets_.left(), | |
| 667 insets_.top(), | |
| 668 // top-right | |
| 669 insets_.right(), | |
| 670 insets_.top(), | |
| 671 // bottom-right | |
| 672 insets_.right(), | |
| 673 insets_.bottom(), | |
| 674 // bottom-left | |
| 675 insets_.left(), | |
| 676 insets_.bottom(), | |
| 677 }; | |
| 678 SkPath path; | |
| 679 path.addRoundRect(rect, corners); | |
| 680 SkPaint paint; | |
| 681 paint.setStyle(SkPaint::kStroke_Style); | |
| 682 paint.setFlags(SkPaint::kAntiAlias_Flag); | |
| 683 // TODO(oshima): Copy what WebKit does for focused border. | |
| 684 paint.setColor(has_focus_ ? kFocusedBorderColor : kDefaultBorderColor); | |
| 685 paint.setStrokeWidth(has_focus_ ? 3 : 1); | |
| 686 | |
| 687 canvas->AsCanvasSkia()->drawPath(path, paint); | |
| 688 } | |
| 689 | |
| 690 void NativeTextfieldView::TextfieldBorder::GetInsets(gfx::Insets* insets) const | |
| 691 { | |
| 692 *insets = insets_; | |
| 693 } | |
| 694 | |
| 695 void NativeTextfieldView::TextfieldBorder::SetInsets(int top, | |
| 696 int left, | |
| 697 int bottom, | |
| 698 int right) { | |
| 699 insets_.Set(top, left, bottom, right); | |
| 700 } | |
| 701 | |
| 702 } // namespace views | |
| OLD | NEW |