diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-28 20:54:06 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-28 20:54:06 +0000 |
commit | 9163266850ce5d2c871afbd362940a2d04246e23 (patch) | |
tree | 31fdefc8be174c686bb02539ba2672be844fbaed /views | |
parent | b7f02c6a9cb5cb58af781761b34895f40d35ddf1 (diff) | |
download | chromium_src-9163266850ce5d2c871afbd362940a2d04246e23.zip chromium_src-9163266850ce5d2c871afbd362940a2d04246e23.tar.gz chromium_src-9163266850ce5d2c871afbd362940a2d04246e23.tar.bz2 |
Make Textfield more portable.
Split off Windows bits into NativeTextfieldWin class obscured behind NativeTextfieldWrapper interface.
The APIs aren't perfectly tidy yet but this is a good first pass.
BUG=none
TEST=make sure you can still edit text fields, run the view unit tests.
Review URL: http://codereview.chromium.org/113940
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@17112 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views')
-rw-r--r-- | views/controls/message_box_view.cc | 2 | ||||
-rw-r--r-- | views/controls/textfield/native_textfield_win.cc | 832 | ||||
-rw-r--r-- | views/controls/textfield/native_textfield_win.h | 204 | ||||
-rw-r--r-- | views/controls/textfield/native_textfield_wrapper.h | 77 | ||||
-rw-r--r-- | views/controls/textfield/textfield.cc | 1241 | ||||
-rw-r--r-- | views/controls/textfield/textfield.h | 161 | ||||
-rw-r--r-- | views/view_unittest.cc | 36 |
7 files changed, 1346 insertions, 1207 deletions
diff --git a/views/controls/message_box_view.cc b/views/controls/message_box_view.cc index 064f644..8d68e4f 100644 --- a/views/controls/message_box_view.cc +++ b/views/controls/message_box_view.cc @@ -47,7 +47,7 @@ MessageBoxView::MessageBoxView(int dialog_flags, std::wstring MessageBoxView::GetInputText() { if (prompt_field_) - return prompt_field_->GetText(); + return prompt_field_->text(); return EmptyWString(); } diff --git a/views/controls/textfield/native_textfield_win.cc b/views/controls/textfield/native_textfield_win.cc index e69de29..33fc104 100644 --- a/views/controls/textfield/native_textfield_win.cc +++ b/views/controls/textfield/native_textfield_win.cc @@ -0,0 +1,832 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "app/l10n_util.h" +#include "app/l10n_util_win.h" +#include "app/win_util.h" +#include "base/clipboard.h" +#include "base/gfx/native_theme.h" +#include "base/scoped_clipboard_writer.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "grit/app_strings.h" +#include "skia/ext/skia_utils_win.h" +#include "views/controls/hwnd_view.h" +#include "views/controls/menu/menu_win.h" +#include "views/controls/textfield/native_textfield_win.h" +#include "views/controls/textfield/textfield.h" +#include "views/focus/focus_util_win.h" +#include "views/views_delegate.h" +#include "views/widget/widget.h" + +namespace views { + +/////////////////////////////////////////////////////////////////////////////// +// Helper classes + +NativeTextfieldWin::ScopedFreeze::ScopedFreeze(NativeTextfieldWin* edit, + ITextDocument* text_object_model) + : edit_(edit), + text_object_model_(text_object_model) { + // Freeze the screen. + if (text_object_model_) { + long count; + text_object_model_->Freeze(&count); + } +} + +NativeTextfieldWin::ScopedFreeze::~ScopedFreeze() { + // Unfreeze the screen. + if (text_object_model_) { + long count; + text_object_model_->Unfreeze(&count); + if (count == 0) { + // We need to UpdateWindow() here instead of InvalidateRect() because, as + // far as I can tell, the edit likes to synchronously erase its background + // when unfreezing, thus requiring us to synchronously redraw if we don't + // want flicker. + edit_->UpdateWindow(); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// NativeTextfieldWin + +bool NativeTextfieldWin::did_load_library_ = false; + +NativeTextfieldWin::NativeTextfieldWin(Textfield* textfield) + : textfield_(textfield), + tracking_double_click_(false), + double_click_time_(0), + can_discard_mousemove_(false), + contains_mouse_(false), + ime_discard_composition_(false), + ime_composition_start_(0), + ime_composition_length_(0), + bg_color_(0) { + if (!did_load_library_) + did_load_library_ = !!LoadLibrary(L"riched20.dll"); + + DWORD style = kDefaultEditStyle; + if (textfield_->style() & Textfield::STYLE_PASSWORD) + style |= ES_PASSWORD; + + if (textfield_->read_only()) + style |= ES_READONLY; + + if (textfield_->style() & Textfield::STYLE_MULTILINE) + style |= ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL; + else + style |= ES_AUTOHSCROLL; + // Make sure we apply RTL related extended window styles if necessary. + DWORD ex_style = l10n_util::GetExtendedStyles(); + + RECT r = {0, 0, textfield_->width(), textfield_->height()}; + Create(textfield_->GetWidget()->GetNativeView(), r, NULL, style, ex_style); + + if (textfield_->style() & Textfield::STYLE_LOWERCASE) { + DCHECK((textfield_->style() & Textfield::STYLE_PASSWORD) == 0); + SetEditStyle(SES_LOWERCASE, SES_LOWERCASE); + } + + // Set up the text_object_model_. + CComPtr<IRichEditOle> ole_interface; + ole_interface.Attach(GetOleInterface()); + text_object_model_ = ole_interface; + + context_menu_.reset(new MenuWin(this, Menu::TOPLEFT, m_hWnd)); + context_menu_->AppendMenuItemWithLabel(IDS_APP_UNDO, + l10n_util::GetString(IDS_APP_UNDO)); + context_menu_->AppendSeparator(); + context_menu_->AppendMenuItemWithLabel(IDS_APP_CUT, + l10n_util::GetString(IDS_APP_CUT)); + context_menu_->AppendMenuItemWithLabel(IDS_APP_COPY, + l10n_util::GetString(IDS_APP_COPY)); + context_menu_->AppendMenuItemWithLabel(IDS_APP_PASTE, + l10n_util::GetString(IDS_APP_PASTE)); + context_menu_->AppendSeparator(); + context_menu_->AppendMenuItemWithLabel( + IDS_APP_SELECT_ALL, + l10n_util::GetString(IDS_APP_SELECT_ALL)); + + container_view_ = new HWNDView; + textfield_->AddChildView(container_view_); + container_view_->SetAssociatedFocusView(textfield_); + container_view_->Attach(m_hWnd); +} + +NativeTextfieldWin::~NativeTextfieldWin() { + if (IsWindow()) + DestroyWindow(); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeTextfieldWin, NativeTextfieldWrapper implementation: + +std::wstring NativeTextfieldWin::GetText() const { + int len = GetTextLength() + 1; + std::wstring str; + GetWindowText(WriteInto(&str, len), len); + return str; +} + +void NativeTextfieldWin::UpdateText() { + std::wstring text = textfield_->text(); + // Adjusting the string direction before setting the text in order to make + // sure both RTL and LTR strings are displayed properly. + std::wstring text_to_set; + if (!l10n_util::AdjustStringForLocaleDirection(text, &text_to_set)) + text_to_set = text; + if (textfield_->style() & Textfield::STYLE_LOWERCASE) + text_to_set = l10n_util::ToLower(text_to_set); + SetWindowText(text_to_set.c_str()); +} + +void NativeTextfieldWin::AppendText(const std::wstring& text) { + int text_length = GetWindowTextLength(); + ::SendMessage(m_hWnd, TBM_SETSEL, true, MAKELPARAM(text_length, text_length)); + ::SendMessage(m_hWnd, EM_REPLACESEL, false, + reinterpret_cast<LPARAM>(text.c_str())); +} + +std::wstring NativeTextfieldWin::GetSelectedText() const { + // Figure out the length of the selection. + long start; + long end; + GetSel(start, end); + + // Grab the selected text. + std::wstring str; + GetSelText(WriteInto(&str, end - start + 1)); + + return str; +} + +void NativeTextfieldWin::SelectAll() { + // Select from the end to the front so that the first part of the text is + // always visible. + SetSel(GetTextLength(), 0); +} + +void NativeTextfieldWin::ClearSelection() { + SetSel(GetTextLength(), GetTextLength()); +} + +void NativeTextfieldWin::UpdateBorder() { + SetWindowPos(NULL, 0, 0, 0, 0, + SWP_NOMOVE | SWP_FRAMECHANGED | SWP_NOACTIVATE | + SWP_NOOWNERZORDER | SWP_NOSIZE); +} + +void NativeTextfieldWin::UpdateBackgroundColor() { + if (!textfield_->use_default_background_color()) { + bg_color_ = skia::SkColorToCOLORREF(textfield_->background_color()); + } else { + bg_color_ = GetSysColor(textfield_->read_only() ? COLOR_3DFACE + : COLOR_WINDOW); + } + CRichEditCtrl::SetBackgroundColor(bg_color_); +} + +void NativeTextfieldWin::UpdateReadOnly() { + SendMessage(m_hWnd, EM_SETREADONLY, textfield_->read_only(), 0); +} + +void NativeTextfieldWin::UpdateFont() { + SendMessage(m_hWnd, WM_SETFONT, + reinterpret_cast<WPARAM>(textfield_->font().hfont()), TRUE); +} + +void NativeTextfieldWin::UpdateEnabled() { + SendMessage(m_hWnd, WM_ENABLE, textfield_->IsEnabled(), 0); +} + +void NativeTextfieldWin::SetHorizontalMargins(int left, int right) { + // SendMessage expects the two values to be packed into one using MAKELONG + // so we truncate to 16 bits if necessary. + SendMessage(m_hWnd, EM_SETMARGINS, + EC_LEFTMARGIN | EC_RIGHTMARGIN, + MAKELONG(left & 0xFFFF, right & 0xFFFF)); +} + +void NativeTextfieldWin::SetFocus() { + // Focus the associated HWND. + //container_view_->Focus(); + ::SetFocus(m_hWnd); +} + +View* NativeTextfieldWin::GetView() { + return container_view_; +} + +gfx::NativeView NativeTextfieldWin::GetTestingHandle() const { + return m_hWnd; +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeTextfieldWin, Menu::Delegate implementation: + +bool NativeTextfieldWin::IsCommandEnabled(int id) const { + switch (id) { + case IDS_APP_UNDO: return !textfield_->read_only() && !!CanUndo(); + case IDS_APP_CUT: return !textfield_->read_only() && + !textfield_->IsPassword() && !!CanCut(); + case IDS_APP_COPY: return !!CanCopy() && !textfield_->IsPassword(); + case IDS_APP_PASTE: return !textfield_->read_only() && !!CanPaste(); + case IDS_APP_SELECT_ALL: return !!CanSelectAll(); + default: NOTREACHED(); + return false; + } +} + +void NativeTextfieldWin::ExecuteCommand(int id) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + switch (id) { + case IDS_APP_UNDO: Undo(); break; + case IDS_APP_CUT: Cut(); break; + case IDS_APP_COPY: Copy(); break; + case IDS_APP_PASTE: Paste(); break; + case IDS_APP_SELECT_ALL: SelectAll(); break; + default: NOTREACHED(); break; + } + OnAfterPossibleChange(); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeTextfieldWin, private: + +void NativeTextfieldWin::OnChar(TCHAR ch, UINT repeat_count, UINT flags) { + HandleKeystroke(GetCurrentMessage()->message, ch, repeat_count, flags); +} + +void NativeTextfieldWin::OnContextMenu(HWND window, const CPoint& point) { + CPoint p(point); + if (point.x == -1 || point.y == -1) { + GetCaretPos(&p); + MapWindowPoints(HWND_DESKTOP, &p, 1); + } + context_menu_->RunMenuAt(p.x, p.y); +} + +void NativeTextfieldWin::OnCopy() { + if (textfield_->IsPassword()) + return; + + const std::wstring text(GetSelectedText()); + + if (!text.empty() && ViewsDelegate::views_delegate) { + ScopedClipboardWriter scw(ViewsDelegate::views_delegate->GetClipboard()); + scw.WriteText(text); + } +} + +void NativeTextfieldWin::OnCut() { + if (textfield_->read_only() || textfield_->IsPassword()) + return; + + OnCopy(); + + // This replace selection will have no effect (even on the undo stack) if the + // current selection is empty. + ReplaceSel(L"", true); +} + +LRESULT NativeTextfieldWin::OnImeChar(UINT message, WPARAM wparam, LPARAM lparam) { + // http://crbug.com/7707: a rich-edit control may crash when it receives a + // WM_IME_CHAR message while it is processing a WM_IME_COMPOSITION message. + // Since view controls don't need WM_IME_CHAR messages, we prevent WM_IME_CHAR + // messages from being dispatched to view controls via the CallWindowProc() + // call. + return 0; +} + +LRESULT NativeTextfieldWin::OnImeStartComposition(UINT message, + WPARAM wparam, + LPARAM lparam) { + // Users may press alt+shift or control+shift keys to change their keyboard + // layouts. So, we retrieve the input locale identifier everytime we start + // an IME composition. + int language_id = PRIMARYLANGID(GetKeyboardLayout(0)); + ime_discard_composition_ = + language_id == LANG_JAPANESE || language_id == LANG_CHINESE; + ime_composition_start_ = 0; + ime_composition_length_ = 0; + + return DefWindowProc(message, wparam, lparam); +} + +LRESULT NativeTextfieldWin::OnImeComposition(UINT message, + WPARAM wparam, + LPARAM lparam) { + text_before_change_.clear(); + LRESULT result = DefWindowProc(message, wparam, lparam); + + ime_composition_start_ = 0; + ime_composition_length_ = 0; + if (ime_discard_composition_) { + // Call IMM32 functions to retrieve the position and the length of the + // ongoing composition string and notify the OnAfterPossibleChange() + // function that it should discard the composition string from a search + // string. We should not call IMM32 functions in the function because it + // is called when an IME is not composing a string. + HIMC imm_context = ImmGetContext(m_hWnd); + if (imm_context) { + CHARRANGE selection; + GetSel(selection); + const int cursor_position = + ImmGetCompositionString(imm_context, GCS_CURSORPOS, NULL, 0); + if (cursor_position >= 0) + ime_composition_start_ = selection.cpMin - cursor_position; + + const int composition_size = + ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0); + if (composition_size >= 0) + ime_composition_length_ = composition_size / sizeof(wchar_t); + + ImmReleaseContext(m_hWnd, imm_context); + } + } + + OnAfterPossibleChange(); + return result; +} + +LRESULT NativeTextfieldWin::OnImeEndComposition(UINT message, + WPARAM wparam, + LPARAM lparam) { + // Bug 11863: Korean IMEs send a WM_IME_ENDCOMPOSITION message without + // sending any WM_IME_COMPOSITION messages when a user deletes all + // composition characters, i.e. a composition string becomes empty. To handle + // this case, we need to update the find results when a composition is + // finished or canceled. + textfield_->SyncText(); + if (textfield_->GetController()) + textfield_->GetController()->ContentsChanged(textfield_, GetText()); + return DefWindowProc(message, wparam, lparam); +} + +void NativeTextfieldWin::OnKeyDown(TCHAR key, UINT repeat_count, UINT flags) { + // NOTE: Annoyingly, ctrl-alt-<key> generates WM_KEYDOWN rather than + // WM_SYSKEYDOWN, so we need to check (flags & KF_ALTDOWN) in various places + // in this function even with a WM_SYSKEYDOWN handler. + + switch (key) { + case VK_RETURN: + // If we are multi-line, we want to let returns through so they start a + // new line. + if (textfield_->IsMultiLine()) + break; + else + return; + // Hijacking Editing Commands + // + // We hijack the keyboard short-cuts for Cut, Copy, and Paste here so that + // they go through our clipboard routines. This allows us to be smarter + // about how we interact with the clipboard and avoid bugs in the + // CRichEditCtrl. If we didn't hijack here, the edit control would handle + // these internally with sending the WM_CUT, WM_COPY, or WM_PASTE messages. + // + // Cut: Shift-Delete and Ctrl-x are treated as cut. Ctrl-Shift-Delete and + // Ctrl-Shift-x are not treated as cut even though the underlying + // CRichTextEdit would treat them as such. + // Copy: Ctrl-c is treated as copy. Shift-Ctrl-c is not. + // Paste: Shift-Insert and Ctrl-v are tread as paste. Ctrl-Shift-Insert and + // Ctrl-Shift-v are not. + // + // This behavior matches most, but not all Windows programs, and largely + // conforms to what users expect. + + case VK_DELETE: + case 'X': + if ((flags & KF_ALTDOWN) || + (GetKeyState((key == 'X') ? VK_CONTROL : VK_SHIFT) >= 0)) + break; + if (GetKeyState((key == 'X') ? VK_SHIFT : VK_CONTROL) >= 0) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + Cut(); + OnAfterPossibleChange(); + } + return; + + case 'C': + if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0)) + break; + if (GetKeyState(VK_SHIFT) >= 0) + Copy(); + return; + + case VK_INSERT: + case 'V': + if ((flags & KF_ALTDOWN) || + (GetKeyState((key == 'V') ? VK_CONTROL : VK_SHIFT) >= 0)) + break; + if (GetKeyState((key == 'V') ? VK_SHIFT : VK_CONTROL) >= 0) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + Paste(); + OnAfterPossibleChange(); + } + return; + + case 0xbb: // Ctrl-'='. Triggers subscripting, even in plain text mode. + return; + + case VK_PROCESSKEY: + // This key event is consumed by an IME. + // We ignore this event because an IME sends WM_IME_COMPOSITION messages + // when it updates the CRichEditCtrl text. + return; + } + + // CRichEditCtrl changes its text on WM_KEYDOWN instead of WM_CHAR for many + // different keys (backspace, ctrl-v, ...), so we call this in both cases. + HandleKeystroke(GetCurrentMessage()->message, key, repeat_count, flags); +} + +void NativeTextfieldWin::OnLButtonDblClk(UINT keys, const CPoint& point) { + // Save the double click info for later triple-click detection. + tracking_double_click_ = true; + double_click_point_ = point; + double_click_time_ = GetCurrentMessage()->time; + + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + DefWindowProc(WM_LBUTTONDBLCLK, keys, + MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); + OnAfterPossibleChange(); +} + +void NativeTextfieldWin::OnLButtonDown(UINT keys, const CPoint& point) { + // Check for triple click, then reset tracker. Should be safe to subtract + // double_click_time_ from the current message's time even if the timer has + // wrapped in between. + const bool is_triple_click = tracking_double_click_ && + win_util::IsDoubleClick(double_click_point_, point, + GetCurrentMessage()->time - double_click_time_); + tracking_double_click_ = false; + + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + DefWindowProc(WM_LBUTTONDOWN, keys, + MAKELPARAM(ClipXCoordToVisibleText(point.x, is_triple_click), + point.y)); + OnAfterPossibleChange(); +} + +void NativeTextfieldWin::OnLButtonUp(UINT keys, const CPoint& point) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + DefWindowProc(WM_LBUTTONUP, keys, + MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); + OnAfterPossibleChange(); +} + +void NativeTextfieldWin::OnMouseLeave() { + SetContainsMouse(false); +} + +LRESULT NativeTextfieldWin::OnMouseWheel(UINT message, WPARAM w_param, + LPARAM l_param) { + // Reroute the mouse-wheel to the window under the mouse pointer if + // applicable. + if (views::RerouteMouseWheel(m_hWnd, w_param, l_param)) + return 0; + return DefWindowProc(message, w_param, l_param);; +} + +void NativeTextfieldWin::OnMouseMove(UINT keys, const CPoint& point) { + SetContainsMouse(true); + // Clamp the selection to the visible text so the user can't drag to select + // the "phantom newline". In theory we could achieve this by clipping the X + // coordinate, but in practice the edit seems to behave nondeterministically + // with similar sequences of clipped input coordinates fed to it. Maybe it's + // reading the mouse cursor position directly? + // + // This solution has a minor visual flaw, however: if there's a visible + // cursor at the edge of the text (only true when there's no selection), + // dragging the mouse around outside that edge repaints the cursor on every + // WM_MOUSEMOVE instead of allowing it to blink normally. To fix this, we + // special-case this exact case and discard the WM_MOUSEMOVE messages instead + // of passing them along. + // + // But even this solution has a flaw! (Argh.) In the case where the user + // has a selection that starts at the edge of the edit, and proceeds to the + // middle of the edit, and the user is dragging back past the start edge to + // remove the selection, there's a redraw problem where the change between + // having the last few bits of text still selected and having nothing + // selected can be slow to repaint (which feels noticeably strange). This + // occurs if you only let the edit receive a single WM_MOUSEMOVE past the + // edge of the text. I think on each WM_MOUSEMOVE the edit is repainting its + // previous state, then updating its internal variables to the new state but + // not repainting. To fix this, we allow one more WM_MOUSEMOVE through after + // the selection has supposedly been shrunk to nothing; this makes the edit + // redraw the selection quickly so it feels smooth. + CHARRANGE selection; + GetSel(selection); + const bool possibly_can_discard_mousemove = + (selection.cpMin == selection.cpMax) && + (((selection.cpMin == 0) && + (ClipXCoordToVisibleText(point.x, false) > point.x)) || + ((selection.cpMin == GetTextLength()) && + (ClipXCoordToVisibleText(point.x, false) < point.x))); + if (!can_discard_mousemove_ || !possibly_can_discard_mousemove) { + can_discard_mousemove_ = possibly_can_discard_mousemove; + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + // Force the Y coordinate to the center of the clip rect. The edit + // behaves strangely when the cursor is dragged vertically: if the cursor + // is in the middle of the text, drags inside the clip rect do nothing, + // and drags outside the clip rect act as if the cursor jumped to the + // left edge of the text. When the cursor is at the right edge, drags of + // just a few pixels vertically end up selecting the "phantom newline"... + // sometimes. + RECT r; + GetRect(&r); + DefWindowProc(WM_MOUSEMOVE, keys, + MAKELPARAM(point.x, (r.bottom - r.top) / 2)); + OnAfterPossibleChange(); + } +} + +int NativeTextfieldWin::OnNCCalcSize(BOOL w_param, LPARAM l_param) { + content_insets_.Set(0, 0, 0, 0); + textfield_->CalculateInsets(&content_insets_); + if (w_param) { + NCCALCSIZE_PARAMS* nc_params = + reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param); + nc_params->rgrc[0].left += content_insets_.left(); + nc_params->rgrc[0].right -= content_insets_.right(); + nc_params->rgrc[0].top += content_insets_.top(); + nc_params->rgrc[0].bottom -= content_insets_.bottom(); + } else { + RECT* rect = reinterpret_cast<RECT*>(l_param); + rect->left += content_insets_.left(); + rect->right -= content_insets_.right(); + rect->top += content_insets_.top(); + rect->bottom -= content_insets_.bottom(); + } + return 0; +} + +void NativeTextfieldWin::OnNCPaint(HRGN region) { + if (!textfield_->draw_border()) + return; + + HDC hdc = GetWindowDC(); + + CRect window_rect; + GetWindowRect(&window_rect); + // Convert to be relative to 0x0. + window_rect.MoveToXY(0, 0); + + ExcludeClipRect(hdc, + window_rect.left + content_insets_.left(), + window_rect.top + content_insets_.top(), + window_rect.right - content_insets_.right(), + window_rect.bottom - content_insets_.bottom()); + + HBRUSH brush = CreateSolidBrush(bg_color_); + FillRect(hdc, &window_rect, brush); + DeleteObject(brush); + + int part; + int state; + + if (win_util::GetWinVersion() < win_util::WINVERSION_VISTA) { + part = EP_EDITTEXT; + + if (!textfield_->IsEnabled()) + state = ETS_DISABLED; + else if (textfield_->read_only()) + state = ETS_READONLY; + else if (!contains_mouse_) + state = ETS_NORMAL; + else + state = ETS_HOT; + } else { + part = EP_EDITBORDER_HVSCROLL; + + if (!textfield_->IsEnabled()) + state = EPSHV_DISABLED; + else if (GetFocus() == m_hWnd) + state = EPSHV_FOCUSED; + else if (contains_mouse_) + state = EPSHV_HOT; + else + state = EPSHV_NORMAL; + // Vista doesn't appear to have a unique state for readonly. + } + + int classic_state = + (!textfield_->IsEnabled() || textfield_->read_only()) ? DFCS_INACTIVE : 0; + + gfx::NativeTheme::instance()->PaintTextField(hdc, part, state, classic_state, + &window_rect, bg_color_, false, + true); + + // NOTE: I tried checking the transparent property of the theme and invoking + // drawParentBackground, but it didn't seem to make a difference. + + ReleaseDC(hdc); +} + +void NativeTextfieldWin::OnNonLButtonDown(UINT keys, const CPoint& point) { + // Interestingly, the edit doesn't seem to cancel triple clicking when the + // x-buttons (which usually means "thumb buttons") are pressed, so we only + // call this for M and R down. + tracking_double_click_ = false; + SetMsgHandled(false); +} + +void NativeTextfieldWin::OnPaste() { + if (textfield_->read_only() || !ViewsDelegate::views_delegate) + return; + + Clipboard* clipboard = ViewsDelegate::views_delegate->GetClipboard(); + if (!clipboard->IsFormatAvailable(Clipboard::GetPlainTextWFormatType())) + return; + + std::wstring clipboard_str; + clipboard->ReadText(&clipboard_str); + if (!clipboard_str.empty()) { + std::wstring collapsed(CollapseWhitespace(clipboard_str, false)); + if (textfield_->style() & Textfield::STYLE_LOWERCASE) + collapsed = l10n_util::ToLower(collapsed); + // Force a Paste operation to trigger OnContentsChanged, even if identical + // contents are pasted into the text box. + text_before_change_.clear(); + ReplaceSel(collapsed.c_str(), true); + } +} + +void NativeTextfieldWin::OnSysChar(TCHAR ch, UINT repeat_count, UINT flags) { + // Nearly all alt-<xxx> combos result in beeping rather than doing something + // useful, so we discard most. Exceptions: + // * ctrl-alt-<xxx>, which is sometimes important, generates WM_CHAR instead + // of WM_SYSCHAR, so it doesn't need to be handled here. + // * alt-space gets translated by the default WM_SYSCHAR handler to a + // WM_SYSCOMMAND to open the application context menu, so we need to allow + // it through. + if (ch == VK_SPACE) + SetMsgHandled(false); +} + +void NativeTextfieldWin::HandleKeystroke(UINT message, + TCHAR key, + UINT repeat_count, + UINT flags) { + ScopedFreeze freeze(this, GetTextObjectModel()); + + Textfield::Controller* controller = textfield_->GetController(); + bool handled = false; + if (controller) { + handled = controller->HandleKeystroke(textfield_, + Textfield::Keystroke(message, key, repeat_count, flags)); + } + + if (!handled) { + OnBeforePossibleChange(); + DefWindowProc(message, key, MAKELPARAM(repeat_count, flags)); + OnAfterPossibleChange(); + } +} + +void NativeTextfieldWin::OnBeforePossibleChange() { + // Record our state. + text_before_change_ = GetText(); +} + +void NativeTextfieldWin::OnAfterPossibleChange() { + // Prevent the user from selecting the "phantom newline" at the end of the + // edit. If they try, we just silently move the end of the selection back to + // the end of the real text. + CHARRANGE new_sel; + GetSel(new_sel); + const int length = GetTextLength(); + if (new_sel.cpMax > length) { + new_sel.cpMax = length; + if (new_sel.cpMin > length) + new_sel.cpMin = length; + SetSel(new_sel); + } + + std::wstring new_text(GetText()); + if (new_text != text_before_change_) { + if (ime_discard_composition_ && ime_composition_start_ >= 0 && + ime_composition_length_ > 0) { + // A string retrieved with a GetText() call contains a string being + // composed by an IME. We remove the composition string from this search + // string. + new_text.erase(ime_composition_start_, ime_composition_length_); + ime_composition_start_ = 0; + ime_composition_length_ = 0; + if (new_text.empty()) + return; + } + textfield_->SyncText(); + if (textfield_->GetController()) + textfield_->GetController()->ContentsChanged(textfield_, new_text); + } +} + +LONG NativeTextfieldWin::ClipXCoordToVisibleText(LONG x, + bool is_triple_click) const { + // Clip the X coordinate to the left edge of the text. Careful: + // PosFromChar(0) may return a negative X coordinate if the beginning of the + // text has scrolled off the edit, so don't go past the clip rect's edge. + PARAFORMAT2 pf2; + GetParaFormat(pf2); + // Calculation of the clipped coordinate is more complicated if the paragraph + // layout is RTL layout, or if there is RTL characters inside the LTR layout + // paragraph. + bool ltr_text_in_ltr_layout = true; + if ((pf2.wEffects & PFE_RTLPARA) || + l10n_util::StringContainsStrongRTLChars(GetText())) { + ltr_text_in_ltr_layout = false; + } + const int length = GetTextLength(); + RECT r; + GetRect(&r); + // The values returned by PosFromChar() seem to refer always + // to the left edge of the character's bounding box. + const LONG first_position_x = PosFromChar(0).x; + LONG min_x = first_position_x; + if (!ltr_text_in_ltr_layout) { + for (int i = 1; i < length; ++i) + min_x = std::min(min_x, PosFromChar(i).x); + } + const LONG left_bound = std::max(r.left, min_x); + + // PosFromChar(length) is a phantom character past the end of the text. It is + // not necessarily a right bound; in RTL controls it may be a left bound. So + // treat it as a right bound only if it is to the right of the first + // character. + LONG right_bound = r.right; + LONG end_position_x = PosFromChar(length).x; + if (end_position_x >= first_position_x) { + right_bound = std::min(right_bound, end_position_x); // LTR case. + } + // For trailing characters that are 2 pixels wide of less (like "l" in some + // fonts), we have a problem: + // * Clicks on any pixel within the character will place the cursor before + // the character. + // * Clicks on the pixel just after the character will not allow triple- + // click to work properly (true for any last character width). + // So, we move to the last pixel of the character when this is a + // triple-click, and moving to one past the last pixel in all other + // scenarios. This way, all clicks that can move the cursor will place it at + // the end of the text, but triple-click will still work. + if (x < left_bound) { + return (is_triple_click && ltr_text_in_ltr_layout) ? left_bound - 1 : + left_bound; + } + if ((length == 0) || (x < right_bound)) + return x; + return is_triple_click ? (right_bound - 1) : right_bound; +} + +void NativeTextfieldWin::SetContainsMouse(bool contains_mouse) { + if (contains_mouse == contains_mouse_) + return; + + contains_mouse_ = contains_mouse; + + if (!textfield_->draw_border()) + return; + + if (contains_mouse_) { + // Register for notification when the mouse leaves. Need to do this so + // that we can reset contains mouse properly. + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = m_hWnd; + tme.dwHoverTime = 0; + TrackMouseEvent(&tme); + } + RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_FRAME); +} + +ITextDocument* NativeTextfieldWin::GetTextObjectModel() const { + if (!text_object_model_) { + CComPtr<IRichEditOle> ole_interface; + ole_interface.Attach(GetOleInterface()); + text_object_model_ = ole_interface; + } + return text_object_model_; +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeTextfieldWrapper, public: + +// static +NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper( + Textfield* field) { + return new NativeTextfieldWin(field); +} + +} // namespace views diff --git a/views/controls/textfield/native_textfield_win.h b/views/controls/textfield/native_textfield_win.h index e69de29..b7c336f 100644 --- a/views/controls/textfield/native_textfield_win.h +++ b/views/controls/textfield/native_textfield_win.h @@ -0,0 +1,204 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_WIN_H_ +#define VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_WIN_H_ + +#include <atlbase.h> +#include <atlapp.h> +#include <atlcrack.h> +#include <atlctrls.h> +#include <atlmisc.h> +#include <tom.h> // For ITextDocument, a COM interface to CRichEditCtrl +#include <vsstyle.h> + +#include "views/controls/textfield/native_textfield_wrapper.h" + +namespace views { + +class HWNDView; +class Textfield; + +static const int kDefaultEditStyle = WS_CHILD | WS_VISIBLE; + +// TODO(beng): make a subclass of NativeControlWin instead. +class NativeTextfieldWin + : public CWindowImpl<NativeTextfieldWin, CRichEditCtrl, + CWinTraits<kDefaultEditStyle> >, + public CRichEditCommands<NativeTextfieldWin>, + public NativeTextfieldWrapper, + public Menu::Delegate { + public: + DECLARE_WND_CLASS(L"ViewsTextfieldEdit"); + + explicit NativeTextfieldWin(Textfield* parent); + ~NativeTextfieldWin(); + + // Overridden from NativeTextfieldWrapper: + virtual std::wstring GetText() const; + virtual void UpdateText(); + virtual void AppendText(const std::wstring& text); + virtual std::wstring GetSelectedText() const; + virtual void SelectAll(); + virtual void ClearSelection(); + virtual void UpdateBorder(); + virtual void UpdateBackgroundColor(); + virtual void UpdateReadOnly(); + virtual void UpdateFont(); + virtual void UpdateEnabled(); + virtual void SetHorizontalMargins(int left, int right); + virtual void SetFocus(); + virtual View* GetView(); + virtual gfx::NativeView GetTestingHandle() const; + + // CWindowImpl + BEGIN_MSG_MAP(Edit) + MSG_WM_CHAR(OnChar) + MSG_WM_CONTEXTMENU(OnContextMenu) + MSG_WM_COPY(OnCopy) + MSG_WM_CUT(OnCut) + MESSAGE_HANDLER_EX(WM_IME_CHAR, OnImeChar) + MESSAGE_HANDLER_EX(WM_IME_STARTCOMPOSITION, OnImeStartComposition) + MESSAGE_HANDLER_EX(WM_IME_COMPOSITION, OnImeComposition) + MESSAGE_HANDLER_EX(WM_IME_ENDCOMPOSITION, OnImeEndComposition) + MSG_WM_KEYDOWN(OnKeyDown) + MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk) + MSG_WM_LBUTTONDOWN(OnLButtonDown) + MSG_WM_LBUTTONUP(OnLButtonUp) + MSG_WM_MBUTTONDOWN(OnNonLButtonDown) + MSG_WM_MOUSEMOVE(OnMouseMove) + MSG_WM_MOUSELEAVE(OnMouseLeave) + MESSAGE_HANDLER_EX(WM_MOUSEWHEEL, OnMouseWheel) + MSG_WM_NCCALCSIZE(OnNCCalcSize) + MSG_WM_NCPAINT(OnNCPaint) + MSG_WM_RBUTTONDOWN(OnNonLButtonDown) + MSG_WM_PASTE(OnPaste) + MSG_WM_SYSCHAR(OnSysChar) // WM_SYSxxx == WM_xxx with ALT down + MSG_WM_SYSKEYDOWN(OnKeyDown) + END_MSG_MAP() + + // Menu::Delegate + virtual bool IsCommandEnabled(int id) const; + virtual void ExecuteCommand(int id); + + private: + // This object freezes repainting of the edit until the object is destroyed. + // Some methods of the CRichEditCtrl draw synchronously to the screen. If we + // don't freeze, the user will see a rapid series of calls to these as + // flickers. + // + // Freezing the control while it is already frozen is permitted; the control + // will unfreeze once both freezes are released (the freezes stack). + class ScopedFreeze { + public: + ScopedFreeze(NativeTextfieldWin* edit, ITextDocument* text_object_model); + ~ScopedFreeze(); + + private: + NativeTextfieldWin* const edit_; + ITextDocument* const text_object_model_; + + DISALLOW_COPY_AND_ASSIGN(ScopedFreeze); + }; + + // message handlers + void OnChar(TCHAR key, UINT repeat_count, UINT flags); + void OnContextMenu(HWND window, const CPoint& point); + void OnCopy(); + void OnCut(); + LRESULT OnImeChar(UINT message, WPARAM wparam, LPARAM lparam); + LRESULT OnImeStartComposition(UINT message, WPARAM wparam, LPARAM lparam); + LRESULT OnImeComposition(UINT message, WPARAM wparam, LPARAM lparam); + LRESULT OnImeEndComposition(UINT message, WPARAM wparam, LPARAM lparam); + void OnKeyDown(TCHAR key, UINT repeat_count, UINT flags); + void OnLButtonDblClk(UINT keys, const CPoint& point); + void OnLButtonDown(UINT keys, const CPoint& point); + void OnLButtonUp(UINT keys, const CPoint& point); + void OnMouseLeave(); + LRESULT OnMouseWheel(UINT message, WPARAM w_param, LPARAM l_param); + void OnMouseMove(UINT keys, const CPoint& point); + int OnNCCalcSize(BOOL w_param, LPARAM l_param); + void OnNCPaint(HRGN region); + void OnNonLButtonDown(UINT keys, const CPoint& point); + void OnPaste(); + void OnSysChar(TCHAR ch, UINT repeat_count, UINT flags); + + // Helper function for OnChar() and OnKeyDown() that handles keystrokes that + // could change the text in the edit. + void HandleKeystroke(UINT message, TCHAR key, UINT repeat_count, UINT flags); + + // Every piece of code that can change the edit should call these functions + // before and after the change. These functions determine if anything + // meaningful changed, and do any necessary updating and notification. + void OnBeforePossibleChange(); + void OnAfterPossibleChange(); + + // Given an X coordinate in client coordinates, returns that coordinate + // clipped to be within the horizontal bounds of the visible text. + // + // This is used in our mouse handlers to work around quirky behaviors of the + // underlying CRichEditCtrl like not supporting triple-click when the user + // doesn't click on the text itself. + // + // |is_triple_click| should be true iff this is the third click of a triple + // click. Sadly, we need to clip slightly differently in this case. + LONG ClipXCoordToVisibleText(LONG x, bool is_triple_click) const; + + // Sets whether the mouse is in the edit. As necessary this redraws the + // edit. + void SetContainsMouse(bool contains_mouse); + + // Getter for the text_object_model_, used by the ScopedFreeze class. Note + // that the pointer returned here is only valid as long as the Edit is still + // alive. + ITextDocument* GetTextObjectModel() const; + + // The Textfield this object is bound to. + Textfield* textfield_; + + // We need to know if the user triple-clicks, so track double click points + // and times so we can see if subsequent clicks are actually triple clicks. + bool tracking_double_click_; + CPoint double_click_point_; + DWORD double_click_time_; + + // Used to discard unnecessary WM_MOUSEMOVE events after the first such + // unnecessary event. See detailed comments in OnMouseMove(). + bool can_discard_mousemove_; + + // The text of this control before a possible change. + std::wstring text_before_change_; + + // If true, the mouse is over the edit. + bool contains_mouse_; + + static bool did_load_library_; + + // The context menu for the edit. + scoped_ptr<Menu> context_menu_; + + // Border insets. + gfx::Insets content_insets_; + + // This interface is useful for accessing the CRichEditCtrl at a low level. + mutable CComQIPtr<ITextDocument> text_object_model_; + + // The position and the length of the ongoing composition string. + // These values are used for removing a composition string from a search + // text to emulate Firefox. + bool ime_discard_composition_; + int ime_composition_start_; + int ime_composition_length_; + + // TODO(beng): remove this when we are a subclass of NativeControlWin. + HWNDView* container_view_; + + COLORREF bg_color_; + + DISALLOW_COPY_AND_ASSIGN(NativeTextfieldWin); +}; + +}; + +#endif // VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_WIN_H_ diff --git a/views/controls/textfield/native_textfield_wrapper.h b/views/controls/textfield/native_textfield_wrapper.h index e69de29..d1aba9a 100644 --- a/views/controls/textfield/native_textfield_wrapper.h +++ b/views/controls/textfield/native_textfield_wrapper.h @@ -0,0 +1,77 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this +// source code is governed by a BSD-style license that can be found in the +// LICENSE file. + +#ifndef VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_WRAPPER_H_ +#define VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_WRAPPER_H_ + +#include "base/gfx/native_widget_types.h" + +namespace views { + +class Textfield; +class View; + +// An interface implemented by an object that provides a platform-native +// text field. +class NativeTextfieldWrapper { + public: + // The Textfield calls this when it is destroyed to clean up the wrapper + // object. + virtual ~NativeTextfieldWrapper() {} + + // Gets the text displayed in the wrapped native text field. + virtual std::wstring GetText() const = 0; + + // Updates the text displayed with the text held by the Textfield. + virtual void UpdateText() = 0; + + // Adds the specified text to the text already displayed by the wrapped native + // text field. + virtual void AppendText(const std::wstring& text) = 0; + + // Gets the text that is selected in the wrapped native text field. + virtual std::wstring GetSelectedText() const = 0; + + // Selects all the text in the edit. Use this in place of SetSelAll() to + // avoid selecting the "phantom newline" at the end of the edit. + virtual void SelectAll() = 0; + + // Clears the selection within the edit field and sets the caret to the end. + virtual void ClearSelection() = 0; + + // Updates the border display for the native text field with the state desired + // by the Textfield. + virtual void UpdateBorder() = 0; + + // Updates the background color used when painting the native text field. + virtual void UpdateBackgroundColor() = 0; + + // Updates the read-only state of the native text field. + virtual void UpdateReadOnly() = 0; + + // Updates the font used to render text in the native text field. + virtual void UpdateFont() = 0; + + // Updates the enabled state of the native text field. + virtual void UpdateEnabled() = 0; + + // Sets the horizontal margins for the native text field. + virtual void SetHorizontalMargins(int left, int right) = 0; + + // Sets the focus to the text field. + virtual void SetFocus() = 0; + + // Retrieves the views::View that hosts the native control. + virtual View* GetView() = 0; + + // Returns a handle to the underlying native view for testing. + virtual gfx::NativeView GetTestingHandle() const = 0; + + // Creates an appropriate NativeTextfieldWrapper for the platform. + static NativeTextfieldWrapper* CreateWrapper(Textfield* field); +}; + +} // namespace views + +#endif // #ifndef VIEWS_CONTROLS_TEXTFIELD_NATIVE_TEXTFIELD_WRAPPER_H_ diff --git a/views/controls/textfield/textfield.cc b/views/controls/textfield/textfield.cc index 6675ce9..68afc91 100644 --- a/views/controls/textfield/textfield.cc +++ b/views/controls/textfield/textfield.cc @@ -4,1054 +4,139 @@ #include "views/controls/textfield/textfield.h" -#include <atlbase.h> -#include <atlapp.h> -#include <atlcrack.h> -#include <atlctrls.h> -#include <atlmisc.h> -#include <tom.h> // For ITextDocument, a COM interface to CRichEditCtrl -#include <vsstyle.h> - #include "app/gfx/insets.h" -#include "app/l10n_util.h" -#include "app/l10n_util_win.h" +#if defined(OS_WIN) #include "app/win_util.h" -#include "base/clipboard.h" -#include "base/gfx/native_theme.h" -#include "base/scoped_clipboard_writer.h" +#endif #include "base/string_util.h" -#include "base/win_util.h" -#include "grit/app_strings.h" -#include "skia/ext/skia_utils_win.h" -#include "views/controls/hwnd_view.h" -#include "views/controls/menu/menu_win.h" -#include "views/focus/focus_util_win.h" -#include "views/views_delegate.h" +#include "views/controls/textfield/native_textfield_wrapper.h" #include "views/widget/widget.h" -using gfx::NativeTheme; - namespace views { -static const int kDefaultEditStyle = WS_CHILD | WS_VISIBLE; - -class Textfield::Edit - : public CWindowImpl<Textfield::Edit, CRichEditCtrl, - CWinTraits<kDefaultEditStyle> >, - public CRichEditCommands<Textfield::Edit>, - public Menu::Delegate { - public: - DECLARE_WND_CLASS(L"ChromeViewsTextfieldEdit"); - - Edit(Textfield* parent, bool draw_border); - ~Edit(); - - std::wstring GetText() const; - void SetText(const std::wstring& text); - void AppendText(const std::wstring& text); - - std::wstring GetSelectedText() const; - - // Selects all the text in the edit. Use this in place of SetSelAll() to - // avoid selecting the "phantom newline" at the end of the edit. - void SelectAll(); - - // Clears the selection within the edit field and sets the caret to the end. - void ClearSelection(); - - // Removes the border. - void RemoveBorder(); - - void SetEnabled(bool enabled); - - void SetBackgroundColor(COLORREF bg_color); - - // CWindowImpl - BEGIN_MSG_MAP(Edit) - MSG_WM_CHAR(OnChar) - MSG_WM_CONTEXTMENU(OnContextMenu) - MSG_WM_COPY(OnCopy) - MSG_WM_CUT(OnCut) - MESSAGE_HANDLER_EX(WM_IME_CHAR, OnImeChar) - MESSAGE_HANDLER_EX(WM_IME_STARTCOMPOSITION, OnImeStartComposition) - MESSAGE_HANDLER_EX(WM_IME_COMPOSITION, OnImeComposition) - MESSAGE_HANDLER_EX(WM_IME_ENDCOMPOSITION, OnImeEndComposition) - MSG_WM_KEYDOWN(OnKeyDown) - MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk) - MSG_WM_LBUTTONDOWN(OnLButtonDown) - MSG_WM_LBUTTONUP(OnLButtonUp) - MSG_WM_MBUTTONDOWN(OnNonLButtonDown) - MSG_WM_MOUSEMOVE(OnMouseMove) - MSG_WM_MOUSELEAVE(OnMouseLeave) - MESSAGE_HANDLER_EX(WM_MOUSEWHEEL, OnMouseWheel) - MSG_WM_NCCALCSIZE(OnNCCalcSize) - MSG_WM_NCPAINT(OnNCPaint) - MSG_WM_RBUTTONDOWN(OnNonLButtonDown) - MSG_WM_PASTE(OnPaste) - MSG_WM_SYSCHAR(OnSysChar) // WM_SYSxxx == WM_xxx with ALT down - MSG_WM_SYSKEYDOWN(OnKeyDown) - END_MSG_MAP() - - // Menu::Delegate - virtual bool IsCommandEnabled(int id) const; - virtual void ExecuteCommand(int id); - - private: - // This object freezes repainting of the edit until the object is destroyed. - // Some methods of the CRichEditCtrl draw synchronously to the screen. If we - // don't freeze, the user will see a rapid series of calls to these as - // flickers. - // - // Freezing the control while it is already frozen is permitted; the control - // will unfreeze once both freezes are released (the freezes stack). - class ScopedFreeze { - public: - ScopedFreeze(Edit* edit, ITextDocument* text_object_model); - ~ScopedFreeze(); - - private: - Edit* const edit_; - ITextDocument* const text_object_model_; - - DISALLOW_COPY_AND_ASSIGN(ScopedFreeze); - }; - - // message handlers - void OnChar(TCHAR key, UINT repeat_count, UINT flags); - void OnContextMenu(HWND window, const CPoint& point); - void OnCopy(); - void OnCut(); - LRESULT OnImeChar(UINT message, WPARAM wparam, LPARAM lparam); - LRESULT OnImeStartComposition(UINT message, WPARAM wparam, LPARAM lparam); - LRESULT OnImeComposition(UINT message, WPARAM wparam, LPARAM lparam); - LRESULT OnImeEndComposition(UINT message, WPARAM wparam, LPARAM lparam); - void OnKeyDown(TCHAR key, UINT repeat_count, UINT flags); - void OnLButtonDblClk(UINT keys, const CPoint& point); - void OnLButtonDown(UINT keys, const CPoint& point); - void OnLButtonUp(UINT keys, const CPoint& point); - void OnMouseLeave(); - LRESULT OnMouseWheel(UINT message, WPARAM w_param, LPARAM l_param); - void OnMouseMove(UINT keys, const CPoint& point); - int OnNCCalcSize(BOOL w_param, LPARAM l_param); - void OnNCPaint(HRGN region); - void OnNonLButtonDown(UINT keys, const CPoint& point); - void OnPaste(); - void OnSysChar(TCHAR ch, UINT repeat_count, UINT flags); - - // Helper function for OnChar() and OnKeyDown() that handles keystrokes that - // could change the text in the edit. - void HandleKeystroke(UINT message, TCHAR key, UINT repeat_count, UINT flags); - - // Every piece of code that can change the edit should call these functions - // before and after the change. These functions determine if anything - // meaningful changed, and do any necessary updating and notification. - void OnBeforePossibleChange(); - void OnAfterPossibleChange(); - - // Given an X coordinate in client coordinates, returns that coordinate - // clipped to be within the horizontal bounds of the visible text. - // - // This is used in our mouse handlers to work around quirky behaviors of the - // underlying CRichEditCtrl like not supporting triple-click when the user - // doesn't click on the text itself. - // - // |is_triple_click| should be true iff this is the third click of a triple - // click. Sadly, we need to clip slightly differently in this case. - LONG ClipXCoordToVisibleText(LONG x, bool is_triple_click) const; - - // Sets whether the mouse is in the edit. As necessary this redraws the - // edit. - void SetContainsMouse(bool contains_mouse); - - // Getter for the text_object_model_, used by the ScopedFreeze class. Note - // that the pointer returned here is only valid as long as the Edit is still - // alive. - ITextDocument* GetTextObjectModel() const; - - // We need to know if the user triple-clicks, so track double click points - // and times so we can see if subsequent clicks are actually triple clicks. - bool tracking_double_click_; - CPoint double_click_point_; - DWORD double_click_time_; - - // Used to discard unnecessary WM_MOUSEMOVE events after the first such - // unnecessary event. See detailed comments in OnMouseMove(). - bool can_discard_mousemove_; - - // The text of this control before a possible change. - std::wstring text_before_change_; - - // If true, the mouse is over the edit. - bool contains_mouse_; - - static bool did_load_library_; - - Textfield* parent_; - - // The context menu for the edit. - scoped_ptr<Menu> context_menu_; - - // Border insets. - gfx::Insets content_insets_; - - // Whether the border is drawn. - bool draw_border_; - - // This interface is useful for accessing the CRichEditCtrl at a low level. - mutable CComQIPtr<ITextDocument> text_object_model_; - - // The position and the length of the ongoing composition string. - // These values are used for removing a composition string from a search - // text to emulate Firefox. - bool ime_discard_composition_; - int ime_composition_start_; - int ime_composition_length_; - - COLORREF bg_color_; - - DISALLOW_COPY_AND_ASSIGN(Edit); -}; - -/////////////////////////////////////////////////////////////////////////////// -// Helper classes - -Textfield::Edit::ScopedFreeze::ScopedFreeze(Textfield::Edit* edit, - ITextDocument* text_object_model) - : edit_(edit), - text_object_model_(text_object_model) { - // Freeze the screen. - if (text_object_model_) { - long count; - text_object_model_->Freeze(&count); - } -} - -Textfield::Edit::ScopedFreeze::~ScopedFreeze() { - // Unfreeze the screen. - if (text_object_model_) { - long count; - text_object_model_->Unfreeze(&count); - if (count == 0) { - // We need to UpdateWindow() here instead of InvalidateRect() because, as - // far as I can tell, the edit likes to synchronously erase its background - // when unfreezing, thus requiring us to synchronously redraw if we don't - // want flicker. - edit_->UpdateWindow(); - } - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Textfield::Edit - -bool Textfield::Edit::did_load_library_ = false; - -Textfield::Edit::Edit(Textfield* parent, bool draw_border) - : parent_(parent), - tracking_double_click_(false), - double_click_time_(0), - can_discard_mousemove_(false), - contains_mouse_(false), - draw_border_(draw_border), - ime_discard_composition_(false), - ime_composition_start_(0), - ime_composition_length_(0), - bg_color_(0) { - if (!did_load_library_) - did_load_library_ = !!LoadLibrary(L"riched20.dll"); - - DWORD style = kDefaultEditStyle; - if (parent->GetStyle() & Textfield::STYLE_PASSWORD) - style |= ES_PASSWORD; - - if (parent->read_only_) - style |= ES_READONLY; - - if (parent->GetStyle() & Textfield::STYLE_MULTILINE) - style |= ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL; - else - style |= ES_AUTOHSCROLL; - // Make sure we apply RTL related extended window styles if necessary. - DWORD ex_style = l10n_util::GetExtendedStyles(); - - RECT r = {0, 0, parent_->width(), parent_->height()}; - Create(parent_->GetWidget()->GetNativeView(), r, NULL, style, ex_style); - - if (parent->GetStyle() & Textfield::STYLE_LOWERCASE) { - DCHECK((parent->GetStyle() & Textfield::STYLE_PASSWORD) == 0); - SetEditStyle(SES_LOWERCASE, SES_LOWERCASE); - } - - // Set up the text_object_model_. - CComPtr<IRichEditOle> ole_interface; - ole_interface.Attach(GetOleInterface()); - text_object_model_ = ole_interface; - - context_menu_.reset(new MenuWin(this, Menu::TOPLEFT, m_hWnd)); - context_menu_->AppendMenuItemWithLabel(IDS_APP_UNDO, - l10n_util::GetString(IDS_APP_UNDO)); - context_menu_->AppendSeparator(); - context_menu_->AppendMenuItemWithLabel(IDS_APP_CUT, - l10n_util::GetString(IDS_APP_CUT)); - context_menu_->AppendMenuItemWithLabel(IDS_APP_COPY, - l10n_util::GetString(IDS_APP_COPY)); - context_menu_->AppendMenuItemWithLabel(IDS_APP_PASTE, - l10n_util::GetString(IDS_APP_PASTE)); - context_menu_->AppendSeparator(); - context_menu_->AppendMenuItemWithLabel(IDS_APP_SELECT_ALL, - l10n_util::GetString(IDS_APP_SELECT_ALL)); -} - -Textfield::Edit::~Edit() { -} - -std::wstring Textfield::Edit::GetText() const { - int len = GetTextLength() + 1; - std::wstring str; - GetWindowText(WriteInto(&str, len), len); - return str; -} - -void Textfield::Edit::SetText(const std::wstring& text) { - // Adjusting the string direction before setting the text in order to make - // sure both RTL and LTR strings are displayed properly. - std::wstring text_to_set; - if (!l10n_util::AdjustStringForLocaleDirection(text, &text_to_set)) - text_to_set = text; - if (parent_->GetStyle() & STYLE_LOWERCASE) - text_to_set = l10n_util::ToLower(text_to_set); - SetWindowText(text_to_set.c_str()); -} - -void Textfield::Edit::AppendText(const std::wstring& text) { - int text_length = GetWindowTextLength(); - ::SendMessage(m_hWnd, TBM_SETSEL, true, MAKELPARAM(text_length, text_length)); - ::SendMessage(m_hWnd, EM_REPLACESEL, false, - reinterpret_cast<LPARAM>(text.c_str())); -} - -std::wstring Textfield::Edit::GetSelectedText() const { - // Figure out the length of the selection. - long start; - long end; - GetSel(start, end); - - // Grab the selected text. - std::wstring str; - GetSelText(WriteInto(&str, end - start + 1)); - - return str; -} - -void Textfield::Edit::SelectAll() { - // Select from the end to the front so that the first part of the text is - // always visible. - SetSel(GetTextLength(), 0); -} - -void Textfield::Edit::ClearSelection() { - SetSel(GetTextLength(), GetTextLength()); -} - -void Textfield::Edit::RemoveBorder() { - if (!draw_border_) - return; - - draw_border_ = false; - SetWindowPos(NULL, 0, 0, 0, 0, - SWP_NOMOVE | SWP_FRAMECHANGED | SWP_NOACTIVATE | - SWP_NOOWNERZORDER | SWP_NOSIZE); -} - -void Textfield::Edit::SetEnabled(bool enabled) { - SendMessage(parent_->GetNativeComponent(), WM_ENABLE, - static_cast<WPARAM>(enabled), 0); -} - -// static -bool Textfield::IsKeystrokeEnter(const Keystroke& key) { - return key.key == VK_RETURN; -} - // static -bool Textfield::IsKeystrokeEscape(const Keystroke& key) { - return key.key == VK_ESCAPE; -} - -void Textfield::Edit::SetBackgroundColor(COLORREF bg_color) { - CRichEditCtrl::SetBackgroundColor(bg_color); - bg_color_ = bg_color; -} - -bool Textfield::Edit::IsCommandEnabled(int id) const { - switch (id) { - case IDS_APP_UNDO: return !parent_->IsReadOnly() && !!CanUndo(); - case IDS_APP_CUT: return !parent_->IsReadOnly() && - !parent_->IsPassword() && !!CanCut(); - case IDS_APP_COPY: return !!CanCopy() && !parent_->IsPassword(); - case IDS_APP_PASTE: return !parent_->IsReadOnly() && !!CanPaste(); - case IDS_APP_SELECT_ALL: return !!CanSelectAll(); - default: NOTREACHED(); - return false; - } -} - -void Textfield::Edit::ExecuteCommand(int id) { - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - switch (id) { - case IDS_APP_UNDO: Undo(); break; - case IDS_APP_CUT: Cut(); break; - case IDS_APP_COPY: Copy(); break; - case IDS_APP_PASTE: Paste(); break; - case IDS_APP_SELECT_ALL: SelectAll(); break; - default: NOTREACHED(); break; - } - OnAfterPossibleChange(); -} - -void Textfield::Edit::OnChar(TCHAR ch, UINT repeat_count, UINT flags) { - HandleKeystroke(GetCurrentMessage()->message, ch, repeat_count, flags); -} - -void Textfield::Edit::OnContextMenu(HWND window, const CPoint& point) { - CPoint p(point); - if (point.x == -1 || point.y == -1) { - GetCaretPos(&p); - MapWindowPoints(HWND_DESKTOP, &p, 1); - } - context_menu_->RunMenuAt(p.x, p.y); -} +const char Textfield::kViewClassName[] = "views/Textfield"; -void Textfield::Edit::OnCopy() { - if (parent_->IsPassword()) - return; - - const std::wstring text(GetSelectedText()); - - if (!text.empty() && ViewsDelegate::views_delegate) { - ScopedClipboardWriter scw(ViewsDelegate::views_delegate->GetClipboard()); - scw.WriteText(text); - } -} - -void Textfield::Edit::OnCut() { - if (parent_->IsReadOnly() || parent_->IsPassword()) - return; - - OnCopy(); - - // This replace selection will have no effect (even on the undo stack) if the - // current selection is empty. - ReplaceSel(L"", true); -} +///////////////////////////////////////////////////////////////////////////// +// Textfield -LRESULT Textfield::Edit::OnImeChar(UINT message, WPARAM wparam, LPARAM lparam) { - // http://crbug.com/7707: a rich-edit control may crash when it receives a - // WM_IME_CHAR message while it is processing a WM_IME_COMPOSITION message. - // Since view controls don't need WM_IME_CHAR messages, we prevent WM_IME_CHAR - // messages from being dispatched to view controls via the CallWindowProc() - // call. - return 0; +Textfield::Textfield() + : native_wrapper_(NULL), + controller_(NULL), + style_(STYLE_DEFAULT), + read_only_(false), + default_width_in_chars_(0), + draw_border_(true), + background_color_(SK_ColorWHITE), + use_default_background_color_(true), + num_lines_(1), + initialized_(false) { + SetFocusable(true); +} + +Textfield::Textfield(StyleFlags style) + : native_wrapper_(NULL), + controller_(NULL), + style_(style), + read_only_(false), + default_width_in_chars_(0), + draw_border_(true), + background_color_(SK_ColorWHITE), + use_default_background_color_(true), + num_lines_(1), + initialized_(false) { + SetFocusable(true); } -LRESULT Textfield::Edit::OnImeStartComposition(UINT message, - WPARAM wparam, - LPARAM lparam) { - // Users may press alt+shift or control+shift keys to change their keyboard - // layouts. So, we retrieve the input locale identifier everytime we start - // an IME composition. - int language_id = PRIMARYLANGID(GetKeyboardLayout(0)); - ime_discard_composition_ = - language_id == LANG_JAPANESE || language_id == LANG_CHINESE; - ime_composition_start_ = 0; - ime_composition_length_ = 0; - - return DefWindowProc(message, wparam, lparam); +Textfield::~Textfield() { + if (native_wrapper_) + delete native_wrapper_; } -LRESULT Textfield::Edit::OnImeComposition(UINT message, - WPARAM wparam, - LPARAM lparam) { - text_before_change_.clear(); - LRESULT result = DefWindowProc(message, wparam, lparam); - - ime_composition_start_ = 0; - ime_composition_length_ = 0; - if (ime_discard_composition_) { - // Call IMM32 functions to retrieve the position and the length of the - // ongoing composition string and notify the OnAfterPossibleChange() - // function that it should discard the composition string from a search - // string. We should not call IMM32 functions in the function because it - // is called when an IME is not composing a string. - HIMC imm_context = ImmGetContext(m_hWnd); - if (imm_context) { - CHARRANGE selection; - GetSel(selection); - const int cursor_position = - ImmGetCompositionString(imm_context, GCS_CURSORPOS, NULL, 0); - if (cursor_position >= 0) - ime_composition_start_ = selection.cpMin - cursor_position; - - const int composition_size = - ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0); - if (composition_size >= 0) - ime_composition_length_ = composition_size / sizeof(wchar_t); - - ImmReleaseContext(m_hWnd, imm_context); - } - } - - OnAfterPossibleChange(); - return result; +void Textfield::SetController(Controller* controller) { + controller_ = controller; } -LRESULT Textfield::Edit::OnImeEndComposition(UINT message, - WPARAM wparam, - LPARAM lparam) { - // Bug 11863: Korean IMEs send a WM_IME_ENDCOMPOSITION message without - // sending any WM_IME_COMPOSITION messages when a user deletes all - // composition characters, i.e. a composition string becomes empty. To handle - // this case, we need to update the find results when a composition is - // finished or canceled. - parent_->SyncText(); - if (parent_->GetController()) - parent_->GetController()->ContentsChanged(parent_, GetText()); - return DefWindowProc(message, wparam, lparam); +Textfield::Controller* Textfield::GetController() const { + return controller_; } -void Textfield::Edit::OnKeyDown(TCHAR key, UINT repeat_count, UINT flags) { - // NOTE: Annoyingly, ctrl-alt-<key> generates WM_KEYDOWN rather than - // WM_SYSKEYDOWN, so we need to check (flags & KF_ALTDOWN) in various places - // in this function even with a WM_SYSKEYDOWN handler. - - switch (key) { - case VK_RETURN: - // If we are multi-line, we want to let returns through so they start a - // new line. - if (parent_->IsMultiLine()) - break; - else - return; - // Hijacking Editing Commands - // - // We hijack the keyboard short-cuts for Cut, Copy, and Paste here so that - // they go through our clipboard routines. This allows us to be smarter - // about how we interact with the clipboard and avoid bugs in the - // CRichEditCtrl. If we didn't hijack here, the edit control would handle - // these internally with sending the WM_CUT, WM_COPY, or WM_PASTE messages. - // - // Cut: Shift-Delete and Ctrl-x are treated as cut. Ctrl-Shift-Delete and - // Ctrl-Shift-x are not treated as cut even though the underlying - // CRichTextEdit would treat them as such. - // Copy: Ctrl-c is treated as copy. Shift-Ctrl-c is not. - // Paste: Shift-Insert and Ctrl-v are tread as paste. Ctrl-Shift-Insert and - // Ctrl-Shift-v are not. - // - // This behavior matches most, but not all Windows programs, and largely - // conforms to what users expect. - - case VK_DELETE: - case 'X': - if ((flags & KF_ALTDOWN) || - (GetKeyState((key == 'X') ? VK_CONTROL : VK_SHIFT) >= 0)) - break; - if (GetKeyState((key == 'X') ? VK_SHIFT : VK_CONTROL) >= 0) { - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - Cut(); - OnAfterPossibleChange(); - } - return; - - case 'C': - if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0)) - break; - if (GetKeyState(VK_SHIFT) >= 0) - Copy(); - return; - - case VK_INSERT: - case 'V': - if ((flags & KF_ALTDOWN) || - (GetKeyState((key == 'V') ? VK_CONTROL : VK_SHIFT) >= 0)) - break; - if (GetKeyState((key == 'V') ? VK_SHIFT : VK_CONTROL) >= 0) { - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - Paste(); - OnAfterPossibleChange(); - } - return; - - case 0xbb: // Ctrl-'='. Triggers subscripting, even in plain text mode. - return; - - case VK_PROCESSKEY: - // This key event is consumed by an IME. - // We ignore this event because an IME sends WM_IME_COMPOSITION messages - // when it updates the CRichEditCtrl text. - return; +void Textfield::SetReadOnly(bool read_only) { + read_only_ = read_only; + if (native_wrapper_) { + native_wrapper_->UpdateReadOnly(); + native_wrapper_->UpdateBackgroundColor(); } - - // CRichEditCtrl changes its text on WM_KEYDOWN instead of WM_CHAR for many - // different keys (backspace, ctrl-v, ...), so we call this in both cases. - HandleKeystroke(GetCurrentMessage()->message, key, repeat_count, flags); -} - -void Textfield::Edit::OnLButtonDblClk(UINT keys, const CPoint& point) { - // Save the double click info for later triple-click detection. - tracking_double_click_ = true; - double_click_point_ = point; - double_click_time_ = GetCurrentMessage()->time; - - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - DefWindowProc(WM_LBUTTONDBLCLK, keys, - MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); - OnAfterPossibleChange(); -} - -void Textfield::Edit::OnLButtonDown(UINT keys, const CPoint& point) { - // Check for triple click, then reset tracker. Should be safe to subtract - // double_click_time_ from the current message's time even if the timer has - // wrapped in between. - const bool is_triple_click = tracking_double_click_ && - win_util::IsDoubleClick(double_click_point_, point, - GetCurrentMessage()->time - double_click_time_); - tracking_double_click_ = false; - - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - DefWindowProc(WM_LBUTTONDOWN, keys, - MAKELPARAM(ClipXCoordToVisibleText(point.x, is_triple_click), - point.y)); - OnAfterPossibleChange(); -} - -void Textfield::Edit::OnLButtonUp(UINT keys, const CPoint& point) { - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - DefWindowProc(WM_LBUTTONUP, keys, - MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); - OnAfterPossibleChange(); } -void Textfield::Edit::OnMouseLeave() { - SetContainsMouse(false); -} - -LRESULT Textfield::Edit::OnMouseWheel(UINT message, - WPARAM w_param, LPARAM l_param) { - // Reroute the mouse-wheel to the window under the mouse pointer if - // applicable. - if (views::RerouteMouseWheel(m_hWnd, w_param, l_param)) - return 0; - return DefWindowProc(message, w_param, l_param);; +bool Textfield::IsPassword() const { + return style_ & STYLE_PASSWORD; } -void Textfield::Edit::OnMouseMove(UINT keys, const CPoint& point) { - SetContainsMouse(true); - // Clamp the selection to the visible text so the user can't drag to select - // the "phantom newline". In theory we could achieve this by clipping the X - // coordinate, but in practice the edit seems to behave nondeterministically - // with similar sequences of clipped input coordinates fed to it. Maybe it's - // reading the mouse cursor position directly? - // - // This solution has a minor visual flaw, however: if there's a visible - // cursor at the edge of the text (only true when there's no selection), - // dragging the mouse around outside that edge repaints the cursor on every - // WM_MOUSEMOVE instead of allowing it to blink normally. To fix this, we - // special-case this exact case and discard the WM_MOUSEMOVE messages instead - // of passing them along. - // - // But even this solution has a flaw! (Argh.) In the case where the user - // has a selection that starts at the edge of the edit, and proceeds to the - // middle of the edit, and the user is dragging back past the start edge to - // remove the selection, there's a redraw problem where the change between - // having the last few bits of text still selected and having nothing - // selected can be slow to repaint (which feels noticeably strange). This - // occurs if you only let the edit receive a single WM_MOUSEMOVE past the - // edge of the text. I think on each WM_MOUSEMOVE the edit is repainting its - // previous state, then updating its internal variables to the new state but - // not repainting. To fix this, we allow one more WM_MOUSEMOVE through after - // the selection has supposedly been shrunk to nothing; this makes the edit - // redraw the selection quickly so it feels smooth. - CHARRANGE selection; - GetSel(selection); - const bool possibly_can_discard_mousemove = - (selection.cpMin == selection.cpMax) && - (((selection.cpMin == 0) && - (ClipXCoordToVisibleText(point.x, false) > point.x)) || - ((selection.cpMin == GetTextLength()) && - (ClipXCoordToVisibleText(point.x, false) < point.x))); - if (!can_discard_mousemove_ || !possibly_can_discard_mousemove) { - can_discard_mousemove_ = possibly_can_discard_mousemove; - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - // Force the Y coordinate to the center of the clip rect. The edit - // behaves strangely when the cursor is dragged vertically: if the cursor - // is in the middle of the text, drags inside the clip rect do nothing, - // and drags outside the clip rect act as if the cursor jumped to the - // left edge of the text. When the cursor is at the right edge, drags of - // just a few pixels vertically end up selecting the "phantom newline"... - // sometimes. - RECT r; - GetRect(&r); - DefWindowProc(WM_MOUSEMOVE, keys, - MAKELPARAM(point.x, (r.bottom - r.top) / 2)); - OnAfterPossibleChange(); - } +bool Textfield::IsMultiLine() const { + return !!(style_ & STYLE_MULTILINE); } -int Textfield::Edit::OnNCCalcSize(BOOL w_param, LPARAM l_param) { - content_insets_.Set(0, 0, 0, 0); - parent_->CalculateInsets(&content_insets_); - if (w_param) { - NCCALCSIZE_PARAMS* nc_params = - reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param); - nc_params->rgrc[0].left += content_insets_.left(); - nc_params->rgrc[0].right -= content_insets_.right(); - nc_params->rgrc[0].top += content_insets_.top(); - nc_params->rgrc[0].bottom -= content_insets_.bottom(); - } else { - RECT* rect = reinterpret_cast<RECT*>(l_param); - rect->left += content_insets_.left(); - rect->right -= content_insets_.right(); - rect->top += content_insets_.top(); - rect->bottom -= content_insets_.bottom(); - } - return 0; +void Textfield::SetText(const std::wstring& text) { + text_ = text; + if (native_wrapper_) + native_wrapper_->UpdateText(); } -void Textfield::Edit::OnNCPaint(HRGN region) { - if (!draw_border_) - return; - - HDC hdc = GetWindowDC(); - - CRect window_rect; - GetWindowRect(&window_rect); - // Convert to be relative to 0x0. - window_rect.MoveToXY(0, 0); - - ExcludeClipRect(hdc, - window_rect.left + content_insets_.left(), - window_rect.top + content_insets_.top(), - window_rect.right - content_insets_.right(), - window_rect.bottom - content_insets_.bottom()); - - HBRUSH brush = CreateSolidBrush(bg_color_); - FillRect(hdc, &window_rect, brush); - DeleteObject(brush); - - int part; - int state; - - if (win_util::GetWinVersion() < win_util::WINVERSION_VISTA) { - part = EP_EDITTEXT; - - if (!parent_->IsEnabled()) - state = ETS_DISABLED; - else if (parent_->IsReadOnly()) - state = ETS_READONLY; - else if (!contains_mouse_) - state = ETS_NORMAL; - else - state = ETS_HOT; - } else { - part = EP_EDITBORDER_HVSCROLL; - - if (!parent_->IsEnabled()) - state = EPSHV_DISABLED; - else if (GetFocus() == m_hWnd) - state = EPSHV_FOCUSED; - else if (contains_mouse_) - state = EPSHV_HOT; - else - state = EPSHV_NORMAL; - // Vista doesn't appear to have a unique state for readonly. - } - - int classic_state = - (!parent_->IsEnabled() || parent_->IsReadOnly()) ? DFCS_INACTIVE : 0; - - NativeTheme::instance()->PaintTextField(hdc, part, state, classic_state, - &window_rect, bg_color_, false, - true); - - // NOTE: I tried checking the transparent property of the theme and invoking - // drawParentBackground, but it didn't seem to make a difference. - - ReleaseDC(hdc); +void Textfield::AppendText(const std::wstring& text) { + text_ += text; + if (native_wrapper_) + native_wrapper_->AppendText(text); } -void Textfield::Edit::OnNonLButtonDown(UINT keys, const CPoint& point) { - // Interestingly, the edit doesn't seem to cancel triple clicking when the - // x-buttons (which usually means "thumb buttons") are pressed, so we only - // call this for M and R down. - tracking_double_click_ = false; - SetMsgHandled(false); +void Textfield::SelectAll() { + if (native_wrapper_) + native_wrapper_->SelectAll(); } -void Textfield::Edit::OnPaste() { - if (parent_->IsReadOnly() || !ViewsDelegate::views_delegate) - return; - - Clipboard* clipboard = ViewsDelegate::views_delegate->GetClipboard(); - - if (!clipboard->IsFormatAvailable(Clipboard::GetPlainTextWFormatType())) - return; - - std::wstring clipboard_str; - clipboard->ReadText(&clipboard_str); - if (!clipboard_str.empty()) { - std::wstring collapsed(CollapseWhitespace(clipboard_str, false)); - if (parent_->GetStyle() & STYLE_LOWERCASE) - collapsed = l10n_util::ToLower(collapsed); - // Force a Paste operation to trigger OnContentsChanged, even if identical - // contents are pasted into the text box. - text_before_change_.clear(); - ReplaceSel(collapsed.c_str(), true); - } +void Textfield::ClearSelection() const { + if (native_wrapper_) + native_wrapper_->ClearSelection(); } -void Textfield::Edit::OnSysChar(TCHAR ch, UINT repeat_count, UINT flags) { - // Nearly all alt-<xxx> combos result in beeping rather than doing something - // useful, so we discard most. Exceptions: - // * ctrl-alt-<xxx>, which is sometimes important, generates WM_CHAR instead - // of WM_SYSCHAR, so it doesn't need to be handled here. - // * alt-space gets translated by the default WM_SYSCHAR handler to a - // WM_SYSCOMMAND to open the application context menu, so we need to allow - // it through. - if (ch == VK_SPACE) - SetMsgHandled(false); +void Textfield::SetBackgroundColor(SkColor color) { + background_color_ = color; + use_default_background_color_ = false; + if (native_wrapper_) + native_wrapper_->UpdateBackgroundColor(); } -void Textfield::Edit::HandleKeystroke(UINT message, - TCHAR key, - UINT repeat_count, - UINT flags) { - ScopedFreeze freeze(this, GetTextObjectModel()); - - Textfield::Controller* controller = parent_->GetController(); - bool handled = false; - if (controller) { - handled = controller->HandleKeystroke(parent_, - Textfield::Keystroke(message, key, repeat_count, flags)); - } - - if (!handled) { - OnBeforePossibleChange(); - DefWindowProc(message, key, MAKELPARAM(repeat_count, flags)); - OnAfterPossibleChange(); - } +void Textfield::UseDefaultBackgroundColor() { + use_default_background_color_ = true; + if (native_wrapper_) + native_wrapper_->UpdateBackgroundColor(); } -void Textfield::Edit::OnBeforePossibleChange() { - // Record our state. - text_before_change_ = GetText(); +void Textfield::SetFont(const gfx::Font& font) { + font_ = font; + if (native_wrapper_) + native_wrapper_->UpdateFont(); } -void Textfield::Edit::OnAfterPossibleChange() { - // Prevent the user from selecting the "phantom newline" at the end of the - // edit. If they try, we just silently move the end of the selection back to - // the end of the real text. - CHARRANGE new_sel; - GetSel(new_sel); - const int length = GetTextLength(); - if (new_sel.cpMax > length) { - new_sel.cpMax = length; - if (new_sel.cpMin > length) - new_sel.cpMin = length; - SetSel(new_sel); - } - - std::wstring new_text(GetText()); - if (new_text != text_before_change_) { - if (ime_discard_composition_ && ime_composition_start_ >= 0 && - ime_composition_length_ > 0) { - // A string retrieved with a GetText() call contains a string being - // composed by an IME. We remove the composition string from this search - // string. - new_text.erase(ime_composition_start_, ime_composition_length_); - ime_composition_start_ = 0; - ime_composition_length_ = 0; - if (new_text.empty()) - return; - } - parent_->SyncText(); - if (parent_->GetController()) - parent_->GetController()->ContentsChanged(parent_, new_text); - } +void Textfield::SetHorizontalMargins(int left, int right) { + if (native_wrapper_) + native_wrapper_->SetHorizontalMargins(left, right); } -LONG Textfield::Edit::ClipXCoordToVisibleText(LONG x, - bool is_triple_click) const { - // Clip the X coordinate to the left edge of the text. Careful: - // PosFromChar(0) may return a negative X coordinate if the beginning of the - // text has scrolled off the edit, so don't go past the clip rect's edge. - PARAFORMAT2 pf2; - GetParaFormat(pf2); - // Calculation of the clipped coordinate is more complicated if the paragraph - // layout is RTL layout, or if there is RTL characters inside the LTR layout - // paragraph. - bool ltr_text_in_ltr_layout = true; - if ((pf2.wEffects & PFE_RTLPARA) || - l10n_util::StringContainsStrongRTLChars(GetText())) { - ltr_text_in_ltr_layout = false; - } - const int length = GetTextLength(); - RECT r; - GetRect(&r); - // The values returned by PosFromChar() seem to refer always - // to the left edge of the character's bounding box. - const LONG first_position_x = PosFromChar(0).x; - LONG min_x = first_position_x; - if (!ltr_text_in_ltr_layout) { - for (int i = 1; i < length; ++i) - min_x = std::min(min_x, PosFromChar(i).x); - } - const LONG left_bound = std::max(r.left, min_x); - - // PosFromChar(length) is a phantom character past the end of the text. It is - // not necessarily a right bound; in RTL controls it may be a left bound. So - // treat it as a right bound only if it is to the right of the first - // character. - LONG right_bound = r.right; - LONG end_position_x = PosFromChar(length).x; - if (end_position_x >= first_position_x) { - right_bound = std::min(right_bound, end_position_x); // LTR case. - } - // For trailing characters that are 2 pixels wide of less (like "l" in some - // fonts), we have a problem: - // * Clicks on any pixel within the character will place the cursor before - // the character. - // * Clicks on the pixel just after the character will not allow triple- - // click to work properly (true for any last character width). - // So, we move to the last pixel of the character when this is a - // triple-click, and moving to one past the last pixel in all other - // scenarios. This way, all clicks that can move the cursor will place it at - // the end of the text, but triple-click will still work. - if (x < left_bound) { - return (is_triple_click && ltr_text_in_ltr_layout) ? left_bound - 1 : - left_bound; - } - if ((length == 0) || (x < right_bound)) - return x; - return is_triple_click ? (right_bound - 1) : right_bound; +void Textfield::SetHeightInLines(int num_lines) { + DCHECK(IsMultiLine()); + num_lines_ = num_lines; } -void Textfield::Edit::SetContainsMouse(bool contains_mouse) { - if (contains_mouse == contains_mouse_) - return; - - contains_mouse_ = contains_mouse; - +void Textfield::RemoveBorder() { if (!draw_border_) return; - if (contains_mouse_) { - // Register for notification when the mouse leaves. Need to do this so - // that we can reset contains mouse properly. - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(tme); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = m_hWnd; - tme.dwHoverTime = 0; - TrackMouseEvent(&tme); - } - RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_FRAME); -} - -ITextDocument* Textfield::Edit::GetTextObjectModel() const { - if (!text_object_model_) { - CComPtr<IRichEditOle> ole_interface; - ole_interface.Attach(GetOleInterface()); - text_object_model_ = ole_interface; - } - return text_object_model_; -} - -///////////////////////////////////////////////////////////////////////////// -// Textfield - -Textfield::~Textfield() { - if (edit_) { - // If the edit hwnd still exists, we need to destroy it explicitly. - if (*edit_) - edit_->DestroyWindow(); - delete edit_; - } -} - -void Textfield::ViewHierarchyChanged(bool is_add, View* parent, View* child) { - Widget* widget; - - if (is_add && (widget = GetWidget())) { - // This notification is called from the AddChildView call below. Ignore it. - if (native_view_ && !edit_) - return; - - if (!native_view_) { - native_view_ = new HWNDView(); // Deleted from our superclass destructor - AddChildView(native_view_); - - // Maps the focus of the native control to the focus of this view. - native_view_->SetAssociatedFocusView(this); - } - - // If edit_ is invalid from a previous use. Reset it. - if (edit_ && !IsWindow(edit_->m_hWnd)) { - native_view_->Detach(); - delete edit_; - edit_ = NULL; - } - - if (!edit_) { - edit_ = new Edit(this, draw_border_); - edit_->SetFont(font_.hfont()); - native_view_->Attach(*edit_); - if (!text_.empty()) - edit_->SetText(text_); - UpdateEditBackgroundColor(); - Layout(); - } - } else if (!is_add && edit_ && IsWindow(edit_->m_hWnd)) { - edit_->SetParent(NULL); - } -} - -void Textfield::Layout() { - if (native_view_) { - native_view_->SetBounds(GetLocalBounds(true)); - native_view_->Layout(); - } -} - -gfx::Size Textfield::GetPreferredSize() { - gfx::Insets insets; - CalculateInsets(&insets); - return gfx::Size(font_.GetExpectedTextWidth(default_width_in_chars_) + - insets.width(), - num_lines_ * font_.height() + insets.height()); -} - -std::wstring Textfield::GetText() const { - return text_; -} - -void Textfield::SetText(const std::wstring& text) { - text_ = text; - if (edit_) - edit_->SetText(text); + draw_border_ = false; + if (native_wrapper_) + native_wrapper_->UpdateBorder(); } -void Textfield::AppendText(const std::wstring& text) { - text_ += text; - if (edit_) - edit_->AppendText(text); -} void Textfield::CalculateInsets(gfx::Insets* insets) { DCHECK(insets); @@ -1068,108 +153,40 @@ void Textfield::CalculateInsets(gfx::Insets* insets) { } void Textfield::SyncText() { - if (edit_) - text_ = edit_->GetText(); -} - -void Textfield::SetController(Controller* controller) { - controller_ = controller; -} - -Textfield::Controller* Textfield::GetController() const { - return controller_; + if (native_wrapper_) + text_ = native_wrapper_->GetText(); } -bool Textfield::IsReadOnly() const { - return edit_ ? ((edit_->GetStyle() & ES_READONLY) != 0) : read_only_; +// static +bool Textfield::IsKeystrokeEnter(const Keystroke& key) { + return key.key == VK_RETURN; } -bool Textfield::IsPassword() const { - return GetStyle() & Textfield::STYLE_PASSWORD; +// static +bool Textfield::IsKeystrokeEscape(const Keystroke& key) { + return key.key == VK_ESCAPE; } -bool Textfield::IsMultiLine() const { - return (style_ & STYLE_MULTILINE) != 0; -} +//////////////////////////////////////////////////////////////////////////////// +// Textfield, View overrides: -void Textfield::SetReadOnly(bool read_only) { - read_only_ = read_only; - if (edit_) { - edit_->SetReadOnly(read_only); - UpdateEditBackgroundColor(); +void Textfield::Layout() { + if (native_wrapper_) { + native_wrapper_->GetView()->SetBounds(GetLocalBounds(true)); + native_wrapper_->GetView()->Layout(); } } -void Textfield::Focus() { - ::SetFocus(native_view_->GetHWND()); -} - -void Textfield::SelectAll() { - if (edit_) - edit_->SelectAll(); -} - -void Textfield::ClearSelection() const { - if (edit_) - edit_->ClearSelection(); -} - -HWND Textfield::GetNativeComponent() { - return native_view_->GetHWND(); -} - -void Textfield::SetBackgroundColor(SkColor color) { - background_color_ = color; - use_default_background_color_ = false; - UpdateEditBackgroundColor(); -} - -void Textfield::SetDefaultBackgroundColor() { - use_default_background_color_ = true; - UpdateEditBackgroundColor(); -} - -void Textfield::SetFont(const gfx::Font& font) { - font_ = font; - if (edit_) - edit_->SetFont(font.hfont()); -} - -gfx::Font Textfield::GetFont() const { - return font_; -} - -bool Textfield::SetHorizontalMargins(int left, int right) { - // SendMessage expects the two values to be packed into one using MAKELONG - // so we truncate to 16 bits if necessary. - return ERROR_SUCCESS == SendMessage(GetNativeComponent(), - (UINT) EM_SETMARGINS, - (WPARAM) EC_LEFTMARGIN | EC_RIGHTMARGIN, - (LPARAM) MAKELONG(left & 0xFFFF, - right & 0xFFFF)); -} - -void Textfield::SetHeightInLines(int num_lines) { - DCHECK(IsMultiLine()); - num_lines_ = num_lines; -} - -void Textfield::RemoveBorder() { - if (!draw_border_) - return; - - draw_border_ = false; - if (edit_) - edit_->RemoveBorder(); -} - -void Textfield::SetEnabled(bool enabled) { - View::SetEnabled(enabled); - edit_->SetEnabled(enabled); +gfx::Size Textfield::GetPreferredSize() { + gfx::Insets insets; + CalculateInsets(&insets); + return gfx::Size(font_.GetExpectedTextWidth(default_width_in_chars_) + + insets.width(), + num_lines_ * font_.height() + insets.height()); } bool Textfield::IsFocusable() const { - return IsEnabled() && !IsReadOnly(); + return IsEnabled() && !read_only_; } void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse) { @@ -1177,6 +194,7 @@ void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse) { } bool Textfield::SkipDefaultKeyEventProcessing(const KeyEvent& e) { +#if defined(OS_WIN) // TODO(hamaji): Figure out which keyboard combinations we need to add here, // similar to LocationBarView::SkipDefaultKeyEventProcessing. if (e.GetCharacter() == VK_BACK) @@ -1187,20 +205,45 @@ bool Textfield::SkipDefaultKeyEventProcessing(const KeyEvent& e) { if (e.IsAltDown() && win_util::IsNumPadDigit(e.GetCharacter(), e.IsExtendedKey())) return true; - +#endif return false; } -void Textfield::UpdateEditBackgroundColor() { - if (!edit_) - return; +void Textfield::SetEnabled(bool enabled) { + View::SetEnabled(enabled); + if (native_wrapper_) + native_wrapper_->UpdateEnabled(); +} + +void Textfield::Focus() { + if (native_wrapper_) { + // Forward the focus to the wrapper if it exists. + native_wrapper_->SetFocus(); + } else { + // If there is no wrapper, cause the RootView to be focused so that we still + // get keyboard messages. + View::Focus(); + } +} - COLORREF bg_color; - if (!use_default_background_color_) - bg_color = skia::SkColorToCOLORREF(background_color_); - else - bg_color = GetSysColor(read_only_ ? COLOR_3DFACE : COLOR_WINDOW); - edit_->SetBackgroundColor(bg_color); +void Textfield::ViewHierarchyChanged(bool is_add, View* parent, View* child) { + if (is_add && !native_wrapper_ && GetWidget() && !initialized_) { + initialized_ = true; + native_wrapper_ = NativeTextfieldWrapper::CreateWrapper(this); + //AddChildView(native_wrapper_->GetView()); + // TODO(beng): Move this initialization to NativeTextfieldWin once it + // subclasses NativeControlWin. + native_wrapper_->UpdateText(); + native_wrapper_->UpdateBackgroundColor(); + native_wrapper_->UpdateReadOnly(); + native_wrapper_->UpdateFont(); + native_wrapper_->UpdateEnabled(); + native_wrapper_->UpdateBorder(); + } +} + +std::string Textfield::GetClassName() const { + return kViewClassName; } } // namespace views diff --git a/views/controls/textfield/textfield.h b/views/controls/textfield/textfield.h index 114dbbf..bbd9104 100644 --- a/views/controls/textfield/textfield.h +++ b/views/controls/textfield/textfield.h @@ -12,13 +12,21 @@ #include "views/view.h" #include "third_party/skia/include/core/SkColor.h" +#ifdef UNIT_TEST +#include "base/gfx/native_widget_types.h" +#include "views/controls/textfield/native_textfield_wrapper.h" +#endif + namespace views { -class HWNDView; +class NativeTextfieldWrapper; // This class implements a ChromeView that wraps a native text (edit) field. class Textfield : public View { public: + // The button's class name. + static const char kViewClassName[]; + // Keystroke provides a platform-dependent way to send keystroke events. // Cross-platform code can use IsKeystrokeEnter/Escape to check for these // two common key events. @@ -68,99 +76,62 @@ class Textfield : public View { STYLE_LOWERCASE = 1<<2 }; - Textfield() - : -#if defined(OS_WIN) - native_view_(NULL), -#endif - edit_(NULL), - controller_(NULL), - style_(STYLE_DEFAULT), - read_only_(false), - default_width_in_chars_(0), - draw_border_(true), - use_default_background_color_(true), - num_lines_(1) { - SetFocusable(true); - } - explicit Textfield(StyleFlags style) - : -#if defined(OS_WIN) - native_view_(NULL), -#endif - edit_(NULL), - controller_(NULL), - style_(style), - read_only_(false), - default_width_in_chars_(0), - draw_border_(true), - use_default_background_color_(true), - num_lines_(1) { - SetFocusable(true); - } + Textfield(); + explicit Textfield(StyleFlags style); virtual ~Textfield(); - void ViewHierarchyChanged(bool is_add, View* parent, View* child); - - // Overridden for layout purposes - virtual void Layout(); - virtual gfx::Size GetPreferredSize(); // Controller accessors void SetController(Controller* controller); Controller* GetController() const; + // Gets/Sets whether or not the Textfield is read-only. + bool read_only() const { return read_only_; } void SetReadOnly(bool read_only); - bool IsReadOnly() const; + // Returns true if the Textfield is a password field. bool IsPassword() const; // Whether the text field is multi-line or not, must be set when the text // field is created, using StyleFlags. bool IsMultiLine() const; - virtual bool IsFocusable() const; - virtual void AboutToRequestFocusFromTabTraversal(bool reverse); - - // Overridden from Chrome::View. - virtual bool SkipDefaultKeyEventProcessing(const KeyEvent& e); - -#if defined(OS_WIN) - virtual HWND GetNativeComponent(); -#endif - - // Returns the text currently displayed in the text field. - std::wstring GetText() const; - - // Sets the text currently displayed in the text field. + // Gets/Sets the text currently displayed in the Textfield. + const std::wstring& text() const { return text_; } void SetText(const std::wstring& text); // Appends the given string to the previously-existing text in the field. void AppendText(const std::wstring& text); - virtual void Focus(); - // Causes the edit field to be fully selected. void SelectAll(); // Clears the selection within the edit field and sets the caret to the end. void ClearSelection() const; - StyleFlags GetStyle() const { return style_; } + // Accessor for |style_|. + StyleFlags style() const { return style_; } + // Gets/Sets the background color to be used when painting the Textfield. + // Call |UseDefaultBackgroundColor| to return to the system default colors. + SkColor background_color() const { return background_color_; } void SetBackgroundColor(SkColor color); - void SetDefaultBackgroundColor(); - // Set the font. - void SetFont(const gfx::Font& font); + // Gets/Sets whether the default background color should be used when painting + // the Textfield. + bool use_default_background_color() const { + return use_default_background_color_; + } + void UseDefaultBackgroundColor(); - // Return the font used by this Textfield. - gfx::Font GetFont() const; + // Gets/Sets the font used when rendering the text within the Textfield. + gfx::Font font() const { return font_; } + void SetFont(const gfx::Font& font); // Sets the left and right margin (in pixels) within the text box. On Windows // this is accomplished by packing the left and right margin into a single // 32 bit number, so the left and right margins are effectively 16 bits. - bool SetHorizontalMargins(int left, int right); + void SetHorizontalMargins(int left, int right); // Should only be called on a multi-line text field. Sets how many lines of // text can be displayed at once by this text field. @@ -172,11 +143,17 @@ class Textfield : public View { } // Removes the border from the edit box, giving it a 2D look. + bool draw_border() const { return draw_border_; } void RemoveBorder(); - // Disable the edit control. - // NOTE: this does NOT change the read only property. - void SetEnabled(bool enabled); + // Calculates the insets for the text field. + void CalculateInsets(gfx::Insets* insets); + + // Invoked by the edit control when the value changes. This method set + // the text_ member variable to the value contained in edit control. + // This is important because the edit control can be replaced if it has + // been deleted during a window close. + void SyncText(); // Provides a cross-platform way of checking whether a keystroke is one of // these common keys. Most code only checks keystrokes against these two keys, @@ -187,41 +164,42 @@ class Textfield : public View { static bool IsKeystrokeEnter(const Keystroke& key); static bool IsKeystrokeEscape(const Keystroke& key); - private: - class Edit; - - // Invoked by the edit control when the value changes. This method set - // the text_ member variable to the value contained in edit control. - // This is important because the edit control can be replaced if it has - // been deleted during a window close. - void SyncText(); - - // Reset the text field native control. - void ResetNativeControl(); +#ifdef UNIT_TEST + gfx::NativeView GetTestingHandle() const { + return native_wrapper_ ? native_wrapper_->GetTestingHandle() : NULL; + } +#endif - // Resets the background color of the edit. - void UpdateEditBackgroundColor(); + // Overridden from View: + virtual void Layout(); + virtual gfx::Size GetPreferredSize(); + virtual bool IsFocusable() const; + virtual void AboutToRequestFocusFromTabTraversal(bool reverse); + virtual bool SkipDefaultKeyEventProcessing(const KeyEvent& e); + virtual void SetEnabled(bool enabled); -#if defined(OS_WIN) - // This encapsulates the HWND of the native text field. - HWNDView* native_view_; -#endif + protected: + virtual void Focus(); + virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child); + virtual std::string GetClassName() const; - // This inherits from the native text field. - Edit* edit_; + private: + // The object that actually implements the native text field. + NativeTextfieldWrapper* native_wrapper_; - // This is the current listener for events from this control. + // This is the current listener for events from this Textfield. Controller* controller_; + // The mask of style options for this Textfield. StyleFlags style_; + // The font used to render the text in the Textfield. gfx::Font font_; - // NOTE: this is temporary until we rewrite Textfield to always work whether - // there is an HWND or not. - // Used if the HWND hasn't been created yet. + // The text displayed in the Textfield. std::wstring text_; + // True if this Textfield cannot accept input and is read-only. bool read_only_; // The default number of average characters for the width of this text field. @@ -231,16 +209,21 @@ class Textfield : public View { // Whether the border is drawn. bool draw_border_; + // The background color to be used when painting the Textfield, provided + // |use_default_background_color_| is set to false. SkColor background_color_; + // When true, the system colors for Textfields are used when painting this + // Textfield. When false, the value of |background_color_| determines the + // Textfield's background color. bool use_default_background_color_; // The number of lines of text this Textfield displays at once. int num_lines_; - protected: - // Calculates the insets for the text field. - void CalculateInsets(gfx::Insets* insets); + // TODO(beng): remove this once NativeTextfieldWin subclasses + // NativeControlWin. + bool initialized_; DISALLOW_COPY_AND_ASSIGN(Textfield); }; diff --git a/views/view_unittest.cc b/views/view_unittest.cc index 3e6543d..2ccf8b5 100644 --- a/views/view_unittest.cc +++ b/views/view_unittest.cc @@ -621,26 +621,26 @@ TEST_F(ViewTest, TextfieldCutCopyPaste) { // // Test cut. // - ASSERT_TRUE(normal->GetNativeComponent()); + ASSERT_TRUE(normal->GetTestingHandle()); normal->SelectAll(); - ::SendMessage(normal->GetNativeComponent(), WM_CUT, 0, 0); + ::SendMessage(normal->GetTestingHandle(), WM_CUT, 0, 0); string16 result; clipboard.ReadText(&result); EXPECT_EQ(kNormalText, result); normal->SetText(kNormalText); // Let's revert to the original content. - ASSERT_TRUE(read_only->GetNativeComponent()); + ASSERT_TRUE(read_only->GetTestingHandle()); read_only->SelectAll(); - ::SendMessage(read_only->GetNativeComponent(), WM_CUT, 0, 0); + ::SendMessage(read_only->GetTestingHandle(), WM_CUT, 0, 0); result.clear(); clipboard.ReadText(&result); // Cut should have failed, so the clipboard content should not have changed. EXPECT_EQ(kNormalText, result); - ASSERT_TRUE(password->GetNativeComponent()); + ASSERT_TRUE(password->GetTestingHandle()); password->SelectAll(); - ::SendMessage(password->GetNativeComponent(), WM_CUT, 0, 0); + ::SendMessage(password->GetTestingHandle(), WM_CUT, 0, 0); result.clear(); clipboard.ReadText(&result); // Cut should have failed, so the clipboard content should not have changed. @@ -653,19 +653,19 @@ TEST_F(ViewTest, TextfieldCutCopyPaste) { // Let's start with read_only as the clipboard already contains the content // of normal. read_only->SelectAll(); - ::SendMessage(read_only->GetNativeComponent(), WM_COPY, 0, 0); + ::SendMessage(read_only->GetTestingHandle(), WM_COPY, 0, 0); result.clear(); clipboard.ReadText(&result); EXPECT_EQ(kReadOnlyText, result); normal->SelectAll(); - ::SendMessage(normal->GetNativeComponent(), WM_COPY, 0, 0); + ::SendMessage(normal->GetTestingHandle(), WM_COPY, 0, 0); result.clear(); clipboard.ReadText(&result); EXPECT_EQ(kNormalText, result); password->SelectAll(); - ::SendMessage(password->GetNativeComponent(), WM_COPY, 0, 0); + ::SendMessage(password->GetTestingHandle(), WM_COPY, 0, 0); result.clear(); clipboard.ReadText(&result); // We don't let you copy from a password field, clipboard should not have @@ -681,23 +681,23 @@ TEST_F(ViewTest, TextfieldCutCopyPaste) { // Attempting to copy kNormalText in a read-only text-field should fail. read_only->SelectAll(); - ::SendMessage(read_only->GetNativeComponent(), WM_KEYDOWN, 0, 0); + ::SendMessage(read_only->GetTestingHandle(), WM_KEYDOWN, 0, 0); wchar_t buffer[1024] = { 0 }; - ::GetWindowText(read_only->GetNativeComponent(), buffer, 1024); + ::GetWindowText(read_only->GetTestingHandle(), buffer, 1024); EXPECT_EQ(kReadOnlyText, std::wstring(buffer)); password->SelectAll(); - ::SendMessage(password->GetNativeComponent(), WM_PASTE, 0, 0); - ::GetWindowText(password->GetNativeComponent(), buffer, 1024); + ::SendMessage(password->GetTestingHandle(), WM_PASTE, 0, 0); + ::GetWindowText(password->GetTestingHandle(), buffer, 1024); EXPECT_EQ(kNormalText, std::wstring(buffer)); // Copy from read_only so the string we are pasting is not the same as the // current one. read_only->SelectAll(); - ::SendMessage(read_only->GetNativeComponent(), WM_COPY, 0, 0); + ::SendMessage(read_only->GetTestingHandle(), WM_COPY, 0, 0); normal->SelectAll(); - ::SendMessage(normal->GetNativeComponent(), WM_PASTE, 0, 0); - ::GetWindowText(normal->GetNativeComponent(), buffer, 1024); + ::SendMessage(normal->GetTestingHandle(), WM_PASTE, 0, 0); + ::GetWindowText(normal->GetTestingHandle(), buffer, 1024); EXPECT_EQ(kReadOnlyText, std::wstring(buffer)); } #endif @@ -810,12 +810,12 @@ TEST_F(ViewTest, DISABLED_RerouteMouseWheelTest) { EXPECT_EQ(60, scroll_view->GetVisibleRect().y()); // Then the text-field. - ::SendMessage(view_with_controls->text_field_->GetNativeComponent(), + ::SendMessage(view_with_controls->text_field_->GetTestingHandle(), WM_MOUSEWHEEL, MAKEWPARAM(0, -20), MAKELPARAM(250, 250)); EXPECT_EQ(80, scroll_view->GetVisibleRect().y()); // Ensure we don't scroll when the mouse is not over that window. - ::SendMessage(view_with_controls->text_field_->GetNativeComponent(), + ::SendMessage(view_with_controls->text_field_->GetTestingHandle(), WM_MOUSEWHEEL, MAKEWPARAM(0, -20), MAKELPARAM(50, 50)); EXPECT_EQ(80, scroll_view->GetVisibleRect().y()); } |