Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(167)

Side by Side Diff: views/controls/text_field.cc

Issue 115825: Move text_field.cc and rename the class to Textfield in preparation for porti... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Created 11 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « views/controls/text_field.h ('k') | views/controls/textfield/native_textfield_gtk.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« no previous file with comments | « views/controls/text_field.h ('k') | views/controls/textfield/native_textfield_gtk.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698