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 |