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