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