diff options
Diffstat (limited to 'views/controls/textfield/textfield.cc')
-rw-r--r-- | views/controls/textfield/textfield.cc | 1241 |
1 files changed, 142 insertions, 1099 deletions
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 |