Chromium Code Reviews| 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[] = "views/NativeTextfieldViews" ; | |
|
sky
2010/12/16 22:43:04
nit: > 80
oshima
2010/12/16 23:50:02
Done.
| |
| 50 | |
| 51 NativeTextfieldViews::NativeTextfieldViews(Textfield* parent) | |
| 52 : textfield_(parent), | |
| 53 model_(new TextfieldViewsModel()), | |
| 54 text_border_(new TextfieldBorder()), | |
| 55 text_offset_(0), | |
| 56 insert_(true), | |
| 57 is_cursor_visible_(false), | |
| 58 ALLOW_THIS_IN_INITIALIZER_LIST(cursor_timer_(this)) { | |
| 59 SetFocusable(true); | |
| 60 set_border(text_border_); | |
| 61 | |
| 62 // Multiline is not supported. | |
| 63 DCHECK_NE(parent->style(), Textfield::STYLE_MULTILINE); | |
| 64 // Lowercase is not supported. | |
| 65 DCHECK_NE(parent->style(), Textfield::STYLE_LOWERCASE); | |
| 66 } | |
| 67 | |
| 68 NativeTextfieldViews::~NativeTextfieldViews() { | |
| 69 } | |
| 70 | |
| 71 //////////////////////////////////////////////////////////////////////////////// | |
| 72 // NativeTextfieldViews, View overrides: | |
| 73 | |
| 74 bool NativeTextfieldViews::OnMousePressed(const views::MouseEvent& e) { | |
| 75 RequestFocus(); | |
| 76 size_t pos = FindCursorPosition(e.location()); | |
| 77 if (model_->MoveCursorTo(pos, false)) { | |
| 78 UpdateCursorBoundsAndTextOffset(); | |
| 79 SchedulePaint(); | |
| 80 } | |
| 81 return true; | |
| 82 } | |
| 83 | |
| 84 bool NativeTextfieldViews::OnMouseDragged(const views::MouseEvent& e) { | |
| 85 size_t pos = FindCursorPosition(e.location()); | |
| 86 if (model_->MoveCursorTo(pos, true)) { | |
| 87 UpdateCursorBoundsAndTextOffset(); | |
| 88 SchedulePaint(); | |
| 89 } | |
| 90 return true; | |
| 91 } | |
| 92 | |
| 93 void NativeTextfieldViews::OnMouseReleased(const views::MouseEvent& e, | |
| 94 bool canceled) { | |
| 95 } | |
| 96 | |
| 97 bool NativeTextfieldViews::OnKeyPressed(const views::KeyEvent& e) { | |
| 98 Textfield::Controller* controller = textfield_->GetController(); | |
| 99 bool handled = false; | |
| 100 if (controller) { | |
| 101 Textfield::Keystroke ks(&e); | |
| 102 handled = controller->HandleKeystroke(textfield_, ks); | |
| 103 } | |
| 104 handled = handled || HandleKeyEvent(e); | |
|
sky
2010/12/16 22:43:04
No point in assigning and just returning. Make 104
oshima
2010/12/16 23:50:02
Done.
| |
| 105 return handled; | |
| 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( | |
|
sky
2010/12/16 22:43:04
Any reason you're not using a RepeatingTimer?
oshima
2010/12/16 23:50:02
because its delay is alternativing 800 and 500 ms
| |
| 128 FROM_HERE, | |
| 129 cursor_timer_.NewRunnableMethod(&NativeTextfieldViews::UpdateCursor), | |
| 130 800); | |
|
sky
2010/12/16 22:43:04
make this is a constant.
oshima
2010/12/16 23:50:02
Overlooked. Done.
| |
| 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(); | |
|
sky
2010/12/16 22:43:04
Are you sure we want to notify the controller here
oshima
2010/12/16 23:50:02
gtk is using "changed" signal, which is called whe
| |
| 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(); | |
|
sky
2010/12/16 22:43:04
Doesn't this need to take into consideration text_
oshima
2010/12/16 23:50:02
good catch. Thanks. done.
| |
| 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(); | |
| 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 } 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 : insets_(4, 4, 4, 4) { | |
|
sky
2010/12/16 22:43:04
Initialize has_focus_ to false
oshima
2010/12/16 23:50:02
Done.
| |
| 652 } | |
| 653 | |
| 654 void NativeTextfieldViews::TextfieldBorder::Paint( | |
| 655 const View& view, gfx::Canvas* canvas) const { | |
| 656 SkRect rect; | |
| 657 rect.set(SkIntToScalar(0), SkIntToScalar(0), | |
| 658 SkIntToScalar(view.width()), SkIntToScalar(view.height())); | |
| 659 SkScalar corners[8] = { | |
| 660 // top-left | |
| 661 insets_.left(), | |
| 662 insets_.top(), | |
| 663 // top-right | |
| 664 insets_.right(), | |
| 665 insets_.top(), | |
| 666 // bottom-right | |
| 667 insets_.right(), | |
| 668 insets_.bottom(), | |
| 669 // bottom-left | |
| 670 insets_.left(), | |
| 671 insets_.bottom(), | |
| 672 }; | |
| 673 SkPath path; | |
| 674 path.addRoundRect(rect, corners); | |
| 675 SkPaint paint; | |
| 676 paint.setStyle(SkPaint::kStroke_Style); | |
| 677 paint.setFlags(SkPaint::kAntiAlias_Flag); | |
| 678 // TODO(oshima): Copy what WebKit does for focused border. | |
| 679 paint.setColor(has_focus_ ? kFocusedBorderColor : kDefaultBorderColor); | |
| 680 paint.setStrokeWidth(has_focus_ ? 2 : 1); | |
| 681 | |
| 682 canvas->AsCanvasSkia()->drawPath(path, paint); | |
| 683 } | |
| 684 | |
| 685 void NativeTextfieldViews::TextfieldBorder::GetInsets(gfx::Insets* insets) const | |
| 686 { | |
| 687 *insets = insets_; | |
| 688 } | |
| 689 | |
| 690 void NativeTextfieldViews::TextfieldBorder::SetInsets(int top, | |
| 691 int left, | |
| 692 int bottom, | |
| 693 int right) { | |
| 694 insets_.Set(top, left, bottom, right); | |
| 695 } | |
| 696 | |
| 697 } // namespace views | |
| OLD | NEW |