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. 4 5 #include "ui/views/controls/textfield/native_textfield_win.h" 6 7 #include <algorithm> 8 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/events/event.h" 21 #include "ui/base/ime/win/tsf_bridge.h" 22 #include "ui/base/keycodes/keyboard_codes.h" 23 #include "ui/base/l10n/l10n_util.h" 24 #include "ui/base/l10n/l10n_util_win.h" 25 #include "ui/base/range/range.h" 26 #include "ui/base/win/hwnd_util.h" 27 #include "ui/base/win/mouse_wheel_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" 39 40 namespace views { 41 42 /////////////////////////////////////////////////////////////////////////////// 43 // Helper classes 44 45 NativeTextfieldWin::ScopedFreeze::ScopedFreeze(NativeTextfieldWin* edit, 46 ITextDocument* text_object_model) 47 : edit_(edit), 48 text_object_model_(text_object_model) { 49 // Freeze the screen. 50 if (text_object_model_) { 51 long count; 52 text_object_model_->Freeze(&count); 53 } 54 } 55 56 NativeTextfieldWin::ScopedFreeze::~ScopedFreeze() { 57 // Unfreeze the screen. 58 if (text_object_model_) { 59 long count; 60 text_object_model_->Unfreeze(&count); 61 if (count == 0) { 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 65 // want flicker. 66 edit_->UpdateWindow(); 67 } 68 } 69 } 70 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); 77 } 78 79 NativeTextfieldWin::ScopedSuspendUndo::~ScopedSuspendUndo() { 80 // Resume Undo processing. 81 if (text_object_model_) 82 text_object_model_->Undo(tomResume, NULL); 83 } 84 85 /////////////////////////////////////////////////////////////////////////////// 86 // NativeTextfieldWin 87 88 HMODULE NativeTextfieldWin::loaded_libarary_module_ = false; 89 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), 100 bg_color_(0), 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"); 107 } 108 109 DWORD style = kDefaultEditStyle | ES_AUTOHSCROLL; 110 if (textfield_->style() & Textfield::STYLE_OBSCURED) 111 style |= ES_PASSWORD; 112 113 if (textfield_->read_only()) 114 style |= ES_READONLY; 115 116 // Make sure we apply RTL related extended window styles if necessary. 117 DWORD ex_style = l10n_util::GetExtendedStyles(); 118 119 RECT r = {0, 0, textfield_->width(), textfield_->height()}; 120 Create(textfield_->GetWidget()->GetNativeView(), r, NULL, style, ex_style); 121 122 if (textfield_->style() & Textfield::STYLE_LOWERCASE) { 123 DCHECK((textfield_->style() & Textfield::STYLE_OBSCURED) == 0); 124 SetEditStyle(SES_LOWERCASE, SES_LOWERCASE); 125 } 126 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); 131 132 // Set up the text_object_model_. 133 base::win::ScopedComPtr<IRichEditOle, &IID_IRichEditOle> ole_interface; 134 ole_interface.Attach(GetOleInterface()); 135 if (ole_interface) 136 text_object_model_.QueryFrom(ole_interface); 137 138 InitializeAccessibilityInfo(); 139 } 140 141 NativeTextfieldWin::~NativeTextfieldWin() { 142 if (IsWindow()) 143 DestroyWindow(); 144 } 145 146 // static 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)); 156 } 157 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); 162 } 163 164 //////////////////////////////////////////////////////////////////////////////// 165 // NativeTextfieldWin, NativeTextfieldWrapper implementation: 166 167 string16 NativeTextfieldWin::GetText() const { 168 int len = GetTextLength() + 1; 169 string16 str; 170 if (len > 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); 179 } 180 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); 190 } 191 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())); 197 } 198 199 void NativeTextfieldWin::InsertOrReplaceText(const string16& text) { 200 // Currently not needed. 201 NOTIMPLEMENTED(); 202 } 203 204 base::i18n::TextDirection NativeTextfieldWin::GetTextDirection() const { 205 NOTIMPLEMENTED(); 206 return base::i18n::UNKNOWN_DIRECTION; 207 } 208 209 string16 NativeTextfieldWin::GetSelectedText() const { 210 CHARRANGE sel; 211 GetSel(sel); 212 string16 str; 213 if (sel.cpMin != sel.cpMax) 214 GetSelText(WriteInto(&str, sel.cpMax - sel.cpMin + 1)); 215 return str; 216 } 217 218 void NativeTextfieldWin::SelectAll(bool reversed) { 219 if (reversed) 220 SetSel(GetTextLength(), 0); 221 else 222 SetSel(0, GetTextLength()); 223 } 224 225 void NativeTextfieldWin::ClearSelection() { 226 SetSel(GetTextLength(), GetTextLength()); 227 } 228 229 void NativeTextfieldWin::UpdateBorder() { 230 SetWindowPos(NULL, 0, 0, 0, 0, 231 SWP_NOMOVE | SWP_FRAMECHANGED | SWP_NOACTIVATE | 232 SWP_NOOWNERZORDER | SWP_NOSIZE); 233 } 234 235 void NativeTextfieldWin::UpdateTextColor() { 236 CHARFORMAT cf = {0}; 237 cf.dwMask = CFM_COLOR; 238 cf.crTextColor = skia::SkColorToCOLORREF(textfield_->GetTextColor()); 239 CRichEditCtrl::SetDefaultCharFormat(cf); 240 } 241 242 void NativeTextfieldWin::UpdateBackgroundColor() { 243 CRichEditCtrl::SetBackgroundColor( 244 skia::SkColorToCOLORREF(textfield_->GetBackgroundColor())); 245 } 246 247 void NativeTextfieldWin::UpdateReadOnly() { 248 SendMessage(m_hWnd, EM_SETREADONLY, textfield_->read_only(), 0); 249 UpdateAccessibleState(STATE_SYSTEM_READONLY, textfield_->read_only()); 250 } 251 252 void NativeTextfieldWin::UpdateFont() { 253 SendMessage(m_hWnd, WM_SETFONT, 254 reinterpret_cast<WPARAM>( 255 textfield_->GetPrimaryFont().GetNativeFont()), 256 TRUE); 257 // Setting the font blows away any text color we've set, so reset it. 258 UpdateTextColor(); 259 } 260 261 void NativeTextfieldWin::UpdateIsObscured() { 262 // TODO: Need to implement for Windows. 263 UpdateAccessibleState(STATE_SYSTEM_PROTECTED, textfield_->IsObscured()); 264 } 265 266 void NativeTextfieldWin::UpdateEnabled() { 267 SendMessage(m_hWnd, WM_ENABLE, textfield_->enabled(), 0); 268 UpdateAccessibleState(STATE_SYSTEM_UNAVAILABLE, !textfield_->enabled()); 269 } 270 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 274 // 0. 275 276 // This appears to be the insets used by Windows. 277 return gfx::Insets(3, 3, 3, 3); 278 } 279 280 void NativeTextfieldWin::UpdateHorizontalMargins() { 281 int left, right; 282 if (!textfield_->GetHorizontalMargins(&left, &right)) 283 return; 284 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)); 290 } 291 292 void NativeTextfieldWin::UpdateVerticalMargins() { 293 int top, bottom; 294 if (!textfield_->GetVerticalMargins(&top, &bottom)) 295 return; 296 297 if (top == 0 && bottom == 0) { 298 // Do nothing, default margins are 0 already. 299 return; 300 } 301 // Non-zero margins case. 302 NOTIMPLEMENTED(); 303 } 304 305 void NativeTextfieldWin::UpdateVerticalAlignment() { 306 // Default alignment is vertically centered. 307 if (textfield_->vertical_alignment() != gfx::ALIGN_VCENTER) 308 NOTIMPLEMENTED(); 309 } 310 311 bool NativeTextfieldWin::SetFocus() { 312 // Focus the associated HWND. 313 //container_view_->Focus(); 314 ::SetFocus(m_hWnd); 315 return true; 316 } 317 318 View* NativeTextfieldWin::GetView() { 319 return container_view_; 320 } 321 322 gfx::NativeView NativeTextfieldWin::GetTestingHandle() const { 323 return m_hWnd; 324 } 325 326 bool NativeTextfieldWin::IsIMEComposing() const { 327 // Retrieve the length of the composition string to check if an IME is 328 // composing text. (If this length is > 0 then an IME is being used to compose 329 // text.) 330 if (base::win::IsTSFAwareRequired()) 331 return tsf_event_router_->IsImeComposing(); 332 333 HIMC imm_context = ImmGetContext(m_hWnd); 334 if (!imm_context) 335 return false; 336 337 const int composition_size = ImmGetCompositionString(imm_context, GCS_COMPSTR, 338 NULL, 0); 339 ImmReleaseContext(m_hWnd, imm_context); 340 return composition_size > 0; 341 } 342 343 ui::Range NativeTextfieldWin::GetSelectedRange() const { 344 // TODO(tommi): Implement. 345 NOTIMPLEMENTED(); 346 return ui::Range(); 347 } 348 349 void NativeTextfieldWin::SelectRange(const ui::Range& range) { 350 // TODO(tommi): Implement. 351 NOTIMPLEMENTED(); 352 } 353 354 gfx::SelectionModel NativeTextfieldWin::GetSelectionModel() const { 355 // TODO(tommi): Implement. 356 NOTIMPLEMENTED(); 357 return gfx::SelectionModel(); 358 } 359 360 void NativeTextfieldWin::SelectSelectionModel(const gfx::SelectionModel& sel) { 361 // TODO(tommi): Implement. 362 NOTIMPLEMENTED(); 363 } 364 365 size_t NativeTextfieldWin::GetCursorPosition() const { 366 // TODO(tommi): Implement. 367 NOTIMPLEMENTED(); 368 return 0U; 369 } 370 371 bool NativeTextfieldWin::GetCursorEnabled() const { 372 // TODO(msw): Implement. 373 NOTIMPLEMENTED(); 374 return true; 375 } 376 377 void NativeTextfieldWin::SetCursorEnabled(bool enabled) { 378 // TODO(msw): Implement. 379 NOTIMPLEMENTED(); 380 } 381 382 bool NativeTextfieldWin::HandleKeyPressed(const ui::KeyEvent& event) { 383 return false; 384 } 385 386 bool NativeTextfieldWin::HandleKeyReleased(const ui::KeyEvent& event) { 387 return false; 388 } 389 390 void NativeTextfieldWin::HandleFocus() { 391 } 392 393 void NativeTextfieldWin::HandleBlur() { 394 } 395 396 ui::TextInputClient* NativeTextfieldWin::GetTextInputClient() { 397 return NULL; 398 } 399 400 void NativeTextfieldWin::SetColor(SkColor value) { 401 NOTREACHED(); 402 } 403 404 void NativeTextfieldWin::ApplyColor(SkColor value, const ui::Range& range) { 405 NOTREACHED(); 406 } 407 408 void NativeTextfieldWin::SetStyle(gfx::TextStyle style, bool value) { 409 NOTREACHED(); 410 } 411 412 void NativeTextfieldWin::ApplyStyle(gfx::TextStyle style, 413 bool value, 414 const ui::Range& range) { 415 NOTREACHED(); 416 } 417 418 void NativeTextfieldWin::ClearEditHistory() { 419 NOTREACHED(); 420 } 421 422 int NativeTextfieldWin::GetFontHeight() { 423 return textfield_->font_list().GetHeight(); 424 } 425 426 int NativeTextfieldWin::GetTextfieldBaseline() const { 427 return textfield_->font_list().GetBaseline(); 428 } 429 430 int NativeTextfieldWin::GetWidthNeededForText() const { 431 NOTIMPLEMENTED(); 432 return 0; 433 } 434 435 void NativeTextfieldWin::ExecuteTextCommand(int command_id) { 436 ExecuteCommand(command_id, 0); 437 } 438 439 bool NativeTextfieldWin::HasTextBeingDragged() { 440 NOTIMPLEMENTED(); 441 return false; 442 } 443 444 //////////////////////////////////////////////////////////////////////////////// 445 // NativeTextfieldWin, ui::SimpleMenuModel::Delegate implementation: 446 447 bool NativeTextfieldWin::IsCommandIdChecked(int command_id) const { 448 return false; 449 } 450 451 bool NativeTextfieldWin::IsCommandIdEnabled(int command_id) const { 452 switch (command_id) { 453 case IDS_APP_UNDO: return !textfield_->read_only() && !!CanUndo(); 454 case IDS_APP_CUT: return !textfield_->read_only() && 455 !textfield_->IsObscured() && !!CanCut(); 456 case IDS_APP_COPY: return !!CanCopy() && !textfield_->IsObscured(); 457 case IDS_APP_PASTE: return !textfield_->read_only() && !!CanPaste(); 458 case IDS_APP_SELECT_ALL: return !!CanSelectAll(); 459 default: NOTREACHED(); 460 return false; 461 } 462 } 463 464 bool NativeTextfieldWin::GetAcceleratorForCommandId(int command_id, 465 ui::Accelerator* accelerator) { 466 // The standard Ctrl-X, Ctrl-V and Ctrl-C are not defined as accelerators 467 // anywhere so we need to check for them explicitly here. 468 switch (command_id) { 469 case IDS_APP_CUT: 470 *accelerator = ui::Accelerator(ui::VKEY_X, ui::EF_CONTROL_DOWN); 471 return true; 472 case IDS_APP_COPY: 473 *accelerator = ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN); 474 return true; 475 case IDS_APP_PASTE: 476 *accelerator = ui::Accelerator(ui::VKEY_V, ui::EF_CONTROL_DOWN); 477 return true; 478 } 479 return container_view_->GetWidget()->GetAccelerator(command_id, accelerator); 480 } 481 482 void NativeTextfieldWin::ExecuteCommand(int command_id, int event_flags) { 483 ScopedFreeze freeze(this, GetTextObjectModel()); 484 OnBeforePossibleChange(); 485 switch (command_id) { 486 case IDS_APP_UNDO: Undo(); break; 487 case IDS_APP_CUT: Cut(); break; 488 case IDS_APP_COPY: Copy(); break; 489 case IDS_APP_PASTE: Paste(); break; 490 case IDS_APP_SELECT_ALL: SelectAll(false); break; 491 default: NOTREACHED(); break; 492 } 493 OnAfterPossibleChange(true); 494 } 495 496 void NativeTextfieldWin::OnTextUpdated(const ui::Range& composition_range) { 497 if (ime_discard_composition_) { 498 ime_composition_start_ = composition_range.start(); 499 ime_composition_length_ = composition_range.length(); 500 } else { 501 ime_composition_start_ = 0; 502 ime_composition_length_ = 0; 503 } 504 OnAfterPossibleChange(false); 505 text_before_change_.clear(); 506 } 507 508 void NativeTextfieldWin::OnImeStartCompositionInternal() { 509 // Users may press alt+shift or control+shift keys to change their keyboard 510 // layouts. So, we retrieve the input locale identifier everytime we start 511 // an IME composition. 512 int language_id = PRIMARYLANGID(GetKeyboardLayout(0)); 513 ime_discard_composition_ = 514 language_id == LANG_JAPANESE || language_id == LANG_CHINESE; 515 ime_composition_start_ = 0; 516 ime_composition_length_ = 0; 517 } 518 519 void NativeTextfieldWin::OnImeEndCompositionInternal() { 520 // Bug 11863: Korean IMEs send a WM_IME_ENDCOMPOSITION message without 521 // sending any WM_IME_COMPOSITION messages when a user deletes all 522 // composition characters, i.e. a composition string becomes empty. To handle 523 // this case, we need to update the find results when a composition is 524 // finished or canceled. 525 textfield_->SyncText(); 526 } 527 528 void NativeTextfieldWin::OnTSFStartComposition() { 529 OnImeStartCompositionInternal(); 530 } 531 532 void NativeTextfieldWin::OnTSFEndComposition() { 533 OnImeEndCompositionInternal(); 534 } 535 536 void NativeTextfieldWin::InitializeAccessibilityInfo() { 537 // Set the accessible state. 538 accessibility_state_ = 0; 539 540 base::win::ScopedComPtr<IAccPropServices> pAccPropServices; 541 HRESULT hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER, 542 IID_IAccPropServices, reinterpret_cast<void**>(&pAccPropServices)); 543 if (!SUCCEEDED(hr)) 544 return; 545 546 VARIANT var; 547 548 // Set the accessible role. 549 var.vt = VT_I4; 550 var.lVal = ROLE_SYSTEM_TEXT; 551 hr = pAccPropServices->SetHwndProp(m_hWnd, OBJID_CLIENT, 552 CHILDID_SELF, PROPID_ACC_ROLE, var); 553 554 // Set the accessible name by getting the label text. 555 View* parent = textfield_->parent(); 556 int label_index = parent->GetIndexOf(textfield_) - 1; 557 if (label_index >= 0) { 558 // Try to find the name of this text field. 559 // We expect it to be a Label preceeding this view (if it exists). 560 string16 name; 561 View* label_view = parent->child_at(label_index); 562 if (!strcmp(label_view->GetClassName(), Label::kViewClassName)) { 563 ui::AccessibleViewState state; 564 label_view->GetAccessibleState(&state); 565 hr = pAccPropServices->SetHwndPropStr(m_hWnd, OBJID_CLIENT, 566 CHILDID_SELF, PROPID_ACC_NAME, state.name.c_str()); 567 } 568 } 569 } 570 571 void NativeTextfieldWin::UpdateAccessibleState(uint32 state_flag, 572 bool set_value) { 573 base::win::ScopedComPtr<IAccPropServices> pAccPropServices; 574 HRESULT hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER, 575 IID_IAccPropServices, reinterpret_cast<void**>(&pAccPropServices)); 576 if (!SUCCEEDED(hr)) 577 return; 578 579 VARIANT var; 580 var.vt = VT_I4; 581 var.lVal = set_value ? accessibility_state_ | state_flag 582 : accessibility_state_ & ~state_flag; 583 hr = pAccPropServices->SetHwndProp(m_hWnd, OBJID_CLIENT, 584 CHILDID_SELF, PROPID_ACC_STATE, var); 585 586 ::NotifyWinEvent(EVENT_OBJECT_STATECHANGE, m_hWnd, OBJID_CLIENT, 587 CHILDID_SELF); 588 } 589 590 void NativeTextfieldWin::UpdateAccessibleValue(const string16& value) { 591 base::win::ScopedComPtr<IAccPropServices> pAccPropServices; 592 HRESULT hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER, 593 IID_IAccPropServices, reinterpret_cast<void**>(&pAccPropServices)); 594 if (!SUCCEEDED(hr)) 595 return; 596 597 hr = pAccPropServices->SetHwndPropStr(m_hWnd, OBJID_CLIENT, 598 CHILDID_SELF, PROPID_ACC_VALUE, value.c_str()); 599 600 ::NotifyWinEvent(EVENT_OBJECT_VALUECHANGE, m_hWnd, OBJID_CLIENT, 601 CHILDID_SELF); 602 } 603 604 //////////////////////////////////////////////////////////////////////////////// 605 // NativeTextfieldWin, private: 606 607 void NativeTextfieldWin::OnChar(TCHAR ch, UINT repeat_count, UINT flags) { 608 HandleKeystroke(); 609 } 610 611 void NativeTextfieldWin::OnContextMenu(HWND window, const POINT& point) { 612 POINT p(point); 613 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE; 614 if (point.x == -1 || point.y == -1) { 615 source_type = ui::MENU_SOURCE_KEYBOARD; 616 GetCaretPos(&p); 617 MapWindowPoints(HWND_DESKTOP, &p, 1); 618 } 619 BuildContextMenu(); 620 621 MenuModelAdapter adapter(context_menu_contents_.get()); 622 context_menu_runner_.reset(new MenuRunner(adapter.CreateMenu())); 623 624 ignore_result(context_menu_runner_->RunMenuAt(textfield_->GetWidget(), NULL, 625 gfx::Rect(gfx::Point(p), gfx::Size()), MenuItemView::TOPLEFT, 626 source_type, MenuRunner::HAS_MNEMONICS)); 627 } 628 629 void NativeTextfieldWin::OnCopy() { 630 if (textfield_->IsObscured()) 631 return; 632 633 const string16 text(GetSelectedText()); 634 if (!text.empty()) { 635 ui::ScopedClipboardWriter( 636 ui::Clipboard::GetForCurrentThread(), 637 ui::Clipboard::BUFFER_STANDARD).WriteText(text); 638 if (TextfieldController* controller = textfield_->GetController()) 639 controller->OnAfterCutOrCopy(); 640 } 641 } 642 643 LRESULT NativeTextfieldWin::OnCreate(const CREATESTRUCTW* /*create_struct*/) { 644 if (base::win::IsTSFAwareRequired()) { 645 // Enable TSF support of RichEdit. 646 SetEditStyle(SES_USECTF, SES_USECTF); 647 648 // When TSF is enabled, OnTextUpdated() may be called without any previous 649 // call that would have indicated the start of an editing session. In order 650 // to guarantee we've always called OnBeforePossibleChange() before 651 // OnAfterPossibleChange(), we therefore call that here. Note that multiple 652 // (i.e. unmatched) calls to this function in a row are safe. 653 OnBeforePossibleChange(); 654 } 655 SetMsgHandled(FALSE); 656 return 0; 657 } 658 659 void NativeTextfieldWin::OnCut() { 660 if (textfield_->read_only() || textfield_->IsObscured()) 661 return; 662 663 OnCopy(); 664 665 // This replace selection will have no effect (even on the undo stack) if the 666 // current selection is empty. 667 ReplaceSel(L"", true); 668 } 669 670 LRESULT NativeTextfieldWin::OnGetObject(UINT message, 671 WPARAM wparam, 672 LPARAM lparam) { 673 LRESULT ret = 0; 674 if (lparam == OBJID_CLIENT) { 675 ret = LresultFromObject(IID_IAccessible, wparam, 676 textfield_->GetNativeViewAccessible()); 677 } 678 return ret; 679 } 680 681 LRESULT NativeTextfieldWin::OnImeChar(UINT message, 682 WPARAM wparam, 683 LPARAM lparam) { 684 // http://crbug.com/7707: a rich-edit control may crash when it receives a 685 // WM_IME_CHAR message while it is processing a WM_IME_COMPOSITION message. 686 // Since view controls don't need WM_IME_CHAR messages, we prevent WM_IME_CHAR 687 // messages from being dispatched to view controls via the CallWindowProc() 688 // call. 689 return 0; 690 } 691 692 LRESULT NativeTextfieldWin::OnImeStartComposition(UINT message, 693 WPARAM wparam, 694 LPARAM lparam) { 695 OnImeStartCompositionInternal(); 696 return DefWindowProc(message, wparam, lparam); 697 } 698 699 LRESULT NativeTextfieldWin::OnImeComposition(UINT message, 700 WPARAM wparam, 701 LPARAM lparam) { 702 text_before_change_.clear(); 703 LRESULT result = DefWindowProc(message, wparam, lparam); 704 705 ime_composition_start_ = 0; 706 ime_composition_length_ = 0; 707 if (ime_discard_composition_) { 708 // Call IMM32 functions to retrieve the position and the length of the 709 // ongoing composition string and notify the OnAfterPossibleChange() 710 // function that it should discard the composition string from a search 711 // string. We should not call IMM32 functions in the function because it 712 // is called when an IME is not composing a string. 713 HIMC imm_context = ImmGetContext(m_hWnd); 714 if (imm_context) { 715 CHARRANGE selection; 716 GetSel(selection); 717 const int cursor_position = 718 ImmGetCompositionString(imm_context, GCS_CURSORPOS, NULL, 0); 719 if (cursor_position >= 0) 720 ime_composition_start_ = selection.cpMin - cursor_position; 721 722 const int composition_size = 723 ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0); 724 if (composition_size >= 0) 725 ime_composition_length_ = composition_size / sizeof(wchar_t); 726 727 ImmReleaseContext(m_hWnd, imm_context); 728 } 729 } 730 731 // If we allow OnAfterPossibleChange() to redraw the text, it will do this by 732 // setting the edit's text directly, which can cancel the current IME 733 // composition or cause other adverse affects. So we set |should_redraw_text| 734 // to false. 735 OnAfterPossibleChange(false); 736 return result; 737 } 738 739 LRESULT NativeTextfieldWin::OnImeEndComposition(UINT message, 740 WPARAM wparam, 741 LPARAM lparam) { 742 OnImeEndCompositionInternal(); 743 return DefWindowProc(message, wparam, lparam); 744 } 745 746 LRESULT NativeTextfieldWin::OnPointerDown(UINT message, WPARAM wparam, 747 LPARAM lparam) { 748 SetFocus(); 749 SetMsgHandled(FALSE); 750 return 0; 751 } 752 753 void NativeTextfieldWin::OnKeyDown(TCHAR key, UINT repeat_count, UINT flags) { 754 // NOTE: Annoyingly, ctrl-alt-<key> generates WM_KEYDOWN rather than 755 // WM_SYSKEYDOWN, so we need to check (flags & KF_ALTDOWN) in various places 756 // in this function even with a WM_SYSKEYDOWN handler. 757 758 switch (key) { 759 760 // Ignore Return 761 case VK_RETURN: 762 return; 763 764 // Hijacking Editing Commands 765 // 766 // We hijack the keyboard short-cuts for Cut, Copy, and Paste here so that 767 // they go through our clipboard routines. This allows us to be smarter 768 // about how we interact with the clipboard and avoid bugs in the 769 // CRichEditCtrl. If we didn't hijack here, the edit control would handle 770 // these internally with sending the WM_CUT, WM_COPY, or WM_PASTE messages. 771 // 772 // Cut: Shift-Delete and Ctrl-x are treated as cut. Ctrl-Shift-Delete and 773 // Ctrl-Shift-x are not treated as cut even though the underlying 774 // CRichTextEdit would treat them as such. 775 // Copy: Ctrl-c is treated as copy. Shift-Ctrl-c is not. 776 // Paste: Shift-Insert and Ctrl-v are tread as paste. Ctrl-Shift-Insert and 777 // Ctrl-Shift-v are not. 778 // 779 // This behavior matches most, but not all Windows programs, and largely 780 // conforms to what users expect. 781 782 case VK_DELETE: 783 case 'X': 784 if ((flags & KF_ALTDOWN) || 785 (GetKeyState((key == 'X') ? VK_CONTROL : VK_SHIFT) >= 0)) 786 break; 787 if (GetKeyState((key == 'X') ? VK_SHIFT : VK_CONTROL) >= 0) { 788 ScopedFreeze freeze(this, GetTextObjectModel()); 789 OnBeforePossibleChange(); 790 Cut(); 791 OnAfterPossibleChange(true); 792 } 793 return; 794 795 case 'C': 796 if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0)) 797 break; 798 if (GetKeyState(VK_SHIFT) >= 0) 799 Copy(); 800 return; 801 802 case VK_INSERT: 803 // Ignore insert by itself, so we don't turn overtype mode on/off. 804 if (!(flags & KF_ALTDOWN) && (GetKeyState(VK_SHIFT) >= 0) && 805 (GetKeyState(VK_CONTROL) >= 0)) 806 return; 807 // Fall through to the next case (ie. Shift-Insert == Ctrl-V). 808 case 'V': 809 if ((flags & KF_ALTDOWN) || 810 (GetKeyState((key == 'V') ? VK_CONTROL : VK_SHIFT) >= 0)) 811 break; 812 if (GetKeyState((key == 'V') ? VK_SHIFT : VK_CONTROL) >= 0) { 813 ScopedFreeze freeze(this, GetTextObjectModel()); 814 OnBeforePossibleChange(); 815 Paste(); 816 OnAfterPossibleChange(true); 817 } 818 return; 819 820 case 0xbb: // Ctrl-'='. Triggers subscripting, even in plain text mode. 821 // We don't use VK_OEM_PLUS in case the macro isn't defined. 822 // (e.g., we don't have this symbol in embeded environment). 823 return; 824 825 case VK_PROCESSKEY: 826 // This key event is consumed by an IME. 827 // We ignore this event because an IME sends WM_IME_COMPOSITION messages 828 // when it updates the CRichEditCtrl text. 829 return; 830 } 831 832 // CRichEditCtrl changes its text on WM_KEYDOWN instead of WM_CHAR for many 833 // different keys (backspace, ctrl-v, ...), so we call this in both cases. 834 HandleKeystroke(); 835 } 836 837 void NativeTextfieldWin::OnLButtonDblClk(UINT keys, const CPoint& point) { 838 // Save the double click info for later triple-click detection. 839 tracking_double_click_ = true; 840 double_click_point_ = point; 841 double_click_time_ = GetCurrentMessage()->time; 842 843 if (!ShouldProcessMouseEvent()) 844 return; 845 846 ScopedFreeze freeze(this, GetTextObjectModel()); 847 OnBeforePossibleChange(); 848 DefWindowProc(WM_LBUTTONDBLCLK, keys, 849 MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); 850 OnAfterPossibleChange(true); 851 } 852 853 void NativeTextfieldWin::OnLButtonDown(UINT keys, const CPoint& point) { 854 // Check for triple click, then reset tracker. Should be safe to subtract 855 // double_click_time_ from the current message's time even if the timer has 856 // wrapped in between. 857 const bool is_triple_click = tracking_double_click_ && 858 IsDoubleClick(double_click_point_, point, 859 GetCurrentMessage()->time - double_click_time_); 860 tracking_double_click_ = false; 861 862 if (!ShouldProcessMouseEvent()) 863 return; 864 865 ScopedFreeze freeze(this, GetTextObjectModel()); 866 OnBeforePossibleChange(); 867 DefWindowProc(WM_LBUTTONDOWN, keys, 868 MAKELPARAM(ClipXCoordToVisibleText(point.x, is_triple_click), 869 point.y)); 870 OnAfterPossibleChange(true); 871 } 872 873 void NativeTextfieldWin::OnLButtonUp(UINT keys, const CPoint& point) { 874 ScopedFreeze freeze(this, GetTextObjectModel()); 875 OnBeforePossibleChange(); 876 DefWindowProc(WM_LBUTTONUP, keys, 877 MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); 878 OnAfterPossibleChange(true); 879 } 880 881 void NativeTextfieldWin::OnMouseLeave() { 882 SetContainsMouse(false); 883 } 884 885 LRESULT NativeTextfieldWin::OnMouseWheel(UINT message, 886 WPARAM w_param, 887 LPARAM l_param) { 888 // Reroute the mouse-wheel to the window under the mouse pointer if 889 // applicable. 890 if (ui::RerouteMouseWheel(m_hWnd, w_param, l_param)) 891 return 0; 892 return DefWindowProc(message, w_param, l_param); 893 } 894 895 void NativeTextfieldWin::OnMouseMove(UINT keys, const CPoint& point) { 896 SetContainsMouse(true); 897 // Clamp the selection to the visible text so the user can't drag to select 898 // the "phantom newline". In theory we could achieve this by clipping the X 899 // coordinate, but in practice the edit seems to behave nondeterministically 900 // with similar sequences of clipped input coordinates fed to it. Maybe it's 901 // reading the mouse cursor position directly? 902 // 903 // This solution has a minor visual flaw, however: if there's a visible 904 // cursor at the edge of the text (only true when there's no selection), 905 // dragging the mouse around outside that edge repaints the cursor on every 906 // WM_MOUSEMOVE instead of allowing it to blink normally. To fix this, we 907 // special-case this exact case and discard the WM_MOUSEMOVE messages instead 908 // of passing them along. 909 // 910 // But even this solution has a flaw! (Argh.) In the case where the user 911 // has a selection that starts at the edge of the edit, and proceeds to the 912 // middle of the edit, and the user is dragging back past the start edge to 913 // remove the selection, there's a redraw problem where the change between 914 // having the last few bits of text still selected and having nothing 915 // selected can be slow to repaint (which feels noticeably strange). This 916 // occurs if you only let the edit receive a single WM_MOUSEMOVE past the 917 // edge of the text. I think on each WM_MOUSEMOVE the edit is repainting its 918 // previous state, then updating its internal variables to the new state but 919 // not repainting. To fix this, we allow one more WM_MOUSEMOVE through after 920 // the selection has supposedly been shrunk to nothing; this makes the edit 921 // redraw the selection quickly so it feels smooth. 922 CHARRANGE selection; 923 GetSel(selection); 924 const bool possibly_can_discard_mousemove = 925 (selection.cpMin == selection.cpMax) && 926 (((selection.cpMin == 0) && 927 (ClipXCoordToVisibleText(point.x, false) > point.x)) || 928 ((selection.cpMin == GetTextLength()) && 929 (ClipXCoordToVisibleText(point.x, false) < point.x))); 930 if (!can_discard_mousemove_ || !possibly_can_discard_mousemove) { 931 can_discard_mousemove_ = possibly_can_discard_mousemove; 932 ScopedFreeze freeze(this, GetTextObjectModel()); 933 OnBeforePossibleChange(); 934 // Force the Y coordinate to the center of the clip rect. The edit 935 // behaves strangely when the cursor is dragged vertically: if the cursor 936 // is in the middle of the text, drags inside the clip rect do nothing, 937 // and drags outside the clip rect act as if the cursor jumped to the 938 // left edge of the text. When the cursor is at the right edge, drags of 939 // just a few pixels vertically end up selecting the "phantom newline"... 940 // sometimes. 941 RECT r; 942 GetRect(&r); 943 DefWindowProc(WM_MOUSEMOVE, keys, 944 MAKELPARAM(point.x, (r.bottom - r.top) / 2)); 945 OnAfterPossibleChange(true); 946 } 947 } 948 949 int NativeTextfieldWin::OnNCCalcSize(BOOL w_param, LPARAM l_param) { 950 content_insets_.Set(0, 0, 0, 0); 951 if (textfield_->draw_border()) 952 content_insets_ = CalculateInsets(); 953 if (w_param) { 954 NCCALCSIZE_PARAMS* nc_params = 955 reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param); 956 nc_params->rgrc[0].left += content_insets_.left(); 957 nc_params->rgrc[0].right -= content_insets_.right(); 958 nc_params->rgrc[0].top += content_insets_.top(); 959 nc_params->rgrc[0].bottom -= content_insets_.bottom(); 960 } else { 961 RECT* rect = reinterpret_cast<RECT*>(l_param); 962 rect->left += content_insets_.left(); 963 rect->right -= content_insets_.right(); 964 rect->top += content_insets_.top(); 965 rect->bottom -= content_insets_.bottom(); 966 } 967 return 0; 968 } 969 970 void NativeTextfieldWin::OnNCPaint(HRGN region) { 971 if (!textfield_->draw_border()) 972 return; 973 974 HDC hdc = GetWindowDC(); 975 976 CRect window_rect; 977 GetWindowRect(&window_rect); 978 // Convert to be relative to 0x0. 979 window_rect.MoveToXY(0, 0); 980 981 ExcludeClipRect(hdc, 982 window_rect.left + content_insets_.left(), 983 window_rect.top + content_insets_.top(), 984 window_rect.right - content_insets_.right(), 985 window_rect.bottom - content_insets_.bottom()); 986 987 HBRUSH brush = CreateSolidBrush(bg_color_); 988 FillRect(hdc, &window_rect, brush); 989 DeleteObject(brush); 990 991 int part; 992 int state; 993 994 if (base::win::GetVersion() < base::win::VERSION_VISTA) { 995 part = EP_EDITTEXT; 996 997 if (!textfield_->enabled()) 998 state = ETS_DISABLED; 999 else if (textfield_->read_only()) 1000 state = ETS_READONLY; 1001 else if (!contains_mouse_) 1002 state = ETS_NORMAL; 1003 else 1004 state = ETS_HOT; 1005 } else { 1006 part = EP_EDITBORDER_HVSCROLL; 1007 1008 if (!textfield_->enabled()) 1009 state = EPSHV_DISABLED; 1010 else if (GetFocus() == m_hWnd) 1011 state = EPSHV_FOCUSED; 1012 else if (contains_mouse_) 1013 state = EPSHV_HOT; 1014 else 1015 state = EPSHV_NORMAL; 1016 // Vista doesn't appear to have a unique state for readonly. 1017 } 1018 1019 int classic_state = 1020 (!textfield_->enabled() || textfield_->read_only()) ? DFCS_INACTIVE : 0; 1021 1022 ui::NativeThemeWin::instance()->PaintTextField(hdc, part, state, 1023 classic_state, &window_rect, 1024 bg_color_, false, true); 1025 1026 // NOTE: I tried checking the transparent property of the theme and invoking 1027 // drawParentBackground, but it didn't seem to make a difference. 1028 1029 ReleaseDC(hdc); 1030 } 1031 1032 void NativeTextfieldWin::OnNonLButtonDown(UINT keys, const CPoint& point) { 1033 // Interestingly, the edit doesn't seem to cancel triple clicking when the 1034 // x-buttons (which usually means "thumb buttons") are pressed, so we only 1035 // call this for M and R down. 1036 tracking_double_click_ = false; 1037 1038 if (!ShouldProcessMouseEvent()) 1039 return; 1040 1041 SetMsgHandled(false); 1042 } 1043 1044 void NativeTextfieldWin::OnPaste() { 1045 if (textfield_->read_only()) 1046 return; 1047 1048 ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); 1049 if (!clipboard->IsFormatAvailable(ui::Clipboard::GetPlainTextWFormatType(), 1050 ui::Clipboard::BUFFER_STANDARD)) 1051 return; 1052 1053 string16 clipboard_str; 1054 clipboard->ReadText(ui::Clipboard::BUFFER_STANDARD, &clipboard_str); 1055 if (!clipboard_str.empty()) { 1056 string16 collapsed(CollapseWhitespace(clipboard_str, false)); 1057 if (textfield_->style() & Textfield::STYLE_LOWERCASE) 1058 collapsed = base::i18n::ToLower(collapsed); 1059 // Force a Paste operation to trigger ContentsChanged, even if identical 1060 // contents are pasted into the text box. See http://crbug.com/79002 1061 ReplaceSel(L"", false); 1062 textfield_->SyncText(); 1063 text_before_change_.clear(); 1064 ReplaceSel(collapsed.c_str(), true); 1065 if (TextfieldController* controller = textfield_->GetController()) 1066 controller->OnAfterPaste(); 1067 } 1068 } 1069 1070 void NativeTextfieldWin::OnSetFocus(HWND hwnd) { 1071 SetMsgHandled(FALSE); // We still want the default processing of the message. 1072 1073 views::FocusManager* focus_manager = textfield_->GetFocusManager(); 1074 if (!focus_manager) { 1075 NOTREACHED(); 1076 return; 1077 } 1078 focus_manager->SetFocusedView(textfield_); 1079 1080 if (!base::win::IsTSFAwareRequired()) { 1081 return; 1082 } 1083 1084 DefWindowProc(); 1085 1086 // Document manager created by RichEdit can be obtained only after 1087 // WM_SET_FOCUS event is handled. 1088 tsf_event_router_->SetManager( 1089 ui::TSFBridge::GetInstance()->GetThreadManager()); 1090 SetMsgHandled(TRUE); 1091 } 1092 1093 void NativeTextfieldWin::OnKillFocus(HWND hwnd) { 1094 if(tsf_event_router_) 1095 tsf_event_router_->SetManager(NULL); 1096 SetMsgHandled(FALSE); 1097 } 1098 1099 void NativeTextfieldWin::OnSysChar(TCHAR ch, UINT repeat_count, UINT flags) { 1100 DCHECK(flags & KF_ALTDOWN); 1101 // Explicitly show the system menu at a good location on [Alt]+[Space]. 1102 // Nearly all other [Alt]+<xxx> combos result in beeping rather than doing 1103 // something useful, so discard those. Note that [Ctrl]+[Alt]+<xxx> generates 1104 // WM_CHAR instead of WM_SYSCHAR, so it is not handled here. 1105 if (ch == VK_SPACE) { 1106 ui::ShowSystemMenu( 1107 container_view_->GetWidget()->GetTopLevelWidget()->GetNativeWindow()); 1108 } 1109 } 1110 1111 void NativeTextfieldWin::OnFinalMessage(HWND hwnd) { 1112 delete this; 1113 } 1114 1115 void NativeTextfieldWin::HandleKeystroke() { 1116 const MSG* msg = GetCurrentMessage(); 1117 ui::KeyEvent event(*msg, msg->message == WM_CHAR); 1118 ScopedFreeze freeze(this, GetTextObjectModel()); 1119 1120 TextfieldController* controller = textfield_->GetController(); 1121 if (!controller || !controller->HandleKeyEvent(textfield_, event)) { 1122 OnBeforePossibleChange(); 1123 1124 if (msg->wParam == ui::VKEY_HOME || msg->wParam == ui::VKEY_END) { 1125 // DefWindowProc() might reset the keyboard layout when it receives a 1126 // keydown event for VKEY_HOME or VKEY_END. When the window was created 1127 // with WS_EX_LAYOUTRTL and the current keyboard layout is not a RTL one, 1128 // if the input text is pure LTR text, the layout changes to the first RTL 1129 // keyboard layout in keyboard layout queue; if the input text is 1130 // bidirectional text, the layout changes to the keyboard layout of the 1131 // first RTL character in input text. When the window was created without 1132 // WS_EX_LAYOUTRTL and the current keyboard layout is not a LTR one, if 1133 // the input text is pure RTL text, the layout changes to English; if the 1134 // input text is bidirectional text, the layout changes to the keyboard 1135 // layout of the first LTR character in input text. Such keyboard layout 1136 // change behavior is surprising and inconsistent with keyboard behavior 1137 // elsewhere, so reset the layout in this case. 1138 HKL layout = GetKeyboardLayout(0); 1139 DefWindowProc(msg->message, msg->wParam, msg->lParam); 1140 ActivateKeyboardLayout(layout, KLF_REORDER); 1141 } else { 1142 DefWindowProc(msg->message, msg->wParam, msg->lParam); 1143 } 1144 1145 // CRichEditCtrl automatically turns on IMF_AUTOKEYBOARD when the user 1146 // inputs an RTL character, making it difficult for the user to control 1147 // what language is set as they type. Force this off to make the edit's 1148 // behavior more stable. 1149 const int lang_options = SendMessage(EM_GETLANGOPTIONS, 0, 0); 1150 if (lang_options & IMF_AUTOKEYBOARD) 1151 SendMessage(EM_SETLANGOPTIONS, 0, lang_options & ~IMF_AUTOKEYBOARD); 1152 1153 OnAfterPossibleChange(true); 1154 } 1155 } 1156 1157 void NativeTextfieldWin::OnBeforePossibleChange() { 1158 // Record our state. 1159 text_before_change_ = GetText(); 1160 } 1161 1162 void NativeTextfieldWin::OnAfterPossibleChange(bool should_redraw_text) { 1163 // Prevent the user from selecting the "phantom newline" at the end of the 1164 // edit. If they try, we just silently move the end of the selection back to 1165 // the end of the real text. 1166 CHARRANGE new_sel; 1167 GetSel(new_sel); 1168 const int length = GetTextLength(); 1169 if (new_sel.cpMax > length) { 1170 new_sel.cpMax = length; 1171 if (new_sel.cpMin > length) 1172 new_sel.cpMin = length; 1173 SetSel(new_sel); 1174 } 1175 1176 string16 new_text(GetText()); 1177 if (new_text != text_before_change_) { 1178 if (ime_discard_composition_ && ime_composition_start_ >= 0 && 1179 ime_composition_length_ > 0) { 1180 // A string retrieved with a GetText() call contains a string being 1181 // composed by an IME. We remove the composition string from this search 1182 // string. 1183 new_text.erase(ime_composition_start_, ime_composition_length_); 1184 ime_composition_start_ = 0; 1185 ime_composition_length_ = 0; 1186 if (new_text.empty()) 1187 return; 1188 } 1189 textfield_->SyncText(); 1190 UpdateAccessibleValue(textfield_->text()); 1191 1192 if (should_redraw_text) { 1193 CHARRANGE original_sel; 1194 GetSel(original_sel); 1195 string16 text(GetText()); 1196 ScopedSuspendUndo suspend_undo(GetTextObjectModel()); 1197 1198 SelectAll(true); 1199 ReplaceSel(reinterpret_cast<LPCTSTR>(text.c_str()), true); 1200 SetSel(original_sel); 1201 } 1202 } 1203 } 1204 1205 LONG NativeTextfieldWin::ClipXCoordToVisibleText(LONG x, 1206 bool is_triple_click) const { 1207 // Clip the X coordinate to the left edge of the text. Careful: 1208 // PosFromChar(0) may return a negative X coordinate if the beginning of the 1209 // text has scrolled off the edit, so don't go past the clip rect's edge. 1210 PARAFORMAT2 pf2; 1211 GetParaFormat(pf2); 1212 // Calculation of the clipped coordinate is more complicated if the paragraph 1213 // layout is RTL layout, or if there is RTL characters inside the LTR layout 1214 // paragraph. 1215 bool ltr_text_in_ltr_layout = true; 1216 if ((pf2.wEffects & PFE_RTLPARA) || 1217 base::i18n::StringContainsStrongRTLChars(GetText())) { 1218 ltr_text_in_ltr_layout = false; 1219 } 1220 const int length = GetTextLength(); 1221 RECT r; 1222 GetRect(&r); 1223 // The values returned by PosFromChar() seem to refer always 1224 // to the left edge of the character's bounding box. 1225 const LONG first_position_x = PosFromChar(0).x; 1226 LONG min_x = first_position_x; 1227 if (!ltr_text_in_ltr_layout) { 1228 for (int i = 1; i < length; ++i) 1229 min_x = std::min(min_x, PosFromChar(i).x); 1230 } 1231 const LONG left_bound = std::max(r.left, min_x); 1232 1233 // PosFromChar(length) is a phantom character past the end of the text. It is 1234 // not necessarily a right bound; in RTL controls it may be a left bound. So 1235 // treat it as a right bound only if it is to the right of the first 1236 // character. 1237 LONG right_bound = r.right; 1238 LONG end_position_x = PosFromChar(length).x; 1239 if (end_position_x >= first_position_x) { 1240 right_bound = std::min(right_bound, end_position_x); // LTR case. 1241 } 1242 // For trailing characters that are 2 pixels wide of less (like "l" in some 1243 // fonts), we have a problem: 1244 // * Clicks on any pixel within the character will place the cursor before 1245 // the character. 1246 // * Clicks on the pixel just after the character will not allow triple- 1247 // click to work properly (true for any last character width). 1248 // So, we move to the last pixel of the character when this is a 1249 // triple-click, and moving to one past the last pixel in all other 1250 // scenarios. This way, all clicks that can move the cursor will place it at 1251 // the end of the text, but triple-click will still work. 1252 if (x < left_bound) { 1253 return (is_triple_click && ltr_text_in_ltr_layout) ? left_bound - 1 : 1254 left_bound; 1255 } 1256 if ((length == 0) || (x < right_bound)) 1257 return x; 1258 return is_triple_click ? (right_bound - 1) : right_bound; 1259 } 1260 1261 void NativeTextfieldWin::SetContainsMouse(bool contains_mouse) { 1262 if (contains_mouse == contains_mouse_) 1263 return; 1264 1265 contains_mouse_ = contains_mouse; 1266 1267 if (!textfield_->draw_border()) 1268 return; 1269 1270 if (contains_mouse_) { 1271 // Register for notification when the mouse leaves. Need to do this so 1272 // that we can reset contains mouse properly. 1273 TRACKMOUSEEVENT tme; 1274 tme.cbSize = sizeof(tme); 1275 tme.dwFlags = TME_LEAVE; 1276 tme.hwndTrack = m_hWnd; 1277 tme.dwHoverTime = 0; 1278 TrackMouseEvent(&tme); 1279 } 1280 RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_FRAME); 1281 } 1282 1283 ITextDocument* NativeTextfieldWin::GetTextObjectModel() const { 1284 if (!text_object_model_) { 1285 base::win::ScopedComPtr<IRichEditOle, &IID_IRichEditOle> ole_interface; 1286 ole_interface.Attach(GetOleInterface()); 1287 if (ole_interface) 1288 text_object_model_.QueryFrom(ole_interface); 1289 } 1290 return text_object_model_; 1291 } 1292 1293 void NativeTextfieldWin::BuildContextMenu() { 1294 if (context_menu_contents_.get()) 1295 return; 1296 context_menu_contents_.reset(new ui::SimpleMenuModel(this)); 1297 context_menu_contents_->AddItemWithStringId(IDS_APP_UNDO, IDS_APP_UNDO); 1298 context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR); 1299 context_menu_contents_->AddItemWithStringId(IDS_APP_CUT, IDS_APP_CUT); 1300 context_menu_contents_->AddItemWithStringId(IDS_APP_COPY, IDS_APP_COPY); 1301 context_menu_contents_->AddItemWithStringId(IDS_APP_PASTE, IDS_APP_PASTE); 1302 context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR); 1303 context_menu_contents_->AddItemWithStringId(IDS_APP_SELECT_ALL, 1304 IDS_APP_SELECT_ALL); 1305 } 1306 1307 bool NativeTextfieldWin::ShouldProcessMouseEvent() { 1308 TextfieldController* controller = textfield_->GetController(); 1309 if (!controller) 1310 return true; 1311 MSG msg(*GetCurrentMessage()); 1312 // ATL doesn't set the |time| field. 1313 if (!msg.time) 1314 msg.time = GetMessageTime(); 1315 ui::MouseEvent mouse_event(msg); 1316 return !controller->HandleMouseEvent(textfield_, mouse_event); 1317 } 1318 1319 } // namespace views 1320