1 // Copyright (c) 2012 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.
5 #include "ui/views/controls/textfield/native_textfield_win.h"
9 #include "base/i18n/case_conversion.h"
10 #include "base/i18n/rtl.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/win/metro.h"
14 #include "base/win/windows_version.h"
15 #include "grit/ui_strings.h"
16 #include "skia/ext/skia_utils_win.h"
17 #include "ui/base/accessibility/accessible_view_state.h"
18 #include "ui/base/clipboard/clipboard.h"
19 #include "ui/base/clipboard/scoped_clipboard_writer.h"
20 #include "ui/base/ime/win/tsf_bridge.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "ui/base/l10n/l10n_util_win.h"
23 #include "ui/base/win/mouse_wheel_util.h"
24 #include "ui/events/event.h"
25 #include "ui/events/keycodes/keyboard_codes.h"
26 #include "ui/gfx/range/range.h"
27 #include "ui/gfx/win/hwnd_util.h"
28 #include "ui/native_theme/native_theme_win.h"
29 #include "ui/views/controls/label.h"
30 #include "ui/views/controls/menu/menu_item_view.h"
31 #include "ui/views/controls/menu/menu_model_adapter.h"
32 #include "ui/views/controls/menu/menu_runner.h"
33 #include "ui/views/controls/native/native_view_host.h"
34 #include "ui/views/controls/textfield/textfield.h"
35 #include "ui/views/controls/textfield/textfield_controller.h"
36 #include "ui/views/focus/focus_manager.h"
37 #include "ui/views/metrics.h"
38 #include "ui/views/widget/widget.h"
42 ///////////////////////////////////////////////////////////////////////////////
45 NativeTextfieldWin::ScopedFreeze::ScopedFreeze(NativeTextfieldWin* edit,
46 ITextDocument* text_object_model)
48 text_object_model_(text_object_model) {
50 if (text_object_model_) {
52 text_object_model_->Freeze(&count);
56 NativeTextfieldWin::ScopedFreeze::~ScopedFreeze() {
57 // Unfreeze the screen.
58 if (text_object_model_) {
60 text_object_model_->Unfreeze(&count);
62 // We need to UpdateWindow() here instead of InvalidateRect() because, as
63 // far as I can tell, the edit likes to synchronously erase its background
64 // when unfreezing, thus requiring us to synchronously redraw if we don't
66 edit_->UpdateWindow();
71 NativeTextfieldWin::ScopedSuspendUndo::ScopedSuspendUndo(
72 ITextDocument* text_object_model)
73 : text_object_model_(text_object_model) {
74 // Suspend Undo processing.
75 if (text_object_model_)
76 text_object_model_->Undo(tomSuspend, NULL);
79 NativeTextfieldWin::ScopedSuspendUndo::~ScopedSuspendUndo() {
80 // Resume Undo processing.
81 if (text_object_model_)
82 text_object_model_->Undo(tomResume, NULL);
85 ///////////////////////////////////////////////////////////////////////////////
88 HMODULE NativeTextfieldWin::loaded_libarary_module_ = false;
90 NativeTextfieldWin::NativeTextfieldWin(Textfield* textfield)
91 : textfield_(textfield),
92 tracking_double_click_(false),
93 double_click_time_(0),
94 can_discard_mousemove_(false),
95 contains_mouse_(false),
96 ime_discard_composition_(false),
97 ime_composition_start_(0),
98 ime_composition_length_(0),
99 container_view_(new NativeViewHost),
101 tsf_event_router_(base::win::IsTSFAwareRequired() ?
102 new ui::TSFEventRouter(this) : NULL) {
103 if (!loaded_libarary_module_) {
104 // msftedit.dll is RichEdit ver 4.1.
105 // This version is available from WinXP SP1 and has TSF support.
106 loaded_libarary_module_ = LoadLibrary(L"msftedit.dll");
109 DWORD style = kDefaultEditStyle | ES_AUTOHSCROLL;
110 if (textfield_->style() & Textfield::STYLE_OBSCURED)
111 style |= ES_PASSWORD;
113 if (textfield_->read_only())
114 style |= ES_READONLY;
116 // Make sure we apply RTL related extended window styles if necessary.
117 DWORD ex_style = l10n_util::GetExtendedStyles();
119 RECT r = {0, 0, textfield_->width(), textfield_->height()};
120 Create(textfield_->GetWidget()->GetNativeView(), r, NULL, style, ex_style);
122 if (textfield_->style() & Textfield::STYLE_LOWERCASE) {
123 DCHECK((textfield_->style() & Textfield::STYLE_OBSCURED) == 0);
124 SetEditStyle(SES_LOWERCASE, SES_LOWERCASE);
127 // Disable auto font changing. Otherwise, characters can be rendered with
128 // multiple fonts. See http://crbug.com/168480 for details.
129 const LRESULT lang_option = SendMessage(m_hWnd, EM_GETLANGOPTIONS, 0, 0);
130 SendMessage(EM_SETLANGOPTIONS, 0, lang_option & ~IMF_AUTOFONT);
132 // Set up the text_object_model_.
133 base::win::ScopedComPtr<IRichEditOle, &IID_IRichEditOle> ole_interface;
134 ole_interface.Attach(GetOleInterface());
136 text_object_model_.QueryFrom(ole_interface);
138 InitializeAccessibilityInfo();
141 NativeTextfieldWin::~NativeTextfieldWin() {
147 bool NativeTextfieldWin::IsDoubleClick(const POINT& origin,
148 const POINT& current,
149 DWORD elapsed_time) {
150 // The CXDOUBLECLK and CYDOUBLECLK system metrics describe the width and
151 // height of a rectangle around the origin position, inside of which clicks
152 // within the double click time are considered double clicks.
153 return (elapsed_time <= GetDoubleClickTime()) &&
154 (abs(current.x - origin.x) <= (GetSystemMetrics(SM_CXDOUBLECLK) / 2)) &&
155 (abs(current.y - origin.y) <= (GetSystemMetrics(SM_CYDOUBLECLK) / 2));
158 void NativeTextfieldWin::AttachHack() {
159 // See the code in textfield.cc that calls this for why this is here.
160 container_view_->set_focus_view(textfield_);
161 container_view_->Attach(m_hWnd);
164 ////////////////////////////////////////////////////////////////////////////////
165 // NativeTextfieldWin, NativeTextfieldWrapper implementation:
167 string16 NativeTextfieldWin::GetText() const {
168 int len = GetTextLength() + 1;
171 GetWindowText(WriteInto(&str, len), len);
172 // The text get from GetWindowText() might be wrapped with explicit bidi
173 // control characters. Refer to UpdateText() for detail. Without such
174 // wrapping, in RTL chrome, a pure LTR string ending with parenthesis will
175 // not be displayed correctly in a textfield. For example, "Yahoo!" will be
176 // displayed as "!Yahoo", and "Google (by default)" will be displayed as
177 // "(Google (by default".
178 return base::i18n::StripWrappingBidiControlCharacters(str);
181 void NativeTextfieldWin::UpdateText() {
182 string16 text = textfield_->text();
183 // Adjusting the string direction before setting the text in order to make
184 // sure both RTL and LTR strings are displayed properly.
185 base::i18n::AdjustStringForLocaleDirection(&text);
186 if (textfield_->style() & Textfield::STYLE_LOWERCASE)
187 text = base::i18n::ToLower(text);
188 SetWindowText(text.c_str());
189 UpdateAccessibleValue(text);
192 void NativeTextfieldWin::AppendText(const string16& text) {
193 int text_length = GetWindowTextLength();
194 ::SendMessage(m_hWnd, TBM_SETSEL, true, MAKELPARAM(text_length, text_length));
195 ::SendMessage(m_hWnd, EM_REPLACESEL, false,
196 reinterpret_cast<LPARAM>(text.c_str()));
199 void NativeTextfieldWin::InsertOrReplaceText(const string16& text) {
200 // Currently not needed.
204 base::i18n::TextDirection NativeTextfieldWin::GetTextDirection() const {
206 return base::i18n::UNKNOWN_DIRECTION;
209 string16 NativeTextfieldWin::GetSelectedText() const {
213 if (sel.cpMin != sel.cpMax)
214 GetSelText(WriteInto(&str, sel.cpMax - sel.cpMin + 1));
218 void NativeTextfieldWin::SelectAll(bool reversed) {
220 SetSel(GetTextLength(), 0);
222 SetSel(0, GetTextLength());
225 void NativeTextfieldWin::ClearSelection() {
226 SetSel(GetTextLength(), GetTextLength());
229 void NativeTextfieldWin::UpdateBorder() {
230 SetWindowPos(NULL, 0, 0, 0, 0,
231 SWP_NOMOVE | SWP_FRAMECHANGED | SWP_NOACTIVATE |
232 SWP_NOOWNERZORDER | SWP_NOSIZE);
235 void NativeTextfieldWin::UpdateTextColor() {
237 cf.dwMask = CFM_COLOR;
238 cf.crTextColor = skia::SkColorToCOLORREF(textfield_->GetTextColor());
239 CRichEditCtrl::SetDefaultCharFormat(cf);
242 void NativeTextfieldWin::UpdateBackgroundColor() {
243 CRichEditCtrl::SetBackgroundColor(
244 skia::SkColorToCOLORREF(textfield_->GetBackgroundColor()));
247 void NativeTextfieldWin::UpdateReadOnly() {
248 SendMessage(m_hWnd, EM_SETREADONLY, textfield_->read_only(), 0);
249 UpdateAccessibleState(STATE_SYSTEM_READONLY, textfield_->read_only());
252 void NativeTextfieldWin::UpdateFont() {
253 SendMessage(m_hWnd, WM_SETFONT,
254 reinterpret_cast<WPARAM>(
255 textfield_->GetPrimaryFont().GetNativeFont()),
257 // Setting the font blows away any text color we've set, so reset it.
261 void NativeTextfieldWin::UpdateIsObscured() {
262 // TODO: Need to implement for Windows.
263 UpdateAccessibleState(STATE_SYSTEM_PROTECTED, textfield_->IsObscured());
266 void NativeTextfieldWin::UpdateEnabled() {
267 SendMessage(m_hWnd, WM_ENABLE, textfield_->enabled(), 0);
268 UpdateAccessibleState(STATE_SYSTEM_UNAVAILABLE, !textfield_->enabled());
271 gfx::Insets NativeTextfieldWin::CalculateInsets() {
272 // NOTE: One would think GetThemeMargins would return the insets we should
273 // use, but it doesn't. The margins returned by GetThemeMargins are always
276 // This appears to be the insets used by Windows.
277 return gfx::Insets(3, 3, 3, 3);
280 void NativeTextfieldWin::UpdateHorizontalMargins() {
282 if (!textfield_->GetHorizontalMargins(&left, &right))
285 // SendMessage expects the two values to be packed into one using MAKELONG
286 // so we truncate to 16 bits if necessary.
287 SendMessage(m_hWnd, EM_SETMARGINS,
288 EC_LEFTMARGIN | EC_RIGHTMARGIN,
289 MAKELONG(left & 0xFFFF, right & 0xFFFF));
292 void NativeTextfieldWin::UpdateVerticalMargins() {
294 if (!textfield_->GetVerticalMargins(&top, &bottom))
297 if (top == 0 && bottom == 0) {
298 // Do nothing, default margins are 0 already.
301 // Non-zero margins case.
305 bool NativeTextfieldWin::SetFocus() {
306 // Focus the associated HWND.
307 //container_view_->Focus();
312 View* NativeTextfieldWin::GetView() {
313 return container_view_;
316 gfx::NativeView NativeTextfieldWin::GetTestingHandle() const {
320 bool NativeTextfieldWin::IsIMEComposing() const {
321 // Retrieve the length of the composition string to check if an IME is
322 // composing text. (If this length is > 0 then an IME is being used to compose
324 if (base::win::IsTSFAwareRequired())
325 return tsf_event_router_->IsImeComposing();
327 HIMC imm_context = ImmGetContext(m_hWnd);
331 const int composition_size = ImmGetCompositionString(imm_context, GCS_COMPSTR,
333 ImmReleaseContext(m_hWnd, imm_context);
334 return composition_size > 0;
337 gfx::Range NativeTextfieldWin::GetSelectedRange() const {
338 // TODO(tommi): Implement.
343 void NativeTextfieldWin::SelectRange(const gfx::Range& range) {
344 // TODO(tommi): Implement.
348 gfx::SelectionModel NativeTextfieldWin::GetSelectionModel() const {
349 // TODO(tommi): Implement.
351 return gfx::SelectionModel();
354 void NativeTextfieldWin::SelectSelectionModel(const gfx::SelectionModel& sel) {
355 // TODO(tommi): Implement.
359 size_t NativeTextfieldWin::GetCursorPosition() const {
360 // TODO(tommi): Implement.
365 bool NativeTextfieldWin::GetCursorEnabled() const {
366 // TODO(msw): Implement.
371 void NativeTextfieldWin::SetCursorEnabled(bool enabled) {
372 // TODO(msw): Implement.
376 bool NativeTextfieldWin::HandleKeyPressed(const ui::KeyEvent& event) {
380 bool NativeTextfieldWin::HandleKeyReleased(const ui::KeyEvent& event) {
384 void NativeTextfieldWin::HandleFocus() {
387 void NativeTextfieldWin::HandleBlur() {
390 ui::TextInputClient* NativeTextfieldWin::GetTextInputClient() {
394 void NativeTextfieldWin::SetColor(SkColor value) {
398 void NativeTextfieldWin::ApplyColor(SkColor value, const gfx::Range& range) {
402 void NativeTextfieldWin::SetStyle(gfx::TextStyle style, bool value) {
406 void NativeTextfieldWin::ApplyStyle(gfx::TextStyle style,
408 const gfx::Range& range) {
412 void NativeTextfieldWin::ClearEditHistory() {
416 int NativeTextfieldWin::GetFontHeight() {
417 return textfield_->font_list().GetHeight();
420 int NativeTextfieldWin::GetTextfieldBaseline() const {
421 return textfield_->font_list().GetBaseline();
424 int NativeTextfieldWin::GetWidthNeededForText() const {
429 void NativeTextfieldWin::ExecuteTextCommand(int command_id) {
430 ExecuteCommand(command_id, 0);
433 bool NativeTextfieldWin::HasTextBeingDragged() {
438 gfx::Point NativeTextfieldWin::GetContextMenuLocation() {
443 ////////////////////////////////////////////////////////////////////////////////
444 // NativeTextfieldWin, ui::SimpleMenuModel::Delegate implementation:
446 bool NativeTextfieldWin::IsCommandIdChecked(int command_id) const {
450 bool NativeTextfieldWin::IsCommandIdEnabled(int command_id) const {
451 switch (command_id) {
452 case IDS_APP_UNDO: return !textfield_->read_only() && !!CanUndo();
453 case IDS_APP_CUT: return !textfield_->read_only() &&
454 !textfield_->IsObscured() && !!CanCut();
455 case IDS_APP_COPY: return !!CanCopy() && !textfield_->IsObscured();
456 case IDS_APP_PASTE: return !textfield_->read_only() && !!CanPaste();
457 case IDS_APP_SELECT_ALL: return !!CanSelectAll();
458 default: NOTREACHED();
463 bool NativeTextfieldWin::GetAcceleratorForCommandId(int command_id,
464 ui::Accelerator* accelerator) {
465 // The standard Ctrl-X, Ctrl-V and Ctrl-C are not defined as accelerators
466 // anywhere so we need to check for them explicitly here.
467 switch (command_id) {
469 *accelerator = ui::Accelerator(ui::VKEY_X, ui::EF_CONTROL_DOWN);
472 *accelerator = ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN);
475 *accelerator = ui::Accelerator(ui::VKEY_V, ui::EF_CONTROL_DOWN);
478 return container_view_->GetWidget()->GetAccelerator(command_id, accelerator);
481 void NativeTextfieldWin::ExecuteCommand(int command_id, int event_flags) {
482 ScopedFreeze freeze(this, GetTextObjectModel());
483 OnBeforePossibleChange();
484 switch (command_id) {
485 case IDS_APP_UNDO: Undo(); break;
486 case IDS_APP_CUT: Cut(); break;
487 case IDS_APP_COPY: Copy(); break;
488 case IDS_APP_PASTE: Paste(); break;
489 case IDS_APP_SELECT_ALL: SelectAll(false); break;
490 default: NOTREACHED(); break;
492 OnAfterPossibleChange(true);
495 void NativeTextfieldWin::OnTextUpdated(const gfx::Range& composition_range) {
496 if (ime_discard_composition_) {
497 ime_composition_start_ = composition_range.start();
498 ime_composition_length_ = composition_range.length();
500 ime_composition_start_ = 0;
501 ime_composition_length_ = 0;
503 OnAfterPossibleChange(false);
504 text_before_change_.clear();
507 void NativeTextfieldWin::OnImeStartCompositionInternal() {
508 // Users may press alt+shift or control+shift keys to change their keyboard
509 // layouts. So, we retrieve the input locale identifier everytime we start
510 // an IME composition.
511 int language_id = PRIMARYLANGID(GetKeyboardLayout(0));
512 ime_discard_composition_ =
513 language_id == LANG_JAPANESE || language_id == LANG_CHINESE;
514 ime_composition_start_ = 0;
515 ime_composition_length_ = 0;
518 void NativeTextfieldWin::OnImeEndCompositionInternal() {
519 // Bug 11863: Korean IMEs send a WM_IME_ENDCOMPOSITION message without
520 // sending any WM_IME_COMPOSITION messages when a user deletes all
521 // composition characters, i.e. a composition string becomes empty. To handle
522 // this case, we need to update the find results when a composition is
523 // finished or canceled.
524 textfield_->SyncText();
527 void NativeTextfieldWin::OnTSFStartComposition() {
528 OnImeStartCompositionInternal();
531 void NativeTextfieldWin::OnTSFEndComposition() {
532 OnImeEndCompositionInternal();
535 void NativeTextfieldWin::InitializeAccessibilityInfo() {
536 // Set the accessible state.
537 accessibility_state_ = 0;
539 base::win::ScopedComPtr<IAccPropServices> pAccPropServices;
540 HRESULT hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER,
541 IID_IAccPropServices, reinterpret_cast<void**>(&pAccPropServices));
547 // Set the accessible role.
549 var.lVal = ROLE_SYSTEM_TEXT;
550 hr = pAccPropServices->SetHwndProp(m_hWnd, OBJID_CLIENT,
551 CHILDID_SELF, PROPID_ACC_ROLE, var);
553 // Set the accessible name by getting the label text.
554 View* parent = textfield_->parent();
555 int label_index = parent->GetIndexOf(textfield_) - 1;
556 if (label_index >= 0) {
557 // Try to find the name of this text field.
558 // We expect it to be a Label preceeding this view (if it exists).
560 View* label_view = parent->child_at(label_index);
561 if (!strcmp(label_view->GetClassName(), Label::kViewClassName)) {
562 ui::AccessibleViewState state;
563 label_view->GetAccessibleState(&state);
564 hr = pAccPropServices->SetHwndPropStr(m_hWnd, OBJID_CLIENT,
565 CHILDID_SELF, PROPID_ACC_NAME, state.name.c_str());
570 void NativeTextfieldWin::UpdateAccessibleState(uint32 state_flag,
572 base::win::ScopedComPtr<IAccPropServices> pAccPropServices;
573 HRESULT hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER,
574 IID_IAccPropServices, reinterpret_cast<void**>(&pAccPropServices));
580 var.lVal = set_value ? accessibility_state_ | state_flag
581 : accessibility_state_ & ~state_flag;
582 hr = pAccPropServices->SetHwndProp(m_hWnd, OBJID_CLIENT,
583 CHILDID_SELF, PROPID_ACC_STATE, var);
585 ::NotifyWinEvent(EVENT_OBJECT_STATECHANGE, m_hWnd, OBJID_CLIENT,
589 void NativeTextfieldWin::UpdateAccessibleValue(const string16& value) {
590 base::win::ScopedComPtr<IAccPropServices> pAccPropServices;
591 HRESULT hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER,
592 IID_IAccPropServices, reinterpret_cast<void**>(&pAccPropServices));
596 hr = pAccPropServices->SetHwndPropStr(m_hWnd, OBJID_CLIENT,
597 CHILDID_SELF, PROPID_ACC_VALUE, value.c_str());
599 ::NotifyWinEvent(EVENT_OBJECT_VALUECHANGE, m_hWnd, OBJID_CLIENT,
603 ////////////////////////////////////////////////////////////////////////////////
604 // NativeTextfieldWin, private:
606 void NativeTextfieldWin::OnChar(TCHAR ch, UINT repeat_count, UINT flags) {
610 void NativeTextfieldWin::OnContextMenu(HWND window, const POINT& point) {
612 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
613 if (point.x == -1 || point.y == -1) {
614 source_type = ui::MENU_SOURCE_KEYBOARD;
616 MapWindowPoints(HWND_DESKTOP, &p, 1);
620 MenuModelAdapter adapter(context_menu_contents_.get());
621 context_menu_runner_.reset(new MenuRunner(adapter.CreateMenu()));
623 ignore_result(context_menu_runner_->RunMenuAt(textfield_->GetWidget(), NULL,
624 gfx::Rect(gfx::Point(p), gfx::Size()), MenuItemView::TOPLEFT,
625 source_type, MenuRunner::HAS_MNEMONICS));
628 void NativeTextfieldWin::OnCopy() {
629 if (textfield_->IsObscured())
632 const string16 text(GetSelectedText());
634 ui::ScopedClipboardWriter(
635 ui::Clipboard::GetForCurrentThread(),
636 ui::CLIPBOARD_TYPE_COPY_PASTE).WriteText(text);
637 if (TextfieldController* controller = textfield_->GetController())
638 controller->OnAfterCutOrCopy();
642 LRESULT NativeTextfieldWin::OnCreate(const CREATESTRUCTW* /*create_struct*/) {
643 if (base::win::IsTSFAwareRequired()) {
644 // Enable TSF support of RichEdit.
645 SetEditStyle(SES_USECTF, SES_USECTF);
647 // When TSF is enabled, OnTextUpdated() may be called without any previous
648 // call that would have indicated the start of an editing session. In order
649 // to guarantee we've always called OnBeforePossibleChange() before
650 // OnAfterPossibleChange(), we therefore call that here. Note that multiple
651 // (i.e. unmatched) calls to this function in a row are safe.
652 OnBeforePossibleChange();
654 SetMsgHandled(FALSE);
658 void NativeTextfieldWin::OnCut() {
659 if (textfield_->read_only() || textfield_->IsObscured())
664 // This replace selection will have no effect (even on the undo stack) if the
665 // current selection is empty.
666 ReplaceSel(L"", true);
669 LRESULT NativeTextfieldWin::OnGetObject(UINT message,
673 if (lparam == OBJID_CLIENT) {
674 ret = LresultFromObject(IID_IAccessible, wparam,
675 textfield_->GetNativeViewAccessible());
680 LRESULT NativeTextfieldWin::OnImeChar(UINT message,
683 // http://crbug.com/7707: a rich-edit control may crash when it receives a
684 // WM_IME_CHAR message while it is processing a WM_IME_COMPOSITION message.
685 // Since view controls don't need WM_IME_CHAR messages, we prevent WM_IME_CHAR
686 // messages from being dispatched to view controls via the CallWindowProc()
691 LRESULT NativeTextfieldWin::OnImeStartComposition(UINT message,
694 OnImeStartCompositionInternal();
695 return DefWindowProc(message, wparam, lparam);
698 LRESULT NativeTextfieldWin::OnImeComposition(UINT message,
701 text_before_change_.clear();
702 LRESULT result = DefWindowProc(message, wparam, lparam);
704 ime_composition_start_ = 0;
705 ime_composition_length_ = 0;
706 if (ime_discard_composition_) {
707 // Call IMM32 functions to retrieve the position and the length of the
708 // ongoing composition string and notify the OnAfterPossibleChange()
709 // function that it should discard the composition string from a search
710 // string. We should not call IMM32 functions in the function because it
711 // is called when an IME is not composing a string.
712 HIMC imm_context = ImmGetContext(m_hWnd);
716 const int cursor_position =
717 ImmGetCompositionString(imm_context, GCS_CURSORPOS, NULL, 0);
718 if (cursor_position >= 0)
719 ime_composition_start_ = selection.cpMin - cursor_position;
721 const int composition_size =
722 ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0);
723 if (composition_size >= 0)
724 ime_composition_length_ = composition_size / sizeof(wchar_t);
726 ImmReleaseContext(m_hWnd, imm_context);
730 // If we allow OnAfterPossibleChange() to redraw the text, it will do this by
731 // setting the edit's text directly, which can cancel the current IME
732 // composition or cause other adverse affects. So we set |should_redraw_text|
734 OnAfterPossibleChange(false);
738 LRESULT NativeTextfieldWin::OnImeEndComposition(UINT message,
741 OnImeEndCompositionInternal();
742 return DefWindowProc(message, wparam, lparam);
745 LRESULT NativeTextfieldWin::OnPointerDown(UINT message, WPARAM wparam,
748 SetMsgHandled(FALSE);
752 void NativeTextfieldWin::OnKeyDown(TCHAR key, UINT repeat_count, UINT flags) {
753 // NOTE: Annoyingly, ctrl-alt-<key> generates WM_KEYDOWN rather than
754 // WM_SYSKEYDOWN, so we need to check (flags & KF_ALTDOWN) in various places
755 // in this function even with a WM_SYSKEYDOWN handler.
763 // Hijacking Editing Commands
765 // We hijack the keyboard short-cuts for Cut, Copy, and Paste here so that
766 // they go through our clipboard routines. This allows us to be smarter
767 // about how we interact with the clipboard and avoid bugs in the
768 // CRichEditCtrl. If we didn't hijack here, the edit control would handle
769 // these internally with sending the WM_CUT, WM_COPY, or WM_PASTE messages.
771 // Cut: Shift-Delete and Ctrl-x are treated as cut. Ctrl-Shift-Delete and
772 // Ctrl-Shift-x are not treated as cut even though the underlying
773 // CRichTextEdit would treat them as such.
774 // Copy: Ctrl-c is treated as copy. Shift-Ctrl-c is not.
775 // Paste: Shift-Insert and Ctrl-v are tread as paste. Ctrl-Shift-Insert and
776 // Ctrl-Shift-v are not.
778 // This behavior matches most, but not all Windows programs, and largely
779 // conforms to what users expect.
783 if ((flags & KF_ALTDOWN) ||
784 (GetKeyState((key == 'X') ? VK_CONTROL : VK_SHIFT) >= 0))
786 if (GetKeyState((key == 'X') ? VK_SHIFT : VK_CONTROL) >= 0) {
787 ScopedFreeze freeze(this, GetTextObjectModel());
788 OnBeforePossibleChange();
790 OnAfterPossibleChange(true);
795 if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0))
797 if (GetKeyState(VK_SHIFT) >= 0)
802 // Ignore insert by itself, so we don't turn overtype mode on/off.
803 if (!(flags & KF_ALTDOWN) && (GetKeyState(VK_SHIFT) >= 0) &&
804 (GetKeyState(VK_CONTROL) >= 0))
806 // Fall through to the next case (ie. Shift-Insert == Ctrl-V).
808 if ((flags & KF_ALTDOWN) ||
809 (GetKeyState((key == 'V') ? VK_CONTROL : VK_SHIFT) >= 0))
811 if (GetKeyState((key == 'V') ? VK_SHIFT : VK_CONTROL) >= 0) {
812 ScopedFreeze freeze(this, GetTextObjectModel());
813 OnBeforePossibleChange();
815 OnAfterPossibleChange(true);
819 case 0xbb: // Ctrl-'='. Triggers subscripting, even in plain text mode.
820 // We don't use VK_OEM_PLUS in case the macro isn't defined.
821 // (e.g., we don't have this symbol in embeded environment).
825 // This key event is consumed by an IME.
826 // We ignore this event because an IME sends WM_IME_COMPOSITION messages
827 // when it updates the CRichEditCtrl text.
831 // CRichEditCtrl changes its text on WM_KEYDOWN instead of WM_CHAR for many
832 // different keys (backspace, ctrl-v, ...), so we call this in both cases.
836 void NativeTextfieldWin::OnLButtonDblClk(UINT keys, const CPoint& point) {
837 // Save the double click info for later triple-click detection.
838 tracking_double_click_ = true;
839 double_click_point_ = point;
840 double_click_time_ = GetCurrentMessage()->time;
842 if (!ShouldProcessMouseEvent())
845 ScopedFreeze freeze(this, GetTextObjectModel());
846 OnBeforePossibleChange();
847 DefWindowProc(WM_LBUTTONDBLCLK, keys,
848 MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y));
849 OnAfterPossibleChange(true);
852 void NativeTextfieldWin::OnLButtonDown(UINT keys, const CPoint& point) {
853 // Check for triple click, then reset tracker. Should be safe to subtract
854 // double_click_time_ from the current message's time even if the timer has
855 // wrapped in between.
856 const bool is_triple_click = tracking_double_click_ &&
857 IsDoubleClick(double_click_point_, point,
858 GetCurrentMessage()->time - double_click_time_);
859 tracking_double_click_ = false;
861 if (!ShouldProcessMouseEvent())
864 ScopedFreeze freeze(this, GetTextObjectModel());
865 OnBeforePossibleChange();
866 DefWindowProc(WM_LBUTTONDOWN, keys,
867 MAKELPARAM(ClipXCoordToVisibleText(point.x, is_triple_click),
869 OnAfterPossibleChange(true);
872 void NativeTextfieldWin::OnLButtonUp(UINT keys, const CPoint& point) {
873 ScopedFreeze freeze(this, GetTextObjectModel());
874 OnBeforePossibleChange();
875 DefWindowProc(WM_LBUTTONUP, keys,
876 MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y));
877 OnAfterPossibleChange(true);
880 void NativeTextfieldWin::OnMouseLeave() {
881 SetContainsMouse(false);
884 LRESULT NativeTextfieldWin::OnMouseWheel(UINT message,
887 // Reroute the mouse-wheel to the window under the mouse pointer if
889 if (ui::RerouteMouseWheel(m_hWnd, w_param, l_param))
891 return DefWindowProc(message, w_param, l_param);
894 void NativeTextfieldWin::OnMouseMove(UINT keys, const CPoint& point) {
895 SetContainsMouse(true);
896 // Clamp the selection to the visible text so the user can't drag to select
897 // the "phantom newline". In theory we could achieve this by clipping the X
898 // coordinate, but in practice the edit seems to behave nondeterministically
899 // with similar sequences of clipped input coordinates fed to it. Maybe it's
900 // reading the mouse cursor position directly?
902 // This solution has a minor visual flaw, however: if there's a visible
903 // cursor at the edge of the text (only true when there's no selection),
904 // dragging the mouse around outside that edge repaints the cursor on every
905 // WM_MOUSEMOVE instead of allowing it to blink normally. To fix this, we
906 // special-case this exact case and discard the WM_MOUSEMOVE messages instead
907 // of passing them along.
909 // But even this solution has a flaw! (Argh.) In the case where the user
910 // has a selection that starts at the edge of the edit, and proceeds to the
911 // middle of the edit, and the user is dragging back past the start edge to
912 // remove the selection, there's a redraw problem where the change between
913 // having the last few bits of text still selected and having nothing
914 // selected can be slow to repaint (which feels noticeably strange). This
915 // occurs if you only let the edit receive a single WM_MOUSEMOVE past the
916 // edge of the text. I think on each WM_MOUSEMOVE the edit is repainting its
917 // previous state, then updating its internal variables to the new state but
918 // not repainting. To fix this, we allow one more WM_MOUSEMOVE through after
919 // the selection has supposedly been shrunk to nothing; this makes the edit
920 // redraw the selection quickly so it feels smooth.
923 const bool possibly_can_discard_mousemove =
924 (selection.cpMin == selection.cpMax) &&
925 (((selection.cpMin == 0) &&
926 (ClipXCoordToVisibleText(point.x, false) > point.x)) ||
927 ((selection.cpMin == GetTextLength()) &&
928 (ClipXCoordToVisibleText(point.x, false) < point.x)));
929 if (!can_discard_mousemove_ || !possibly_can_discard_mousemove) {
930 can_discard_mousemove_ = possibly_can_discard_mousemove;
931 ScopedFreeze freeze(this, GetTextObjectModel());
932 OnBeforePossibleChange();
933 // Force the Y coordinate to the center of the clip rect. The edit
934 // behaves strangely when the cursor is dragged vertically: if the cursor
935 // is in the middle of the text, drags inside the clip rect do nothing,
936 // and drags outside the clip rect act as if the cursor jumped to the
937 // left edge of the text. When the cursor is at the right edge, drags of
938 // just a few pixels vertically end up selecting the "phantom newline"...
942 DefWindowProc(WM_MOUSEMOVE, keys,
943 MAKELPARAM(point.x, (r.bottom - r.top) / 2));
944 OnAfterPossibleChange(true);
948 int NativeTextfieldWin::OnNCCalcSize(BOOL w_param, LPARAM l_param) {
949 content_insets_.Set(0, 0, 0, 0);
950 if (textfield_->draw_border())
951 content_insets_ = CalculateInsets();
953 NCCALCSIZE_PARAMS* nc_params =
954 reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param);
955 nc_params->rgrc[0].left += content_insets_.left();
956 nc_params->rgrc[0].right -= content_insets_.right();
957 nc_params->rgrc[0].top += content_insets_.top();
958 nc_params->rgrc[0].bottom -= content_insets_.bottom();
960 RECT* rect = reinterpret_cast<RECT*>(l_param);
961 rect->left += content_insets_.left();
962 rect->right -= content_insets_.right();
963 rect->top += content_insets_.top();
964 rect->bottom -= content_insets_.bottom();
969 void NativeTextfieldWin::OnNCPaint(HRGN region) {
970 if (!textfield_->draw_border())
973 HDC hdc = GetWindowDC();
976 GetWindowRect(&window_rect);
977 // Convert to be relative to 0x0.
978 window_rect.MoveToXY(0, 0);
981 window_rect.left + content_insets_.left(),
982 window_rect.top + content_insets_.top(),
983 window_rect.right - content_insets_.right(),
984 window_rect.bottom - content_insets_.bottom());
986 HBRUSH brush = CreateSolidBrush(bg_color_);
987 FillRect(hdc, &window_rect, brush);
993 if (base::win::GetVersion() < base::win::VERSION_VISTA) {
996 if (!textfield_->enabled())
997 state = ETS_DISABLED;
998 else if (textfield_->read_only())
999 state = ETS_READONLY;
1000 else if (!contains_mouse_)
1005 part = EP_EDITBORDER_HVSCROLL;
1007 if (!textfield_->enabled())
1008 state = EPSHV_DISABLED;
1009 else if (GetFocus() == m_hWnd)
1010 state = EPSHV_FOCUSED;
1011 else if (contains_mouse_)
1014 state = EPSHV_NORMAL;
1015 // Vista doesn't appear to have a unique state for readonly.
1019 (!textfield_->enabled() || textfield_->read_only()) ? DFCS_INACTIVE : 0;
1021 ui::NativeThemeWin::instance()->PaintTextField(hdc, part, state,
1022 classic_state, &window_rect,
1023 bg_color_, false, true);
1025 // NOTE: I tried checking the transparent property of the theme and invoking
1026 // drawParentBackground, but it didn't seem to make a difference.
1031 void NativeTextfieldWin::OnNonLButtonDown(UINT keys, const CPoint& point) {
1032 // Interestingly, the edit doesn't seem to cancel triple clicking when the
1033 // x-buttons (which usually means "thumb buttons") are pressed, so we only
1034 // call this for M and R down.
1035 tracking_double_click_ = false;
1037 if (!ShouldProcessMouseEvent())
1040 SetMsgHandled(false);
1043 void NativeTextfieldWin::OnPaste() {
1044 if (textfield_->read_only())
1047 ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
1048 if (!clipboard->IsFormatAvailable(ui::Clipboard::GetPlainTextWFormatType(),
1049 ui::CLIPBOARD_TYPE_COPY_PASTE))
1052 string16 clipboard_str;
1053 clipboard->ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE, &clipboard_str);
1054 if (!clipboard_str.empty()) {
1055 string16 collapsed(CollapseWhitespace(clipboard_str, false));
1056 if (textfield_->style() & Textfield::STYLE_LOWERCASE)
1057 collapsed = base::i18n::ToLower(collapsed);
1058 // Force a Paste operation to trigger ContentsChanged, even if identical
1059 // contents are pasted into the text box. See http://crbug.com/79002
1060 ReplaceSel(L"", false);
1061 textfield_->SyncText();
1062 text_before_change_.clear();
1063 ReplaceSel(collapsed.c_str(), true);
1064 if (TextfieldController* controller = textfield_->GetController())
1065 controller->OnAfterPaste();
1069 void NativeTextfieldWin::OnSetFocus(HWND hwnd) {
1070 SetMsgHandled(FALSE); // We still want the default processing of the message.
1072 views::FocusManager* focus_manager = textfield_->GetFocusManager();
1073 if (!focus_manager) {
1077 focus_manager->SetFocusedView(textfield_);
1079 if (!base::win::IsTSFAwareRequired()) {
1085 // Document manager created by RichEdit can be obtained only after
1086 // WM_SET_FOCUS event is handled.
1087 tsf_event_router_->SetManager(
1088 ui::TSFBridge::GetInstance()->GetThreadManager());
1089 SetMsgHandled(TRUE);
1092 void NativeTextfieldWin::OnKillFocus(HWND hwnd) {
1093 if(tsf_event_router_)
1094 tsf_event_router_->SetManager(NULL);
1095 SetMsgHandled(FALSE);
1098 void NativeTextfieldWin::OnSysChar(TCHAR ch, UINT repeat_count, UINT flags) {
1099 DCHECK(flags & KF_ALTDOWN);
1100 // Explicitly show the system menu at a good location on [Alt]+[Space].
1101 // Nearly all other [Alt]+<xxx> combos result in beeping rather than doing
1102 // something useful, so discard those. Note that [Ctrl]+[Alt]+<xxx> generates
1103 // WM_CHAR instead of WM_SYSCHAR, so it is not handled here.
1104 if (ch == VK_SPACE) {
1105 gfx::ShowSystemMenu(
1106 container_view_->GetWidget()->GetTopLevelWidget()->GetNativeWindow());
1110 void NativeTextfieldWin::OnFinalMessage(HWND hwnd) {
1114 void NativeTextfieldWin::HandleKeystroke() {
1115 const MSG* msg = GetCurrentMessage();
1116 ui::KeyEvent event(*msg, msg->message == WM_CHAR);
1117 ScopedFreeze freeze(this, GetTextObjectModel());
1119 TextfieldController* controller = textfield_->GetController();
1120 if (!controller || !controller->HandleKeyEvent(textfield_, event)) {
1121 OnBeforePossibleChange();
1123 if (msg->wParam == ui::VKEY_HOME || msg->wParam == ui::VKEY_END) {
1124 // DefWindowProc() might reset the keyboard layout when it receives a
1125 // keydown event for VKEY_HOME or VKEY_END. When the window was created
1126 // with WS_EX_LAYOUTRTL and the current keyboard layout is not a RTL one,
1127 // if the input text is pure LTR text, the layout changes to the first RTL
1128 // keyboard layout in keyboard layout queue; if the input text is
1129 // bidirectional text, the layout changes to the keyboard layout of the
1130 // first RTL character in input text. When the window was created without
1131 // WS_EX_LAYOUTRTL and the current keyboard layout is not a LTR one, if
1132 // the input text is pure RTL text, the layout changes to English; if the
1133 // input text is bidirectional text, the layout changes to the keyboard
1134 // layout of the first LTR character in input text. Such keyboard layout
1135 // change behavior is surprising and inconsistent with keyboard behavior
1136 // elsewhere, so reset the layout in this case.
1137 HKL layout = GetKeyboardLayout(0);
1138 DefWindowProc(msg->message, msg->wParam, msg->lParam);
1139 ActivateKeyboardLayout(layout, KLF_REORDER);
1141 DefWindowProc(msg->message, msg->wParam, msg->lParam);
1144 // CRichEditCtrl automatically turns on IMF_AUTOKEYBOARD when the user
1145 // inputs an RTL character, making it difficult for the user to control
1146 // what language is set as they type. Force this off to make the edit's
1147 // behavior more stable.
1148 const int lang_options = SendMessage(EM_GETLANGOPTIONS, 0, 0);
1149 if (lang_options & IMF_AUTOKEYBOARD)
1150 SendMessage(EM_SETLANGOPTIONS, 0, lang_options & ~IMF_AUTOKEYBOARD);
1152 OnAfterPossibleChange(true);
1156 void NativeTextfieldWin::OnBeforePossibleChange() {
1157 // Record our state.
1158 text_before_change_ = GetText();
1161 void NativeTextfieldWin::OnAfterPossibleChange(bool should_redraw_text) {
1162 // Prevent the user from selecting the "phantom newline" at the end of the
1163 // edit. If they try, we just silently move the end of the selection back to
1164 // the end of the real text.
1167 const int length = GetTextLength();
1168 if (new_sel.cpMax > length) {
1169 new_sel.cpMax = length;
1170 if (new_sel.cpMin > length)
1171 new_sel.cpMin = length;
1175 string16 new_text(GetText());
1176 if (new_text != text_before_change_) {
1177 if (ime_discard_composition_ && ime_composition_start_ >= 0 &&
1178 ime_composition_length_ > 0) {
1179 // A string retrieved with a GetText() call contains a string being
1180 // composed by an IME. We remove the composition string from this search
1182 new_text.erase(ime_composition_start_, ime_composition_length_);
1183 ime_composition_start_ = 0;
1184 ime_composition_length_ = 0;
1185 if (new_text.empty())
1188 textfield_->SyncText();
1189 UpdateAccessibleValue(textfield_->text());
1191 if (should_redraw_text) {
1192 CHARRANGE original_sel;
1193 GetSel(original_sel);
1194 string16 text(GetText());
1195 ScopedSuspendUndo suspend_undo(GetTextObjectModel());
1198 ReplaceSel(reinterpret_cast<LPCTSTR>(text.c_str()), true);
1199 SetSel(original_sel);
1204 LONG NativeTextfieldWin::ClipXCoordToVisibleText(LONG x,
1205 bool is_triple_click) const {
1206 // Clip the X coordinate to the left edge of the text. Careful:
1207 // PosFromChar(0) may return a negative X coordinate if the beginning of the
1208 // text has scrolled off the edit, so don't go past the clip rect's edge.
1211 // Calculation of the clipped coordinate is more complicated if the paragraph
1212 // layout is RTL layout, or if there is RTL characters inside the LTR layout
1214 bool ltr_text_in_ltr_layout = true;
1215 if ((pf2.wEffects & PFE_RTLPARA) ||
1216 base::i18n::StringContainsStrongRTLChars(GetText())) {
1217 ltr_text_in_ltr_layout = false;
1219 const int length = GetTextLength();
1222 // The values returned by PosFromChar() seem to refer always
1223 // to the left edge of the character's bounding box.
1224 const LONG first_position_x = PosFromChar(0).x;
1225 LONG min_x = first_position_x;
1226 if (!ltr_text_in_ltr_layout) {
1227 for (int i = 1; i < length; ++i)
1228 min_x = std::min(min_x, PosFromChar(i).x);
1230 const LONG left_bound = std::max(r.left, min_x);
1232 // PosFromChar(length) is a phantom character past the end of the text. It is
1233 // not necessarily a right bound; in RTL controls it may be a left bound. So
1234 // treat it as a right bound only if it is to the right of the first
1236 LONG right_bound = r.right;
1237 LONG end_position_x = PosFromChar(length).x;
1238 if (end_position_x >= first_position_x) {
1239 right_bound = std::min(right_bound, end_position_x); // LTR case.
1241 // For trailing characters that are 2 pixels wide of less (like "l" in some
1242 // fonts), we have a problem:
1243 // * Clicks on any pixel within the character will place the cursor before
1245 // * Clicks on the pixel just after the character will not allow triple-
1246 // click to work properly (true for any last character width).
1247 // So, we move to the last pixel of the character when this is a
1248 // triple-click, and moving to one past the last pixel in all other
1249 // scenarios. This way, all clicks that can move the cursor will place it at
1250 // the end of the text, but triple-click will still work.
1251 if (x < left_bound) {
1252 return (is_triple_click && ltr_text_in_ltr_layout) ? left_bound - 1 :
1255 if ((length == 0) || (x < right_bound))
1257 return is_triple_click ? (right_bound - 1) : right_bound;
1260 void NativeTextfieldWin::SetContainsMouse(bool contains_mouse) {
1261 if (contains_mouse == contains_mouse_)
1264 contains_mouse_ = contains_mouse;
1266 if (!textfield_->draw_border())
1269 if (contains_mouse_) {
1270 // Register for notification when the mouse leaves. Need to do this so
1271 // that we can reset contains mouse properly.
1272 TRACKMOUSEEVENT tme;
1273 tme.cbSize = sizeof(tme);
1274 tme.dwFlags = TME_LEAVE;
1275 tme.hwndTrack = m_hWnd;
1276 tme.dwHoverTime = 0;
1277 TrackMouseEvent(&tme);
1279 RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_FRAME);
1282 ITextDocument* NativeTextfieldWin::GetTextObjectModel() const {
1283 if (!text_object_model_) {
1284 base::win::ScopedComPtr<IRichEditOle, &IID_IRichEditOle> ole_interface;
1285 ole_interface.Attach(GetOleInterface());
1287 text_object_model_.QueryFrom(ole_interface);
1289 return text_object_model_;
1292 void NativeTextfieldWin::BuildContextMenu() {
1293 if (context_menu_contents_.get())
1295 context_menu_contents_.reset(new ui::SimpleMenuModel(this));
1296 context_menu_contents_->AddItemWithStringId(IDS_APP_UNDO, IDS_APP_UNDO);
1297 context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR);
1298 context_menu_contents_->AddItemWithStringId(IDS_APP_CUT, IDS_APP_CUT);
1299 context_menu_contents_->AddItemWithStringId(IDS_APP_COPY, IDS_APP_COPY);
1300 context_menu_contents_->AddItemWithStringId(IDS_APP_PASTE, IDS_APP_PASTE);
1301 context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR);
1302 context_menu_contents_->AddItemWithStringId(IDS_APP_SELECT_ALL,
1303 IDS_APP_SELECT_ALL);
1306 bool NativeTextfieldWin::ShouldProcessMouseEvent() {
1307 TextfieldController* controller = textfield_->GetController();
1310 MSG msg(*GetCurrentMessage());
1311 // ATL doesn't set the |time| field.
1313 msg.time = GetMessageTime();
1314 ui::MouseEvent mouse_event(msg);
1315 return !controller->HandleMouseEvent(textfield_, mouse_event);
1318 } // namespace views