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 |