OLD | NEW |
| (Empty) |
1 // Copyright (c) 2009 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/text_field.h" | |
6 | |
7 #include <atlbase.h> | |
8 #include <atlapp.h> | |
9 #include <atlcrack.h> | |
10 #include <atlctrls.h> | |
11 #include <atlmisc.h> | |
12 #include <tom.h> // For ITextDocument, a COM interface to CRichEditCtrl | |
13 #include <vsstyle.h> | |
14 | |
15 #include "app/gfx/insets.h" | |
16 #include "app/l10n_util.h" | |
17 #include "app/l10n_util_win.h" | |
18 #include "app/win_util.h" | |
19 #include "base/clipboard.h" | |
20 #include "base/gfx/native_theme.h" | |
21 #include "base/scoped_clipboard_writer.h" | |
22 #include "base/string_util.h" | |
23 #include "base/win_util.h" | |
24 #include "grit/app_strings.h" | |
25 #include "skia/ext/skia_utils_win.h" | |
26 #include "views/controls/hwnd_view.h" | |
27 #include "views/controls/menu/menu_win.h" | |
28 #include "views/focus/focus_util_win.h" | |
29 #include "views/views_delegate.h" | |
30 #include "views/widget/widget.h" | |
31 | |
32 using gfx::NativeTheme; | |
33 | |
34 namespace views { | |
35 | |
36 static const int kDefaultEditStyle = WS_CHILD | WS_VISIBLE; | |
37 | |
38 class TextField::Edit | |
39 : public CWindowImpl<TextField::Edit, CRichEditCtrl, | |
40 CWinTraits<kDefaultEditStyle> >, | |
41 public CRichEditCommands<TextField::Edit>, | |
42 public Menu::Delegate { | |
43 public: | |
44 DECLARE_WND_CLASS(L"ChromeViewsTextFieldEdit"); | |
45 | |
46 Edit(TextField* parent, bool draw_border); | |
47 ~Edit(); | |
48 | |
49 std::wstring GetText() const; | |
50 void SetText(const std::wstring& text); | |
51 void AppendText(const std::wstring& text); | |
52 | |
53 std::wstring GetSelectedText() const; | |
54 | |
55 // Selects all the text in the edit. Use this in place of SetSelAll() to | |
56 // avoid selecting the "phantom newline" at the end of the edit. | |
57 void SelectAll(); | |
58 | |
59 // Clears the selection within the edit field and sets the caret to the end. | |
60 void ClearSelection(); | |
61 | |
62 // Removes the border. | |
63 void RemoveBorder(); | |
64 | |
65 void SetEnabled(bool enabled); | |
66 | |
67 void SetBackgroundColor(COLORREF bg_color); | |
68 | |
69 // CWindowImpl | |
70 BEGIN_MSG_MAP(Edit) | |
71 MSG_WM_CHAR(OnChar) | |
72 MSG_WM_CONTEXTMENU(OnContextMenu) | |
73 MSG_WM_COPY(OnCopy) | |
74 MSG_WM_CUT(OnCut) | |
75 MESSAGE_HANDLER_EX(WM_IME_CHAR, OnImeChar) | |
76 MESSAGE_HANDLER_EX(WM_IME_STARTCOMPOSITION, OnImeStartComposition) | |
77 MESSAGE_HANDLER_EX(WM_IME_COMPOSITION, OnImeComposition) | |
78 MESSAGE_HANDLER_EX(WM_IME_ENDCOMPOSITION, OnImeEndComposition) | |
79 MSG_WM_KEYDOWN(OnKeyDown) | |
80 MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk) | |
81 MSG_WM_LBUTTONDOWN(OnLButtonDown) | |
82 MSG_WM_LBUTTONUP(OnLButtonUp) | |
83 MSG_WM_MBUTTONDOWN(OnNonLButtonDown) | |
84 MSG_WM_MOUSEMOVE(OnMouseMove) | |
85 MSG_WM_MOUSELEAVE(OnMouseLeave) | |
86 MESSAGE_HANDLER_EX(WM_MOUSEWHEEL, OnMouseWheel) | |
87 MSG_WM_NCCALCSIZE(OnNCCalcSize) | |
88 MSG_WM_NCPAINT(OnNCPaint) | |
89 MSG_WM_RBUTTONDOWN(OnNonLButtonDown) | |
90 MSG_WM_PASTE(OnPaste) | |
91 MSG_WM_SYSCHAR(OnSysChar) // WM_SYSxxx == WM_xxx with ALT down | |
92 MSG_WM_SYSKEYDOWN(OnKeyDown) | |
93 END_MSG_MAP() | |
94 | |
95 // Menu::Delegate | |
96 virtual bool IsCommandEnabled(int id) const; | |
97 virtual void ExecuteCommand(int id); | |
98 | |
99 private: | |
100 // This object freezes repainting of the edit until the object is destroyed. | |
101 // Some methods of the CRichEditCtrl draw synchronously to the screen. If we | |
102 // don't freeze, the user will see a rapid series of calls to these as | |
103 // flickers. | |
104 // | |
105 // Freezing the control while it is already frozen is permitted; the control | |
106 // will unfreeze once both freezes are released (the freezes stack). | |
107 class ScopedFreeze { | |
108 public: | |
109 ScopedFreeze(Edit* edit, ITextDocument* text_object_model); | |
110 ~ScopedFreeze(); | |
111 | |
112 private: | |
113 Edit* const edit_; | |
114 ITextDocument* const text_object_model_; | |
115 | |
116 DISALLOW_COPY_AND_ASSIGN(ScopedFreeze); | |
117 }; | |
118 | |
119 // message handlers | |
120 void OnChar(TCHAR key, UINT repeat_count, UINT flags); | |
121 void OnContextMenu(HWND window, const CPoint& point); | |
122 void OnCopy(); | |
123 void OnCut(); | |
124 LRESULT OnImeChar(UINT message, WPARAM wparam, LPARAM lparam); | |
125 LRESULT OnImeStartComposition(UINT message, WPARAM wparam, LPARAM lparam); | |
126 LRESULT OnImeComposition(UINT message, WPARAM wparam, LPARAM lparam); | |
127 LRESULT OnImeEndComposition(UINT message, WPARAM wparam, LPARAM lparam); | |
128 void OnKeyDown(TCHAR key, UINT repeat_count, UINT flags); | |
129 void OnLButtonDblClk(UINT keys, const CPoint& point); | |
130 void OnLButtonDown(UINT keys, const CPoint& point); | |
131 void OnLButtonUp(UINT keys, const CPoint& point); | |
132 void OnMouseLeave(); | |
133 LRESULT OnMouseWheel(UINT message, WPARAM w_param, LPARAM l_param); | |
134 void OnMouseMove(UINT keys, const CPoint& point); | |
135 int OnNCCalcSize(BOOL w_param, LPARAM l_param); | |
136 void OnNCPaint(HRGN region); | |
137 void OnNonLButtonDown(UINT keys, const CPoint& point); | |
138 void OnPaste(); | |
139 void OnSysChar(TCHAR ch, UINT repeat_count, UINT flags); | |
140 | |
141 // Helper function for OnChar() and OnKeyDown() that handles keystrokes that | |
142 // could change the text in the edit. | |
143 void HandleKeystroke(UINT message, TCHAR key, UINT repeat_count, UINT flags); | |
144 | |
145 // Every piece of code that can change the edit should call these functions | |
146 // before and after the change. These functions determine if anything | |
147 // meaningful changed, and do any necessary updating and notification. | |
148 void OnBeforePossibleChange(); | |
149 void OnAfterPossibleChange(); | |
150 | |
151 // Given an X coordinate in client coordinates, returns that coordinate | |
152 // clipped to be within the horizontal bounds of the visible text. | |
153 // | |
154 // This is used in our mouse handlers to work around quirky behaviors of the | |
155 // underlying CRichEditCtrl like not supporting triple-click when the user | |
156 // doesn't click on the text itself. | |
157 // | |
158 // |is_triple_click| should be true iff this is the third click of a triple | |
159 // click. Sadly, we need to clip slightly differently in this case. | |
160 LONG ClipXCoordToVisibleText(LONG x, bool is_triple_click) const; | |
161 | |
162 // Sets whether the mouse is in the edit. As necessary this redraws the | |
163 // edit. | |
164 void SetContainsMouse(bool contains_mouse); | |
165 | |
166 // Getter for the text_object_model_, used by the ScopedFreeze class. Note | |
167 // that the pointer returned here is only valid as long as the Edit is still | |
168 // alive. | |
169 ITextDocument* GetTextObjectModel() const; | |
170 | |
171 // We need to know if the user triple-clicks, so track double click points | |
172 // and times so we can see if subsequent clicks are actually triple clicks. | |
173 bool tracking_double_click_; | |
174 CPoint double_click_point_; | |
175 DWORD double_click_time_; | |
176 | |
177 // Used to discard unnecessary WM_MOUSEMOVE events after the first such | |
178 // unnecessary event. See detailed comments in OnMouseMove(). | |
179 bool can_discard_mousemove_; | |
180 | |
181 // The text of this control before a possible change. | |
182 std::wstring text_before_change_; | |
183 | |
184 // If true, the mouse is over the edit. | |
185 bool contains_mouse_; | |
186 | |
187 static bool did_load_library_; | |
188 | |
189 TextField* parent_; | |
190 | |
191 // The context menu for the edit. | |
192 scoped_ptr<Menu> context_menu_; | |
193 | |
194 // Border insets. | |
195 gfx::Insets content_insets_; | |
196 | |
197 // Whether the border is drawn. | |
198 bool draw_border_; | |
199 | |
200 // This interface is useful for accessing the CRichEditCtrl at a low level. | |
201 mutable CComQIPtr<ITextDocument> text_object_model_; | |
202 | |
203 // The position and the length of the ongoing composition string. | |
204 // These values are used for removing a composition string from a search | |
205 // text to emulate Firefox. | |
206 bool ime_discard_composition_; | |
207 int ime_composition_start_; | |
208 int ime_composition_length_; | |
209 | |
210 COLORREF bg_color_; | |
211 | |
212 DISALLOW_COPY_AND_ASSIGN(Edit); | |
213 }; | |
214 | |
215 /////////////////////////////////////////////////////////////////////////////// | |
216 // Helper classes | |
217 | |
218 TextField::Edit::ScopedFreeze::ScopedFreeze(TextField::Edit* edit, | |
219 ITextDocument* text_object_model) | |
220 : edit_(edit), | |
221 text_object_model_(text_object_model) { | |
222 // Freeze the screen. | |
223 if (text_object_model_) { | |
224 long count; | |
225 text_object_model_->Freeze(&count); | |
226 } | |
227 } | |
228 | |
229 TextField::Edit::ScopedFreeze::~ScopedFreeze() { | |
230 // Unfreeze the screen. | |
231 if (text_object_model_) { | |
232 long count; | |
233 text_object_model_->Unfreeze(&count); | |
234 if (count == 0) { | |
235 // We need to UpdateWindow() here instead of InvalidateRect() because, as | |
236 // far as I can tell, the edit likes to synchronously erase its background | |
237 // when unfreezing, thus requiring us to synchronously redraw if we don't | |
238 // want flicker. | |
239 edit_->UpdateWindow(); | |
240 } | |
241 } | |
242 } | |
243 | |
244 /////////////////////////////////////////////////////////////////////////////// | |
245 // TextField::Edit | |
246 | |
247 bool TextField::Edit::did_load_library_ = false; | |
248 | |
249 TextField::Edit::Edit(TextField* parent, bool draw_border) | |
250 : parent_(parent), | |
251 tracking_double_click_(false), | |
252 double_click_time_(0), | |
253 can_discard_mousemove_(false), | |
254 contains_mouse_(false), | |
255 draw_border_(draw_border), | |
256 ime_discard_composition_(false), | |
257 ime_composition_start_(0), | |
258 ime_composition_length_(0), | |
259 bg_color_(0) { | |
260 if (!did_load_library_) | |
261 did_load_library_ = !!LoadLibrary(L"riched20.dll"); | |
262 | |
263 DWORD style = kDefaultEditStyle; | |
264 if (parent->GetStyle() & TextField::STYLE_PASSWORD) | |
265 style |= ES_PASSWORD; | |
266 | |
267 if (parent->read_only_) | |
268 style |= ES_READONLY; | |
269 | |
270 if (parent->GetStyle() & TextField::STYLE_MULTILINE) | |
271 style |= ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL; | |
272 else | |
273 style |= ES_AUTOHSCROLL; | |
274 // Make sure we apply RTL related extended window styles if necessary. | |
275 DWORD ex_style = l10n_util::GetExtendedStyles(); | |
276 | |
277 RECT r = {0, 0, parent_->width(), parent_->height()}; | |
278 Create(parent_->GetWidget()->GetNativeView(), r, NULL, style, ex_style); | |
279 | |
280 if (parent->GetStyle() & TextField::STYLE_LOWERCASE) { | |
281 DCHECK((parent->GetStyle() & TextField::STYLE_PASSWORD) == 0); | |
282 SetEditStyle(SES_LOWERCASE, SES_LOWERCASE); | |
283 } | |
284 | |
285 // Set up the text_object_model_. | |
286 CComPtr<IRichEditOle> ole_interface; | |
287 ole_interface.Attach(GetOleInterface()); | |
288 text_object_model_ = ole_interface; | |
289 | |
290 context_menu_.reset(new MenuWin(this, Menu::TOPLEFT, m_hWnd)); | |
291 context_menu_->AppendMenuItemWithLabel(IDS_APP_UNDO, | |
292 l10n_util::GetString(IDS_APP_UNDO)); | |
293 context_menu_->AppendSeparator(); | |
294 context_menu_->AppendMenuItemWithLabel(IDS_APP_CUT, | |
295 l10n_util::GetString(IDS_APP_CUT)); | |
296 context_menu_->AppendMenuItemWithLabel(IDS_APP_COPY, | |
297 l10n_util::GetString(IDS_APP_COPY)); | |
298 context_menu_->AppendMenuItemWithLabel(IDS_APP_PASTE, | |
299 l10n_util::GetString(IDS_APP_PASTE)); | |
300 context_menu_->AppendSeparator(); | |
301 context_menu_->AppendMenuItemWithLabel(IDS_APP_SELECT_ALL, | |
302 l10n_util::GetString(IDS_APP_SELECT_ALL
)); | |
303 } | |
304 | |
305 TextField::Edit::~Edit() { | |
306 } | |
307 | |
308 std::wstring TextField::Edit::GetText() const { | |
309 int len = GetTextLength() + 1; | |
310 std::wstring str; | |
311 GetWindowText(WriteInto(&str, len), len); | |
312 return str; | |
313 } | |
314 | |
315 void TextField::Edit::SetText(const std::wstring& text) { | |
316 // Adjusting the string direction before setting the text in order to make | |
317 // sure both RTL and LTR strings are displayed properly. | |
318 std::wstring text_to_set; | |
319 if (!l10n_util::AdjustStringForLocaleDirection(text, &text_to_set)) | |
320 text_to_set = text; | |
321 if (parent_->GetStyle() & STYLE_LOWERCASE) | |
322 text_to_set = l10n_util::ToLower(text_to_set); | |
323 SetWindowText(text_to_set.c_str()); | |
324 } | |
325 | |
326 void TextField::Edit::AppendText(const std::wstring& text) { | |
327 int text_length = GetWindowTextLength(); | |
328 ::SendMessage(m_hWnd, TBM_SETSEL, true, MAKELPARAM(text_length, text_length)); | |
329 ::SendMessage(m_hWnd, EM_REPLACESEL, false, | |
330 reinterpret_cast<LPARAM>(text.c_str())); | |
331 } | |
332 | |
333 std::wstring TextField::Edit::GetSelectedText() const { | |
334 // Figure out the length of the selection. | |
335 long start; | |
336 long end; | |
337 GetSel(start, end); | |
338 | |
339 // Grab the selected text. | |
340 std::wstring str; | |
341 GetSelText(WriteInto(&str, end - start + 1)); | |
342 | |
343 return str; | |
344 } | |
345 | |
346 void TextField::Edit::SelectAll() { | |
347 // Select from the end to the front so that the first part of the text is | |
348 // always visible. | |
349 SetSel(GetTextLength(), 0); | |
350 } | |
351 | |
352 void TextField::Edit::ClearSelection() { | |
353 SetSel(GetTextLength(), GetTextLength()); | |
354 } | |
355 | |
356 void TextField::Edit::RemoveBorder() { | |
357 if (!draw_border_) | |
358 return; | |
359 | |
360 draw_border_ = false; | |
361 SetWindowPos(NULL, 0, 0, 0, 0, | |
362 SWP_NOMOVE | SWP_FRAMECHANGED | SWP_NOACTIVATE | | |
363 SWP_NOOWNERZORDER | SWP_NOSIZE); | |
364 } | |
365 | |
366 void TextField::Edit::SetEnabled(bool enabled) { | |
367 SendMessage(parent_->GetNativeComponent(), WM_ENABLE, | |
368 static_cast<WPARAM>(enabled), 0); | |
369 } | |
370 | |
371 // static | |
372 bool TextField::IsKeystrokeEnter(const Keystroke& key) { | |
373 return key.key == VK_RETURN; | |
374 } | |
375 | |
376 // static | |
377 bool TextField::IsKeystrokeEscape(const Keystroke& key) { | |
378 return key.key == VK_ESCAPE; | |
379 } | |
380 | |
381 void TextField::Edit::SetBackgroundColor(COLORREF bg_color) { | |
382 CRichEditCtrl::SetBackgroundColor(bg_color); | |
383 bg_color_ = bg_color; | |
384 } | |
385 | |
386 bool TextField::Edit::IsCommandEnabled(int id) const { | |
387 switch (id) { | |
388 case IDS_APP_UNDO: return !parent_->IsReadOnly() && !!CanUndo(); | |
389 case IDS_APP_CUT: return !parent_->IsReadOnly() && | |
390 !parent_->IsPassword() && !!CanCut(); | |
391 case IDS_APP_COPY: return !!CanCopy() && !parent_->IsPassword(); | |
392 case IDS_APP_PASTE: return !parent_->IsReadOnly() && !!CanPaste(); | |
393 case IDS_APP_SELECT_ALL: return !!CanSelectAll(); | |
394 default: NOTREACHED(); | |
395 return false; | |
396 } | |
397 } | |
398 | |
399 void TextField::Edit::ExecuteCommand(int id) { | |
400 ScopedFreeze freeze(this, GetTextObjectModel()); | |
401 OnBeforePossibleChange(); | |
402 switch (id) { | |
403 case IDS_APP_UNDO: Undo(); break; | |
404 case IDS_APP_CUT: Cut(); break; | |
405 case IDS_APP_COPY: Copy(); break; | |
406 case IDS_APP_PASTE: Paste(); break; | |
407 case IDS_APP_SELECT_ALL: SelectAll(); break; | |
408 default: NOTREACHED(); break; | |
409 } | |
410 OnAfterPossibleChange(); | |
411 } | |
412 | |
413 void TextField::Edit::OnChar(TCHAR ch, UINT repeat_count, UINT flags) { | |
414 HandleKeystroke(GetCurrentMessage()->message, ch, repeat_count, flags); | |
415 } | |
416 | |
417 void TextField::Edit::OnContextMenu(HWND window, const CPoint& point) { | |
418 CPoint p(point); | |
419 if (point.x == -1 || point.y == -1) { | |
420 GetCaretPos(&p); | |
421 MapWindowPoints(HWND_DESKTOP, &p, 1); | |
422 } | |
423 context_menu_->RunMenuAt(p.x, p.y); | |
424 } | |
425 | |
426 void TextField::Edit::OnCopy() { | |
427 if (parent_->IsPassword()) | |
428 return; | |
429 | |
430 const std::wstring text(GetSelectedText()); | |
431 | |
432 if (!text.empty() && ViewsDelegate::views_delegate) { | |
433 ScopedClipboardWriter scw(ViewsDelegate::views_delegate->GetClipboard()); | |
434 scw.WriteText(text); | |
435 } | |
436 } | |
437 | |
438 void TextField::Edit::OnCut() { | |
439 if (parent_->IsReadOnly() || parent_->IsPassword()) | |
440 return; | |
441 | |
442 OnCopy(); | |
443 | |
444 // This replace selection will have no effect (even on the undo stack) if the | |
445 // current selection is empty. | |
446 ReplaceSel(L"", true); | |
447 } | |
448 | |
449 LRESULT TextField::Edit::OnImeChar(UINT message, WPARAM wparam, LPARAM lparam) { | |
450 // http://crbug.com/7707: a rich-edit control may crash when it receives a | |
451 // WM_IME_CHAR message while it is processing a WM_IME_COMPOSITION message. | |
452 // Since view controls don't need WM_IME_CHAR messages, we prevent WM_IME_CHAR | |
453 // messages from being dispatched to view controls via the CallWindowProc() | |
454 // call. | |
455 return 0; | |
456 } | |
457 | |
458 LRESULT TextField::Edit::OnImeStartComposition(UINT message, | |
459 WPARAM wparam, | |
460 LPARAM lparam) { | |
461 // Users may press alt+shift or control+shift keys to change their keyboard | |
462 // layouts. So, we retrieve the input locale identifier everytime we start | |
463 // an IME composition. | |
464 int language_id = PRIMARYLANGID(GetKeyboardLayout(0)); | |
465 ime_discard_composition_ = | |
466 language_id == LANG_JAPANESE || language_id == LANG_CHINESE; | |
467 ime_composition_start_ = 0; | |
468 ime_composition_length_ = 0; | |
469 | |
470 return DefWindowProc(message, wparam, lparam); | |
471 } | |
472 | |
473 LRESULT TextField::Edit::OnImeComposition(UINT message, | |
474 WPARAM wparam, | |
475 LPARAM lparam) { | |
476 text_before_change_.clear(); | |
477 LRESULT result = DefWindowProc(message, wparam, lparam); | |
478 | |
479 ime_composition_start_ = 0; | |
480 ime_composition_length_ = 0; | |
481 if (ime_discard_composition_) { | |
482 // Call IMM32 functions to retrieve the position and the length of the | |
483 // ongoing composition string and notify the OnAfterPossibleChange() | |
484 // function that it should discard the composition string from a search | |
485 // string. We should not call IMM32 functions in the function because it | |
486 // is called when an IME is not composing a string. | |
487 HIMC imm_context = ImmGetContext(m_hWnd); | |
488 if (imm_context) { | |
489 CHARRANGE selection; | |
490 GetSel(selection); | |
491 const int cursor_position = | |
492 ImmGetCompositionString(imm_context, GCS_CURSORPOS, NULL, 0); | |
493 if (cursor_position >= 0) | |
494 ime_composition_start_ = selection.cpMin - cursor_position; | |
495 | |
496 const int composition_size = | |
497 ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0); | |
498 if (composition_size >= 0) | |
499 ime_composition_length_ = composition_size / sizeof(wchar_t); | |
500 | |
501 ImmReleaseContext(m_hWnd, imm_context); | |
502 } | |
503 } | |
504 | |
505 OnAfterPossibleChange(); | |
506 return result; | |
507 } | |
508 | |
509 LRESULT TextField::Edit::OnImeEndComposition(UINT message, | |
510 WPARAM wparam, | |
511 LPARAM lparam) { | |
512 // Bug 11863: Korean IMEs send a WM_IME_ENDCOMPOSITION message without | |
513 // sending any WM_IME_COMPOSITION messages when a user deletes all | |
514 // composition characters, i.e. a composition string becomes empty. To handle | |
515 // this case, we need to update the find results when a composition is | |
516 // finished or canceled. | |
517 parent_->SyncText(); | |
518 if (parent_->GetController()) | |
519 parent_->GetController()->ContentsChanged(parent_, GetText()); | |
520 return DefWindowProc(message, wparam, lparam); | |
521 } | |
522 | |
523 void TextField::Edit::OnKeyDown(TCHAR key, UINT repeat_count, UINT flags) { | |
524 // NOTE: Annoyingly, ctrl-alt-<key> generates WM_KEYDOWN rather than | |
525 // WM_SYSKEYDOWN, so we need to check (flags & KF_ALTDOWN) in various places | |
526 // in this function even with a WM_SYSKEYDOWN handler. | |
527 | |
528 switch (key) { | |
529 case VK_RETURN: | |
530 // If we are multi-line, we want to let returns through so they start a | |
531 // new line. | |
532 if (parent_->IsMultiLine()) | |
533 break; | |
534 else | |
535 return; | |
536 // Hijacking Editing Commands | |
537 // | |
538 // We hijack the keyboard short-cuts for Cut, Copy, and Paste here so that | |
539 // they go through our clipboard routines. This allows us to be smarter | |
540 // about how we interact with the clipboard and avoid bugs in the | |
541 // CRichEditCtrl. If we didn't hijack here, the edit control would handle | |
542 // these internally with sending the WM_CUT, WM_COPY, or WM_PASTE messages. | |
543 // | |
544 // Cut: Shift-Delete and Ctrl-x are treated as cut. Ctrl-Shift-Delete and | |
545 // Ctrl-Shift-x are not treated as cut even though the underlying | |
546 // CRichTextEdit would treat them as such. | |
547 // Copy: Ctrl-c is treated as copy. Shift-Ctrl-c is not. | |
548 // Paste: Shift-Insert and Ctrl-v are tread as paste. Ctrl-Shift-Insert and | |
549 // Ctrl-Shift-v are not. | |
550 // | |
551 // This behavior matches most, but not all Windows programs, and largely | |
552 // conforms to what users expect. | |
553 | |
554 case VK_DELETE: | |
555 case 'X': | |
556 if ((flags & KF_ALTDOWN) || | |
557 (GetKeyState((key == 'X') ? VK_CONTROL : VK_SHIFT) >= 0)) | |
558 break; | |
559 if (GetKeyState((key == 'X') ? VK_SHIFT : VK_CONTROL) >= 0) { | |
560 ScopedFreeze freeze(this, GetTextObjectModel()); | |
561 OnBeforePossibleChange(); | |
562 Cut(); | |
563 OnAfterPossibleChange(); | |
564 } | |
565 return; | |
566 | |
567 case 'C': | |
568 if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0)) | |
569 break; | |
570 if (GetKeyState(VK_SHIFT) >= 0) | |
571 Copy(); | |
572 return; | |
573 | |
574 case VK_INSERT: | |
575 case 'V': | |
576 if ((flags & KF_ALTDOWN) || | |
577 (GetKeyState((key == 'V') ? VK_CONTROL : VK_SHIFT) >= 0)) | |
578 break; | |
579 if (GetKeyState((key == 'V') ? VK_SHIFT : VK_CONTROL) >= 0) { | |
580 ScopedFreeze freeze(this, GetTextObjectModel()); | |
581 OnBeforePossibleChange(); | |
582 Paste(); | |
583 OnAfterPossibleChange(); | |
584 } | |
585 return; | |
586 | |
587 case 0xbb: // Ctrl-'='. Triggers subscripting, even in plain text mode. | |
588 return; | |
589 | |
590 case VK_PROCESSKEY: | |
591 // This key event is consumed by an IME. | |
592 // We ignore this event because an IME sends WM_IME_COMPOSITION messages | |
593 // when it updates the CRichEditCtrl text. | |
594 return; | |
595 } | |
596 | |
597 // CRichEditCtrl changes its text on WM_KEYDOWN instead of WM_CHAR for many | |
598 // different keys (backspace, ctrl-v, ...), so we call this in both cases. | |
599 HandleKeystroke(GetCurrentMessage()->message, key, repeat_count, flags); | |
600 } | |
601 | |
602 void TextField::Edit::OnLButtonDblClk(UINT keys, const CPoint& point) { | |
603 // Save the double click info for later triple-click detection. | |
604 tracking_double_click_ = true; | |
605 double_click_point_ = point; | |
606 double_click_time_ = GetCurrentMessage()->time; | |
607 | |
608 ScopedFreeze freeze(this, GetTextObjectModel()); | |
609 OnBeforePossibleChange(); | |
610 DefWindowProc(WM_LBUTTONDBLCLK, keys, | |
611 MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); | |
612 OnAfterPossibleChange(); | |
613 } | |
614 | |
615 void TextField::Edit::OnLButtonDown(UINT keys, const CPoint& point) { | |
616 // Check for triple click, then reset tracker. Should be safe to subtract | |
617 // double_click_time_ from the current message's time even if the timer has | |
618 // wrapped in between. | |
619 const bool is_triple_click = tracking_double_click_ && | |
620 win_util::IsDoubleClick(double_click_point_, point, | |
621 GetCurrentMessage()->time - double_click_time_); | |
622 tracking_double_click_ = false; | |
623 | |
624 ScopedFreeze freeze(this, GetTextObjectModel()); | |
625 OnBeforePossibleChange(); | |
626 DefWindowProc(WM_LBUTTONDOWN, keys, | |
627 MAKELPARAM(ClipXCoordToVisibleText(point.x, is_triple_click), | |
628 point.y)); | |
629 OnAfterPossibleChange(); | |
630 } | |
631 | |
632 void TextField::Edit::OnLButtonUp(UINT keys, const CPoint& point) { | |
633 ScopedFreeze freeze(this, GetTextObjectModel()); | |
634 OnBeforePossibleChange(); | |
635 DefWindowProc(WM_LBUTTONUP, keys, | |
636 MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); | |
637 OnAfterPossibleChange(); | |
638 } | |
639 | |
640 void TextField::Edit::OnMouseLeave() { | |
641 SetContainsMouse(false); | |
642 } | |
643 | |
644 LRESULT TextField::Edit::OnMouseWheel(UINT message, | |
645 WPARAM w_param, LPARAM l_param) { | |
646 // Reroute the mouse-wheel to the window under the mouse pointer if | |
647 // applicable. | |
648 if (views::RerouteMouseWheel(m_hWnd, w_param, l_param)) | |
649 return 0; | |
650 return DefWindowProc(message, w_param, l_param);; | |
651 } | |
652 | |
653 void TextField::Edit::OnMouseMove(UINT keys, const CPoint& point) { | |
654 SetContainsMouse(true); | |
655 // Clamp the selection to the visible text so the user can't drag to select | |
656 // the "phantom newline". In theory we could achieve this by clipping the X | |
657 // coordinate, but in practice the edit seems to behave nondeterministically | |
658 // with similar sequences of clipped input coordinates fed to it. Maybe it's | |
659 // reading the mouse cursor position directly? | |
660 // | |
661 // This solution has a minor visual flaw, however: if there's a visible | |
662 // cursor at the edge of the text (only true when there's no selection), | |
663 // dragging the mouse around outside that edge repaints the cursor on every | |
664 // WM_MOUSEMOVE instead of allowing it to blink normally. To fix this, we | |
665 // special-case this exact case and discard the WM_MOUSEMOVE messages instead | |
666 // of passing them along. | |
667 // | |
668 // But even this solution has a flaw! (Argh.) In the case where the user | |
669 // has a selection that starts at the edge of the edit, and proceeds to the | |
670 // middle of the edit, and the user is dragging back past the start edge to | |
671 // remove the selection, there's a redraw problem where the change between | |
672 // having the last few bits of text still selected and having nothing | |
673 // selected can be slow to repaint (which feels noticeably strange). This | |
674 // occurs if you only let the edit receive a single WM_MOUSEMOVE past the | |
675 // edge of the text. I think on each WM_MOUSEMOVE the edit is repainting its | |
676 // previous state, then updating its internal variables to the new state but | |
677 // not repainting. To fix this, we allow one more WM_MOUSEMOVE through after | |
678 // the selection has supposedly been shrunk to nothing; this makes the edit | |
679 // redraw the selection quickly so it feels smooth. | |
680 CHARRANGE selection; | |
681 GetSel(selection); | |
682 const bool possibly_can_discard_mousemove = | |
683 (selection.cpMin == selection.cpMax) && | |
684 (((selection.cpMin == 0) && | |
685 (ClipXCoordToVisibleText(point.x, false) > point.x)) || | |
686 ((selection.cpMin == GetTextLength()) && | |
687 (ClipXCoordToVisibleText(point.x, false) < point.x))); | |
688 if (!can_discard_mousemove_ || !possibly_can_discard_mousemove) { | |
689 can_discard_mousemove_ = possibly_can_discard_mousemove; | |
690 ScopedFreeze freeze(this, GetTextObjectModel()); | |
691 OnBeforePossibleChange(); | |
692 // Force the Y coordinate to the center of the clip rect. The edit | |
693 // behaves strangely when the cursor is dragged vertically: if the cursor | |
694 // is in the middle of the text, drags inside the clip rect do nothing, | |
695 // and drags outside the clip rect act as if the cursor jumped to the | |
696 // left edge of the text. When the cursor is at the right edge, drags of | |
697 // just a few pixels vertically end up selecting the "phantom newline"... | |
698 // sometimes. | |
699 RECT r; | |
700 GetRect(&r); | |
701 DefWindowProc(WM_MOUSEMOVE, keys, | |
702 MAKELPARAM(point.x, (r.bottom - r.top) / 2)); | |
703 OnAfterPossibleChange(); | |
704 } | |
705 } | |
706 | |
707 int TextField::Edit::OnNCCalcSize(BOOL w_param, LPARAM l_param) { | |
708 content_insets_.Set(0, 0, 0, 0); | |
709 parent_->CalculateInsets(&content_insets_); | |
710 if (w_param) { | |
711 NCCALCSIZE_PARAMS* nc_params = | |
712 reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param); | |
713 nc_params->rgrc[0].left += content_insets_.left(); | |
714 nc_params->rgrc[0].right -= content_insets_.right(); | |
715 nc_params->rgrc[0].top += content_insets_.top(); | |
716 nc_params->rgrc[0].bottom -= content_insets_.bottom(); | |
717 } else { | |
718 RECT* rect = reinterpret_cast<RECT*>(l_param); | |
719 rect->left += content_insets_.left(); | |
720 rect->right -= content_insets_.right(); | |
721 rect->top += content_insets_.top(); | |
722 rect->bottom -= content_insets_.bottom(); | |
723 } | |
724 return 0; | |
725 } | |
726 | |
727 void TextField::Edit::OnNCPaint(HRGN region) { | |
728 if (!draw_border_) | |
729 return; | |
730 | |
731 HDC hdc = GetWindowDC(); | |
732 | |
733 CRect window_rect; | |
734 GetWindowRect(&window_rect); | |
735 // Convert to be relative to 0x0. | |
736 window_rect.MoveToXY(0, 0); | |
737 | |
738 ExcludeClipRect(hdc, | |
739 window_rect.left + content_insets_.left(), | |
740 window_rect.top + content_insets_.top(), | |
741 window_rect.right - content_insets_.right(), | |
742 window_rect.bottom - content_insets_.bottom()); | |
743 | |
744 HBRUSH brush = CreateSolidBrush(bg_color_); | |
745 FillRect(hdc, &window_rect, brush); | |
746 DeleteObject(brush); | |
747 | |
748 int part; | |
749 int state; | |
750 | |
751 if (win_util::GetWinVersion() < win_util::WINVERSION_VISTA) { | |
752 part = EP_EDITTEXT; | |
753 | |
754 if (!parent_->IsEnabled()) | |
755 state = ETS_DISABLED; | |
756 else if (parent_->IsReadOnly()) | |
757 state = ETS_READONLY; | |
758 else if (!contains_mouse_) | |
759 state = ETS_NORMAL; | |
760 else | |
761 state = ETS_HOT; | |
762 } else { | |
763 part = EP_EDITBORDER_HVSCROLL; | |
764 | |
765 if (!parent_->IsEnabled()) | |
766 state = EPSHV_DISABLED; | |
767 else if (GetFocus() == m_hWnd) | |
768 state = EPSHV_FOCUSED; | |
769 else if (contains_mouse_) | |
770 state = EPSHV_HOT; | |
771 else | |
772 state = EPSHV_NORMAL; | |
773 // Vista doesn't appear to have a unique state for readonly. | |
774 } | |
775 | |
776 int classic_state = | |
777 (!parent_->IsEnabled() || parent_->IsReadOnly()) ? DFCS_INACTIVE : 0; | |
778 | |
779 NativeTheme::instance()->PaintTextField(hdc, part, state, classic_state, | |
780 &window_rect, bg_color_, false, | |
781 true); | |
782 | |
783 // NOTE: I tried checking the transparent property of the theme and invoking | |
784 // drawParentBackground, but it didn't seem to make a difference. | |
785 | |
786 ReleaseDC(hdc); | |
787 } | |
788 | |
789 void TextField::Edit::OnNonLButtonDown(UINT keys, const CPoint& point) { | |
790 // Interestingly, the edit doesn't seem to cancel triple clicking when the | |
791 // x-buttons (which usually means "thumb buttons") are pressed, so we only | |
792 // call this for M and R down. | |
793 tracking_double_click_ = false; | |
794 SetMsgHandled(false); | |
795 } | |
796 | |
797 void TextField::Edit::OnPaste() { | |
798 if (parent_->IsReadOnly() || !ViewsDelegate::views_delegate) | |
799 return; | |
800 | |
801 Clipboard* clipboard = ViewsDelegate::views_delegate->GetClipboard(); | |
802 | |
803 if (!clipboard->IsFormatAvailable(Clipboard::GetPlainTextWFormatType())) | |
804 return; | |
805 | |
806 std::wstring clipboard_str; | |
807 clipboard->ReadText(&clipboard_str); | |
808 if (!clipboard_str.empty()) { | |
809 std::wstring collapsed(CollapseWhitespace(clipboard_str, false)); | |
810 if (parent_->GetStyle() & STYLE_LOWERCASE) | |
811 collapsed = l10n_util::ToLower(collapsed); | |
812 // Force a Paste operation to trigger OnContentsChanged, even if identical | |
813 // contents are pasted into the text box. | |
814 text_before_change_.clear(); | |
815 ReplaceSel(collapsed.c_str(), true); | |
816 } | |
817 } | |
818 | |
819 void TextField::Edit::OnSysChar(TCHAR ch, UINT repeat_count, UINT flags) { | |
820 // Nearly all alt-<xxx> combos result in beeping rather than doing something | |
821 // useful, so we discard most. Exceptions: | |
822 // * ctrl-alt-<xxx>, which is sometimes important, generates WM_CHAR instead | |
823 // of WM_SYSCHAR, so it doesn't need to be handled here. | |
824 // * alt-space gets translated by the default WM_SYSCHAR handler to a | |
825 // WM_SYSCOMMAND to open the application context menu, so we need to allow | |
826 // it through. | |
827 if (ch == VK_SPACE) | |
828 SetMsgHandled(false); | |
829 } | |
830 | |
831 void TextField::Edit::HandleKeystroke(UINT message, | |
832 TCHAR key, | |
833 UINT repeat_count, | |
834 UINT flags) { | |
835 ScopedFreeze freeze(this, GetTextObjectModel()); | |
836 | |
837 TextField::Controller* controller = parent_->GetController(); | |
838 bool handled = false; | |
839 if (controller) { | |
840 handled = controller->HandleKeystroke(parent_, | |
841 TextField::Keystroke(message, key, repeat_count, flags)); | |
842 } | |
843 | |
844 if (!handled) { | |
845 OnBeforePossibleChange(); | |
846 DefWindowProc(message, key, MAKELPARAM(repeat_count, flags)); | |
847 OnAfterPossibleChange(); | |
848 } | |
849 } | |
850 | |
851 void TextField::Edit::OnBeforePossibleChange() { | |
852 // Record our state. | |
853 text_before_change_ = GetText(); | |
854 } | |
855 | |
856 void TextField::Edit::OnAfterPossibleChange() { | |
857 // Prevent the user from selecting the "phantom newline" at the end of the | |
858 // edit. If they try, we just silently move the end of the selection back to | |
859 // the end of the real text. | |
860 CHARRANGE new_sel; | |
861 GetSel(new_sel); | |
862 const int length = GetTextLength(); | |
863 if (new_sel.cpMax > length) { | |
864 new_sel.cpMax = length; | |
865 if (new_sel.cpMin > length) | |
866 new_sel.cpMin = length; | |
867 SetSel(new_sel); | |
868 } | |
869 | |
870 std::wstring new_text(GetText()); | |
871 if (new_text != text_before_change_) { | |
872 if (ime_discard_composition_ && ime_composition_start_ >= 0 && | |
873 ime_composition_length_ > 0) { | |
874 // A string retrieved with a GetText() call contains a string being | |
875 // composed by an IME. We remove the composition string from this search | |
876 // string. | |
877 new_text.erase(ime_composition_start_, ime_composition_length_); | |
878 ime_composition_start_ = 0; | |
879 ime_composition_length_ = 0; | |
880 if (new_text.empty()) | |
881 return; | |
882 } | |
883 parent_->SyncText(); | |
884 if (parent_->GetController()) | |
885 parent_->GetController()->ContentsChanged(parent_, new_text); | |
886 } | |
887 } | |
888 | |
889 LONG TextField::Edit::ClipXCoordToVisibleText(LONG x, | |
890 bool is_triple_click) const { | |
891 // Clip the X coordinate to the left edge of the text. Careful: | |
892 // PosFromChar(0) may return a negative X coordinate if the beginning of the | |
893 // text has scrolled off the edit, so don't go past the clip rect's edge. | |
894 PARAFORMAT2 pf2; | |
895 GetParaFormat(pf2); | |
896 // Calculation of the clipped coordinate is more complicated if the paragraph | |
897 // layout is RTL layout, or if there is RTL characters inside the LTR layout | |
898 // paragraph. | |
899 bool ltr_text_in_ltr_layout = true; | |
900 if ((pf2.wEffects & PFE_RTLPARA) || | |
901 l10n_util::StringContainsStrongRTLChars(GetText())) { | |
902 ltr_text_in_ltr_layout = false; | |
903 } | |
904 const int length = GetTextLength(); | |
905 RECT r; | |
906 GetRect(&r); | |
907 // The values returned by PosFromChar() seem to refer always | |
908 // to the left edge of the character's bounding box. | |
909 const LONG first_position_x = PosFromChar(0).x; | |
910 LONG min_x = first_position_x; | |
911 if (!ltr_text_in_ltr_layout) { | |
912 for (int i = 1; i < length; ++i) | |
913 min_x = std::min(min_x, PosFromChar(i).x); | |
914 } | |
915 const LONG left_bound = std::max(r.left, min_x); | |
916 | |
917 // PosFromChar(length) is a phantom character past the end of the text. It is | |
918 // not necessarily a right bound; in RTL controls it may be a left bound. So | |
919 // treat it as a right bound only if it is to the right of the first | |
920 // character. | |
921 LONG right_bound = r.right; | |
922 LONG end_position_x = PosFromChar(length).x; | |
923 if (end_position_x >= first_position_x) { | |
924 right_bound = std::min(right_bound, end_position_x); // LTR case. | |
925 } | |
926 // For trailing characters that are 2 pixels wide of less (like "l" in some | |
927 // fonts), we have a problem: | |
928 // * Clicks on any pixel within the character will place the cursor before | |
929 // the character. | |
930 // * Clicks on the pixel just after the character will not allow triple- | |
931 // click to work properly (true for any last character width). | |
932 // So, we move to the last pixel of the character when this is a | |
933 // triple-click, and moving to one past the last pixel in all other | |
934 // scenarios. This way, all clicks that can move the cursor will place it at | |
935 // the end of the text, but triple-click will still work. | |
936 if (x < left_bound) { | |
937 return (is_triple_click && ltr_text_in_ltr_layout) ? left_bound - 1 : | |
938 left_bound; | |
939 } | |
940 if ((length == 0) || (x < right_bound)) | |
941 return x; | |
942 return is_triple_click ? (right_bound - 1) : right_bound; | |
943 } | |
944 | |
945 void TextField::Edit::SetContainsMouse(bool contains_mouse) { | |
946 if (contains_mouse == contains_mouse_) | |
947 return; | |
948 | |
949 contains_mouse_ = contains_mouse; | |
950 | |
951 if (!draw_border_) | |
952 return; | |
953 | |
954 if (contains_mouse_) { | |
955 // Register for notification when the mouse leaves. Need to do this so | |
956 // that we can reset contains mouse properly. | |
957 TRACKMOUSEEVENT tme; | |
958 tme.cbSize = sizeof(tme); | |
959 tme.dwFlags = TME_LEAVE; | |
960 tme.hwndTrack = m_hWnd; | |
961 tme.dwHoverTime = 0; | |
962 TrackMouseEvent(&tme); | |
963 } | |
964 RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_FRAME); | |
965 } | |
966 | |
967 ITextDocument* TextField::Edit::GetTextObjectModel() const { | |
968 if (!text_object_model_) { | |
969 CComPtr<IRichEditOle> ole_interface; | |
970 ole_interface.Attach(GetOleInterface()); | |
971 text_object_model_ = ole_interface; | |
972 } | |
973 return text_object_model_; | |
974 } | |
975 | |
976 ///////////////////////////////////////////////////////////////////////////// | |
977 // TextField | |
978 | |
979 TextField::~TextField() { | |
980 if (edit_) { | |
981 // If the edit hwnd still exists, we need to destroy it explicitly. | |
982 if (*edit_) | |
983 edit_->DestroyWindow(); | |
984 delete edit_; | |
985 } | |
986 } | |
987 | |
988 void TextField::ViewHierarchyChanged(bool is_add, View* parent, View* child) { | |
989 Widget* widget; | |
990 | |
991 if (is_add && (widget = GetWidget())) { | |
992 // This notification is called from the AddChildView call below. Ignore it. | |
993 if (native_view_ && !edit_) | |
994 return; | |
995 | |
996 if (!native_view_) { | |
997 native_view_ = new HWNDView(); // Deleted from our superclass destructor | |
998 AddChildView(native_view_); | |
999 | |
1000 // Maps the focus of the native control to the focus of this view. | |
1001 native_view_->SetAssociatedFocusView(this); | |
1002 } | |
1003 | |
1004 // If edit_ is invalid from a previous use. Reset it. | |
1005 if (edit_ && !IsWindow(edit_->m_hWnd)) { | |
1006 native_view_->Detach(); | |
1007 delete edit_; | |
1008 edit_ = NULL; | |
1009 } | |
1010 | |
1011 if (!edit_) { | |
1012 edit_ = new Edit(this, draw_border_); | |
1013 edit_->SetFont(font_.hfont()); | |
1014 native_view_->Attach(*edit_); | |
1015 if (!text_.empty()) | |
1016 edit_->SetText(text_); | |
1017 UpdateEditBackgroundColor(); | |
1018 Layout(); | |
1019 } | |
1020 } else if (!is_add && edit_ && IsWindow(edit_->m_hWnd)) { | |
1021 edit_->SetParent(NULL); | |
1022 } | |
1023 } | |
1024 | |
1025 void TextField::Layout() { | |
1026 if (native_view_) { | |
1027 native_view_->SetBounds(GetLocalBounds(true)); | |
1028 native_view_->Layout(); | |
1029 } | |
1030 } | |
1031 | |
1032 gfx::Size TextField::GetPreferredSize() { | |
1033 gfx::Insets insets; | |
1034 CalculateInsets(&insets); | |
1035 return gfx::Size(font_.GetExpectedTextWidth(default_width_in_chars_) + | |
1036 insets.width(), | |
1037 num_lines_ * font_.height() + insets.height()); | |
1038 } | |
1039 | |
1040 std::wstring TextField::GetText() const { | |
1041 return text_; | |
1042 } | |
1043 | |
1044 void TextField::SetText(const std::wstring& text) { | |
1045 text_ = text; | |
1046 if (edit_) | |
1047 edit_->SetText(text); | |
1048 } | |
1049 | |
1050 void TextField::AppendText(const std::wstring& text) { | |
1051 text_ += text; | |
1052 if (edit_) | |
1053 edit_->AppendText(text); | |
1054 } | |
1055 | |
1056 void TextField::CalculateInsets(gfx::Insets* insets) { | |
1057 DCHECK(insets); | |
1058 | |
1059 if (!draw_border_) | |
1060 return; | |
1061 | |
1062 // NOTE: One would think GetThemeMargins would return the insets we should | |
1063 // use, but it doesn't. The margins returned by GetThemeMargins are always | |
1064 // 0. | |
1065 | |
1066 // This appears to be the insets used by Windows. | |
1067 insets->Set(3, 3, 3, 3); | |
1068 } | |
1069 | |
1070 void TextField::SyncText() { | |
1071 if (edit_) | |
1072 text_ = edit_->GetText(); | |
1073 } | |
1074 | |
1075 void TextField::SetController(Controller* controller) { | |
1076 controller_ = controller; | |
1077 } | |
1078 | |
1079 TextField::Controller* TextField::GetController() const { | |
1080 return controller_; | |
1081 } | |
1082 | |
1083 bool TextField::IsReadOnly() const { | |
1084 return edit_ ? ((edit_->GetStyle() & ES_READONLY) != 0) : read_only_; | |
1085 } | |
1086 | |
1087 bool TextField::IsPassword() const { | |
1088 return GetStyle() & TextField::STYLE_PASSWORD; | |
1089 } | |
1090 | |
1091 bool TextField::IsMultiLine() const { | |
1092 return (style_ & STYLE_MULTILINE) != 0; | |
1093 } | |
1094 | |
1095 void TextField::SetReadOnly(bool read_only) { | |
1096 read_only_ = read_only; | |
1097 if (edit_) { | |
1098 edit_->SetReadOnly(read_only); | |
1099 UpdateEditBackgroundColor(); | |
1100 } | |
1101 } | |
1102 | |
1103 void TextField::Focus() { | |
1104 ::SetFocus(native_view_->GetHWND()); | |
1105 } | |
1106 | |
1107 void TextField::SelectAll() { | |
1108 if (edit_) | |
1109 edit_->SelectAll(); | |
1110 } | |
1111 | |
1112 void TextField::ClearSelection() const { | |
1113 if (edit_) | |
1114 edit_->ClearSelection(); | |
1115 } | |
1116 | |
1117 HWND TextField::GetNativeComponent() { | |
1118 return native_view_->GetHWND(); | |
1119 } | |
1120 | |
1121 void TextField::SetBackgroundColor(SkColor color) { | |
1122 background_color_ = color; | |
1123 use_default_background_color_ = false; | |
1124 UpdateEditBackgroundColor(); | |
1125 } | |
1126 | |
1127 void TextField::SetDefaultBackgroundColor() { | |
1128 use_default_background_color_ = true; | |
1129 UpdateEditBackgroundColor(); | |
1130 } | |
1131 | |
1132 void TextField::SetFont(const gfx::Font& font) { | |
1133 font_ = font; | |
1134 if (edit_) | |
1135 edit_->SetFont(font.hfont()); | |
1136 } | |
1137 | |
1138 gfx::Font TextField::GetFont() const { | |
1139 return font_; | |
1140 } | |
1141 | |
1142 bool TextField::SetHorizontalMargins(int left, int right) { | |
1143 // SendMessage expects the two values to be packed into one using MAKELONG | |
1144 // so we truncate to 16 bits if necessary. | |
1145 return ERROR_SUCCESS == SendMessage(GetNativeComponent(), | |
1146 (UINT) EM_SETMARGINS, | |
1147 (WPARAM) EC_LEFTMARGIN | EC_RIGHTMARGIN, | |
1148 (LPARAM) MAKELONG(left & 0xFFFF, | |
1149 right & 0xFFFF)); | |
1150 } | |
1151 | |
1152 void TextField::SetHeightInLines(int num_lines) { | |
1153 DCHECK(IsMultiLine()); | |
1154 num_lines_ = num_lines; | |
1155 } | |
1156 | |
1157 void TextField::RemoveBorder() { | |
1158 if (!draw_border_) | |
1159 return; | |
1160 | |
1161 draw_border_ = false; | |
1162 if (edit_) | |
1163 edit_->RemoveBorder(); | |
1164 } | |
1165 | |
1166 void TextField::SetEnabled(bool enabled) { | |
1167 View::SetEnabled(enabled); | |
1168 edit_->SetEnabled(enabled); | |
1169 } | |
1170 | |
1171 bool TextField::IsFocusable() const { | |
1172 return IsEnabled() && !IsReadOnly(); | |
1173 } | |
1174 | |
1175 void TextField::AboutToRequestFocusFromTabTraversal(bool reverse) { | |
1176 SelectAll(); | |
1177 } | |
1178 | |
1179 bool TextField::SkipDefaultKeyEventProcessing(const KeyEvent& e) { | |
1180 // TODO(hamaji): Figure out which keyboard combinations we need to add here, | |
1181 // similar to LocationBarView::SkipDefaultKeyEventProcessing. | |
1182 if (e.GetCharacter() == VK_BACK) | |
1183 return true; // We'll handle BackSpace ourselves. | |
1184 | |
1185 // We don't translate accelerators for ALT + NumPad digit, they are used for | |
1186 // entering special characters. | |
1187 if (e.IsAltDown() && | |
1188 win_util::IsNumPadDigit(e.GetCharacter(), e.IsExtendedKey())) | |
1189 return true; | |
1190 | |
1191 return false; | |
1192 } | |
1193 | |
1194 void TextField::UpdateEditBackgroundColor() { | |
1195 if (!edit_) | |
1196 return; | |
1197 | |
1198 COLORREF bg_color; | |
1199 if (!use_default_background_color_) | |
1200 bg_color = skia::SkColorToCOLORREF(background_color_); | |
1201 else | |
1202 bg_color = GetSysColor(read_only_ ? COLOR_3DFACE : COLOR_WINDOW); | |
1203 edit_->SetBackgroundColor(bg_color); | |
1204 } | |
1205 | |
1206 } // namespace views | |
OLD | NEW |