diff options
author | deanm@chromium.org <deanm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-02 18:58:26 +0000 |
---|---|---|
committer | deanm@chromium.org <deanm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-02 18:58:26 +0000 |
commit | 93e503337da16175761eaa6fbc10af5b8b2e5506 (patch) | |
tree | d9573f2ed83da709763b2ea507e02b877d4aa7bf /chrome/browser/autocomplete | |
parent | 6eec19cbd0a39057cd4e520f5f6c55ca681c08ac (diff) | |
download | chromium_src-93e503337da16175761eaa6fbc10af5b8b2e5506.zip chromium_src-93e503337da16175761eaa6fbc10af5b8b2e5506.tar.gz chromium_src-93e503337da16175761eaa6fbc10af5b8b2e5506.tar.bz2 |
Separate the AutocompleteEditView from the cross-platform code.
Separate the platform specific AutocompleteEditView from the cross-platform model and controller. This leaves the model/controller in autocomplete_edit.{h,cc}, and moves the view into autocomplete_edit_view.h and autocomplete_edit_view_win.{h,cc}.
There is now an abstract interface (AutocompleteEditView) which the model uses to talk to the view. Other views (for example, the LocationBarView) will still talk directly to the AutocompleteEditViewWin, which is the full Chrome view.
This change also pulled in the Windows specific edit_drop_target code into autocomplete_edit_view_win.cc.
Review URL: http://codereview.chromium.org/27327
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@10712 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/autocomplete')
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_accessibility.cc | 4 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_accessibility.h | 16 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_edit.cc | 1934 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_edit.h | 501 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_edit_view.h | 117 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_edit_view_win.cc | 2185 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_edit_view_win.h | 458 | ||||
-rwxr-xr-x | chrome/browser/autocomplete/autocomplete_popup_view_win.cc | 2 | ||||
-rwxr-xr-x | chrome/browser/autocomplete/autocomplete_popup_view_win.h | 11 | ||||
-rw-r--r-- | chrome/browser/autocomplete/edit_drop_target.cc | 152 | ||||
-rw-r--r-- | chrome/browser/autocomplete/edit_drop_target.h | 58 |
11 files changed, 2786 insertions, 2652 deletions
diff --git a/chrome/browser/autocomplete/autocomplete_accessibility.cc b/chrome/browser/autocomplete/autocomplete_accessibility.cc index 5d61fab..807be73 100644 --- a/chrome/browser/autocomplete/autocomplete_accessibility.cc +++ b/chrome/browser/autocomplete/autocomplete_accessibility.cc @@ -4,13 +4,15 @@ #include "chrome/browser/autocomplete/autocomplete_accessibility.h" +#include "chrome/browser/autocomplete/autocomplete_edit.h" +#include "chrome/browser/autocomplete/autocomplete_edit_view_win.h" #include "chrome/common/l10n_util.h" #include "chrome/views/accessibility/accessible_wrapper.h" #include "chrome/views/view.h" #include "grit/generated_resources.h" HRESULT AutocompleteAccessibility::Initialize( - const AutocompleteEditView* edit_box) { + const AutocompleteEditViewWin* edit_box) { if (edit_box == NULL) { return E_INVALIDARG; } diff --git a/chrome/browser/autocomplete/autocomplete_accessibility.h b/chrome/browser/autocomplete/autocomplete_accessibility.h index cd5f970..8671c1f 100644 --- a/chrome/browser/autocomplete/autocomplete_accessibility.h +++ b/chrome/browser/autocomplete/autocomplete_accessibility.h @@ -7,18 +7,19 @@ #include <atlbase.h> #include <atlcom.h> - #include <oleacc.h> -#include "chrome/browser/autocomplete/autocomplete_edit.h" +#include "base/basictypes.h" + +class AutocompleteEditViewWin; //////////////////////////////////////////////////////////////////////////////// // // AutocompleteAccessibility // // Class implementing the MSAA IAccessible COM interface for -// AutocompleteEditView, providing accessibility to be used by screen readers -// and other assistive technology (AT). +// AutocompleteEditViewWin, providing accessibility to be used by screen +// readers and other assistive technology (AT). // //////////////////////////////////////////////////////////////////////////////// class ATL_NO_VTABLE AutocompleteAccessibility @@ -33,7 +34,7 @@ class ATL_NO_VTABLE AutocompleteAccessibility AutocompleteAccessibility() {} ~AutocompleteAccessibility() {} - HRESULT Initialize(const AutocompleteEditView* edit_box); + HRESULT Initialize(const AutocompleteEditViewWin* edit_box); // Supported IAccessible methods. @@ -105,8 +106,9 @@ class ATL_NO_VTABLE AutocompleteAccessibility CComPtr<IAccessible> default_accessibility_server_; private: - const AutocompleteEditView* edit_box_; + const AutocompleteEditViewWin* edit_box_; - DISALLOW_EVIL_CONSTRUCTORS(AutocompleteAccessibility); + DISALLOW_COPY_AND_ASSIGN(AutocompleteAccessibility); }; + #endif // CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_ACCESSIBILITY_H_ diff --git a/chrome/browser/autocomplete/autocomplete_edit.cc b/chrome/browser/autocomplete/autocomplete_edit.cc index 3a0f56b..9d3d843a 100644 --- a/chrome/browser/autocomplete/autocomplete_edit.cc +++ b/chrome/browser/autocomplete/autocomplete_edit.cc @@ -6,11 +6,8 @@ #include <locale> -#include "base/base_drag_source.h" +// TODO(deanm): Cleanup these includes. #include "base/basictypes.h" -#include "base/clipboard.h" -#include "base/iat_patch.h" -#include "base/lazy_instance.h" #include "base/ref_counted.h" #include "base/scoped_clipboard_writer.h" #include "base/string_util.h" @@ -31,18 +28,12 @@ #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/views/location_bar_view.h" #include "chrome/common/clipboard_service.h" -#include "chrome/common/gfx/chrome_canvas.h" -#include "chrome/common/gfx/utils.h" -#include "chrome/common/l10n_util.h" -#include "chrome/common/l10n_util_win.h" #include "chrome/common/notification_service.h" #include "chrome/common/os_exchange_data.h" #include "chrome/common/win_util.h" #include "googleurl/src/url_util.h" #include "grit/generated_resources.h" -#include "skia/ext/skia_utils_win.h" - -#pragma comment(lib, "oleacc.lib") // Needed for accessibility support. +#include "skia/include/SkBitmap.h" /////////////////////////////////////////////////////////////////////////////// // AutocompleteEditModel @@ -309,11 +300,7 @@ void AutocompleteEditModel::SendOpenNotification(size_t selected_line, void AutocompleteEditModel::AcceptKeyword() { view_->OnBeforePossibleChange(); - // NOTE: We don't need the IME composition hack in SetWindowTextAndCaretPos() - // here, because any active IME composition will eat <tab> characters, - // preventing the user from using tab-to-search until the composition is - // ended. - view_->SetWindowText(L""); + view_->SetWindowTextAndCaretPos(std::wstring(), 0); is_keyword_hint_ = false; keyword_ui_state_ = KEYWORD; view_->OnAfterPossibleChange(); @@ -581,1918 +568,3 @@ GURL AutocompleteEditModel::GetURLForCurrentText( is_history_what_you_typed_match, alternate_nav_url); } - -/////////////////////////////////////////////////////////////////////////////// -// Helper classes - -AutocompleteEditView::ScopedFreeze::ScopedFreeze( - AutocompleteEditView* 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); - } -} - -AutocompleteEditView::ScopedFreeze::~ScopedFreeze() { - // Unfreeze the screen. - // NOTE: If this destructor is reached while the edit is being destroyed (for - // example, because we double-clicked the edit of a popup and caused it to - // transform to an unconstrained window), it will no longer have an HWND, and - // text_object_model_ may point to a destroyed object, so do nothing here. - if (edit_->IsWindow() && 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(); - } - } -} - -AutocompleteEditView::ScopedSuspendUndo::ScopedSuspendUndo( - ITextDocument* text_object_model) - : text_object_model_(text_object_model) { - // Suspend Undo processing. - if (text_object_model_) - text_object_model_->Undo(tomSuspend, NULL); -} - -AutocompleteEditView::ScopedSuspendUndo::~ScopedSuspendUndo() { - // Resume Undo processing. - if (text_object_model_) - text_object_model_->Undo(tomResume, NULL); -} - -/////////////////////////////////////////////////////////////////////////////// -// AutocompleteEditView - -// TODO (jcampan): these colors should be derived from the system colors to -// ensure they show properly. Bug #948807. -// Colors used to emphasize the scheme in the URL. - -namespace { - -const COLORREF kSecureSchemeColor = RGB(0, 150, 20); -const COLORREF kInsecureSchemeColor = RGB(200, 0, 0); - -// Colors used to strike-out the scheme when it is insecure. -const SkColor kSchemeStrikeoutColor = SkColorSetRGB(210, 0, 0); -const SkColor kSchemeSelectedStrikeoutColor = - SkColorSetRGB(255, 255, 255); - -// These are used to hook the CRichEditCtrl's calls to BeginPaint() and -// EndPaint() and provide a memory DC instead. See OnPaint(). -HWND edit_hwnd = NULL; -PAINTSTRUCT paint_struct; - -HDC BeginPaintIntercept(HWND hWnd, LPPAINTSTRUCT lpPaint) { - if (!edit_hwnd || (hWnd != edit_hwnd)) - return ::BeginPaint(hWnd, lpPaint); - - *lpPaint = paint_struct; - return paint_struct.hdc; -} - -BOOL EndPaintIntercept(HWND hWnd, const PAINTSTRUCT* lpPaint) { - return (edit_hwnd && (hWnd == edit_hwnd)) ? - true : ::EndPaint(hWnd, lpPaint); -} - -// Returns a lazily initialized property bag accessor for saving our state in a -// TabContents. -PropertyAccessor<AutocompleteEditState>* GetStateAccessor() { - static PropertyAccessor<AutocompleteEditState> state; - return &state; -} - -class PaintPatcher { - public: - PaintPatcher() : refcount_(0) { } - ~PaintPatcher() { DCHECK(refcount_ == 0); } - - void RefPatch() { - if (refcount_ == 0) { - DCHECK(!begin_paint_.is_patched()); - DCHECK(!end_paint_.is_patched()); - begin_paint_.Patch(L"riched20.dll", "user32.dll", "BeginPaint", - &BeginPaintIntercept); - end_paint_.Patch(L"riched20.dll", "user32.dll", "EndPaint", - &EndPaintIntercept); - } - ++refcount_; - } - - void DerefPatch() { - DCHECK(begin_paint_.is_patched()); - DCHECK(end_paint_.is_patched()); - --refcount_; - if (refcount_ == 0) { - begin_paint_.Unpatch(); - end_paint_.Unpatch(); - } - } - - private: - size_t refcount_; - iat_patch::IATPatchFunction begin_paint_; - iat_patch::IATPatchFunction end_paint_; - - DISALLOW_COPY_AND_ASSIGN(PaintPatcher); -}; - -base::LazyInstance<PaintPatcher> g_paint_patcher(base::LINKER_INITIALIZED); - -} // namespace - -AutocompleteEditView::AutocompleteEditView( - const ChromeFont& font, - AutocompleteEditController* controller, - ToolbarModel* toolbar_model, - views::View* parent_view, - HWND hwnd, - Profile* profile, - CommandUpdater* command_updater, - bool popup_window_mode) - : model_(new AutocompleteEditModel(this, controller, profile)), - popup_view_(new AutocompletePopupViewWin(font, this, model_.get(), - profile)), - controller_(controller), - parent_view_(parent_view), - toolbar_model_(toolbar_model), - command_updater_(command_updater), - popup_window_mode_(popup_window_mode), - tracking_click_(false), - tracking_double_click_(false), - double_click_time_(0), - can_discard_mousemove_(false), - font_(font), - possible_drag_(false), - in_drag_(false), - initiated_drag_(false), - drop_highlight_position_(-1), - background_color_(0), - scheme_security_level_(ToolbarModel::NORMAL) { - model_->set_popup_model(popup_view_->model()); - - saved_selection_for_focus_change_.cpMin = -1; - - g_paint_patcher.Pointer()->RefPatch(); - - Create(hwnd, 0, 0, 0, l10n_util::GetExtendedStyles()); - SetReadOnly(popup_window_mode_); - SetFont(font_.hfont()); - - // NOTE: Do not use SetWordBreakProcEx() here, that is no longer supported as - // of Rich Edit 2.0 onward. - SendMessage(m_hWnd, EM_SETWORDBREAKPROC, 0, - reinterpret_cast<LPARAM>(&WordBreakProc)); - - // Get the metrics for the font. - HDC dc = ::GetDC(NULL); - SelectObject(dc, font_.hfont()); - TEXTMETRIC tm = {0}; - GetTextMetrics(dc, &tm); - font_ascent_ = tm.tmAscent; - const float kXHeightRatio = 0.7f; // The ratio of a font's x-height to its - // cap height. Sadly, Windows doesn't - // provide a true value for a font's - // x-height in its text metrics, so we - // approximate. - font_x_height_ = static_cast<int>((static_cast<float>(font_ascent_ - - tm.tmInternalLeading) * kXHeightRatio) + 0.5); - // The distance from the top of the field to the desired baseline of the - // rendered text. - const int kTextBaseline = popup_window_mode_ ? 15 : 18; - font_y_adjustment_ = kTextBaseline - font_ascent_; - font_descent_ = tm.tmDescent; - - // Get the number of twips per pixel, which we need below to offset our text - // by the desired number of pixels. - const long kTwipsPerPixel = kTwipsPerInch / GetDeviceCaps(dc, LOGPIXELSY); - ::ReleaseDC(NULL, dc); - - // Set the default character style -- adjust to our desired baseline and make - // text grey. - CHARFORMAT cf = {0}; - cf.dwMask = CFM_OFFSET | CFM_COLOR; - cf.yOffset = -font_y_adjustment_ * kTwipsPerPixel; - cf.crTextColor = GetSysColor(COLOR_GRAYTEXT); - SetDefaultCharFormat(cf); - - // Set up context menu. - context_menu_.reset(new Menu(this, Menu::TOPLEFT, m_hWnd)); - if (popup_window_mode_) { - context_menu_->AppendMenuItemWithLabel(IDS_COPY, - l10n_util::GetString(IDS_COPY)); - } else { - context_menu_->AppendMenuItemWithLabel(IDS_UNDO, - l10n_util::GetString(IDS_UNDO)); - context_menu_->AppendSeparator(); - context_menu_->AppendMenuItemWithLabel(IDS_CUT, - l10n_util::GetString(IDS_CUT)); - context_menu_->AppendMenuItemWithLabel(IDS_COPY, - l10n_util::GetString(IDS_COPY)); - context_menu_->AppendMenuItemWithLabel(IDS_PASTE, - l10n_util::GetString(IDS_PASTE)); - // GetContextualLabel() will override this next label with the - // IDS_PASTE_AND_SEARCH label as needed. - context_menu_->AppendMenuItemWithLabel( - IDS_PASTE_AND_GO, l10n_util::GetString(IDS_PASTE_AND_GO)); - context_menu_->AppendSeparator(); - context_menu_->AppendMenuItemWithLabel( - IDS_SELECT_ALL, l10n_util::GetString(IDS_SELECT_ALL)); - context_menu_->AppendSeparator(); - context_menu_->AppendMenuItemWithLabel( - IDS_EDIT_SEARCH_ENGINES, l10n_util::GetString(IDS_EDIT_SEARCH_ENGINES)); - } - - // By default RichEdit has a drop target. Revoke it so that we can install our - // own. Revoke takes care of deleting the existing one. - RevokeDragDrop(m_hWnd); - - // Register our drop target. RichEdit appears to invoke RevokeDropTarget when - // done so that we don't have to explicitly. - if (!popup_window_mode_) { - scoped_refptr<EditDropTarget> drop_target = new EditDropTarget(this); - RegisterDragDrop(m_hWnd, drop_target.get()); - } -} - -AutocompleteEditView::~AutocompleteEditView() { - NotificationService::current()->Notify( - NotificationType::AUTOCOMPLETE_EDIT_DESTROYED, - Source<AutocompleteEditView>(this), - NotificationService::NoDetails()); - - // We balance our reference count and unpatch when the last instance has - // been destroyed. This prevents us from relying on the AtExit or static - // destructor sequence to do our unpatching, which is generally fragile. - g_paint_patcher.Pointer()->DerefPatch(); -} - -void AutocompleteEditView::SaveStateToTab(TabContents* tab) { - DCHECK(tab); - - const AutocompleteEditModel::State model_state( - model_->GetStateForTabSwitch()); - - CHARRANGE selection; - GetSelection(selection); - GetStateAccessor()->SetProperty(tab->property_bag(), - AutocompleteEditState( - model_state, - State(selection, saved_selection_for_focus_change_))); -} - -void AutocompleteEditView::Update(const TabContents* tab_for_state_restoring) { - const bool visibly_changed_permanent_text = - model_->UpdatePermanentText(toolbar_model_->GetText()); - - const ToolbarModel::SecurityLevel security_level = - toolbar_model_->GetSchemeSecurityLevel(); - const COLORREF background_color = - LocationBarView::kBackgroundColorByLevel[security_level]; - const bool changed_security_level = - (security_level != scheme_security_level_); - - // Bail early when no visible state will actually change (prevents an - // unnecessary ScopedFreeze, and thus UpdateWindow()). - if ((background_color == background_color_) && !changed_security_level && - !visibly_changed_permanent_text && !tab_for_state_restoring) - return; - - // Update our local state as desired. We set scheme_security_level_ here so - // it will already be correct before we get to any RevertAll()s below and use - // it. - ScopedFreeze freeze(this, GetTextObjectModel()); - if (background_color_ != background_color) { - background_color_ = background_color; - SetBackgroundColor(background_color_); - } - scheme_security_level_ = security_level; - - // When we're switching to a new tab, restore its state, if any. - if (tab_for_state_restoring) { - // Make sure we reset our own state first. The new tab may not have any - // saved state, or it may not have had input in progress, in which case we - // won't overwrite all our local state. - RevertAll(); - - const AutocompleteEditState* state = GetStateAccessor()->GetProperty( - tab_for_state_restoring->property_bag()); - if (state) { - model_->RestoreState(state->model_state); - - // Restore user's selection. We do this after restoring the user_text - // above so we're selecting in the correct string. - SetSelectionRange(state->view_state.selection); - saved_selection_for_focus_change_ = - state->view_state.saved_selection_for_focus_change; - } - } else if (visibly_changed_permanent_text) { - // Not switching tabs, just updating the permanent text. (In the case where - // we _were_ switching tabs, the RevertAll() above already drew the new - // permanent text.) - - // Tweak: if the edit was previously nonempty and had all the text selected, - // select all the new text. This makes one particular case better: the - // user clicks in the box to change it right before the permanent URL is - // changed. Since the new URL is still fully selected, the user's typing - // will replace the edit contents as they'd intended. - // - // NOTE: The selection can be longer than the text length if the edit is in - // in rich text mode and the user has selected the "phantom newline" at the - // end, so use ">=" instead of "==" to see if all the text is selected. In - // theory we prevent this case from ever occurring, but this is still safe. - CHARRANGE sel; - GetSelection(sel); - const bool was_reversed = (sel.cpMin > sel.cpMax); - const bool was_sel_all = (sel.cpMin != sel.cpMax) && - IsSelectAllForRange(sel); - - RevertAll(); - - if (was_sel_all) - SelectAll(was_reversed); - } else if (changed_security_level) { - // Only the security style changed, nothing else. Redraw our text using it. - EmphasizeURLComponents(); - } -} - -void AutocompleteEditView::OpenURL(const GURL& url, - WindowOpenDisposition disposition, - PageTransition::Type transition, - const GURL& alternate_nav_url, - size_t selected_line, - const std::wstring& keyword) { - if (!url.is_valid()) - return; - - model_->SendOpenNotification(selected_line, keyword); - - ScopedFreeze freeze(this, GetTextObjectModel()); - if (disposition != NEW_BACKGROUND_TAB) - RevertAll(); // Revert the box to its unedited state - controller_->OnAutocompleteAccept(url, disposition, transition, - alternate_nav_url); -} - -std::wstring AutocompleteEditView::GetText() const { - const int len = GetTextLength() + 1; - std::wstring str; - GetWindowText(WriteInto(&str, len), len); - return str; -} - -void AutocompleteEditView::SetUserText(const std::wstring& text, - const std::wstring& display_text, - bool update_popup) { - ScopedFreeze freeze(this, GetTextObjectModel()); - model_->SetUserText(text); - saved_selection_for_focus_change_.cpMin = -1; - SetWindowTextAndCaretPos(display_text, display_text.length()); - if (update_popup) - UpdatePopup(); - TextChanged(); -} - -void AutocompleteEditView::SetWindowTextAndCaretPos(const std::wstring& text, - size_t caret_pos) { - HIMC imm_context = ImmGetContext(m_hWnd); - if (imm_context) { - // In Windows Vista, SetWindowText() automatically completes any ongoing - // IME composition, and updates the text of the underlying edit control. - // In Windows XP, however, SetWindowText() gets applied to the IME - // composition string if it exists, and doesn't update the underlying edit - // control. To avoid this, we force the IME to complete any outstanding - // compositions here. This is harmless in Vista and in cases where the IME - // isn't composing. - ImmNotifyIME(imm_context, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); - ImmReleaseContext(m_hWnd, imm_context); - } - - SetWindowText(text.c_str()); - PlaceCaretAt(caret_pos); -} - -bool AutocompleteEditView::IsSelectAll() { - CHARRANGE selection; - GetSel(selection); - return IsSelectAllForRange(selection); -} - -void AutocompleteEditView::SelectAll(bool reversed) { - if (reversed) - SetSelection(GetTextLength(), 0); - else - SetSelection(0, GetTextLength()); -} - -void AutocompleteEditView::RevertAll() { - ScopedFreeze freeze(this, GetTextObjectModel()); - ClosePopup(); - model_->Revert(); - saved_selection_for_focus_change_.cpMin = -1; - TextChanged(); -} - -void AutocompleteEditView::UpdatePopup() { - ScopedFreeze freeze(this, GetTextObjectModel()); - model_->SetInputInProgress(true); - - if (!model_->has_focus()) { - // When we're in the midst of losing focus, don't rerun autocomplete. This - // can happen when losing focus causes the IME to cancel/finalize a - // composition. We still want to note that user input is in progress, we - // just don't want to do anything else. - // - // Note that in this case the ScopedFreeze above was unnecessary; however, - // we're inside the callstack of OnKillFocus(), which has already frozen the - // edit, so this will never result in an unnecessary UpdateWindow() call. - return; - } - - // Figure out whether the user is trying to compose something in an IME. - bool ime_composing = false; - HIMC context = ImmGetContext(m_hWnd); - if (context) { - ime_composing = !!ImmGetCompositionString(context, GCS_COMPSTR, NULL, 0); - ImmReleaseContext(m_hWnd, context); - } - - // Don't inline autocomplete when: - // * The user is deleting text - // * The caret/selection isn't at the end of the text - // * The user has just pasted in something that replaced all the text - // * The user is trying to compose something in an IME - CHARRANGE sel; - GetSel(sel); - model_->StartAutocomplete((sel.cpMax < GetTextLength()) || ime_composing); -} - -void AutocompleteEditView::ClosePopup() { - popup_view_->model()->StopAutocomplete(); -} - -IAccessible* AutocompleteEditView::GetIAccessible() { - if (!autocomplete_accessibility_) { - CComObject<AutocompleteAccessibility>* accessibility = NULL; - if (!SUCCEEDED(CComObject<AutocompleteAccessibility>::CreateInstance( - &accessibility)) || !accessibility) - return NULL; - - // Wrap the created object in a smart pointer so it won't leak. - CComPtr<IAccessible> accessibility_comptr(accessibility); - if (!SUCCEEDED(accessibility->Initialize(this))) - return NULL; - - // Copy to the class smart pointer, and notify that an instance of - // IAccessible was allocated for m_hWnd. - autocomplete_accessibility_ = accessibility_comptr; - NotifyWinEvent(EVENT_OBJECT_CREATE, m_hWnd, OBJID_CLIENT, CHILDID_SELF); - } - // Detach to leave ref counting to the caller. - return autocomplete_accessibility_.Detach(); -} - -void AutocompleteEditView::SetDropHighlightPosition(int position) { - if (drop_highlight_position_ != position) { - RepaintDropHighlight(drop_highlight_position_); - drop_highlight_position_ = position; - RepaintDropHighlight(drop_highlight_position_); - } -} - -void AutocompleteEditView::MoveSelectedText(int new_position) { - const std::wstring selected_text(GetSelectedText()); - CHARRANGE sel; - GetSel(sel); - DCHECK((sel.cpMax != sel.cpMin) && (new_position >= 0) && - (new_position <= GetTextLength())); - - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - - // Nuke the selected text. - ReplaceSel(L"", TRUE); - - // And insert it into the new location. - if (new_position >= sel.cpMin) - new_position -= (sel.cpMax - sel.cpMin); - PlaceCaretAt(new_position); - ReplaceSel(selected_text.c_str(), TRUE); - - OnAfterPossibleChange(); -} - -void AutocompleteEditView::InsertText(int position, const std::wstring& text) { - DCHECK((position >= 0) && (position <= GetTextLength())); - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - SetSelection(position, position); - ReplaceSel(text.c_str()); - OnAfterPossibleChange(); -} - -void AutocompleteEditView::OnTemporaryTextMaybeChanged( - const std::wstring& display_text, - bool save_original_selection) { - if (save_original_selection) - GetSelection(original_selection_); - - // Set new text and cursor position. Sometimes this does extra work (e.g. - // when the new text and the old text are identical), but it's only called - // when the user manually changes the selected line in the popup, so that's - // not really a problem. Also, even when the text hasn't changed we'd want to - // update the caret, because if the user had the cursor in the middle of the - // text and then arrowed to another entry with the same text, we'd still want - // to move the caret. - ScopedFreeze freeze(this, GetTextObjectModel()); - SetWindowTextAndCaretPos(display_text, display_text.length()); - TextChanged(); -} - -bool AutocompleteEditView::OnInlineAutocompleteTextMaybeChanged( - const std::wstring& display_text, - size_t user_text_length) { - // Update the text and selection. Because this can be called repeatedly while - // typing, we've careful not to freeze the edit unless we really need to. - // Also, unlike in the temporary text case above, here we don't want to update - // the caret/selection unless we have to, since this might make the user's - // caret position change without warning during typing. - if (display_text == GetText()) - return false; - - ScopedFreeze freeze(this, GetTextObjectModel()); - // NOTE: We don't need the IME composition hack in SetWindowTextAndCaretPos() - // here, because UpdatePopup() disables inline autocomplete when a - // composition is in progress, thus preventing us from reaching this code. - SetWindowText(display_text.c_str()); - // Set a reversed selection to keep the caret in the same position, which - // avoids scrolling the user's text. - SetSelection(static_cast<LONG>(display_text.length()), - static_cast<LONG>(user_text_length)); - TextChanged(); - return true; -} - -void AutocompleteEditView::OnRevertTemporaryText() { - SetSelectionRange(original_selection_); - TextChanged(); -} - -void AutocompleteEditView::OnBeforePossibleChange() { - // Record our state. - text_before_change_ = GetText(); - GetSelection(sel_before_change_); -} - -bool AutocompleteEditView::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; - GetSelection(new_sel); - const int length = GetTextLength(); - if ((new_sel.cpMin > length) || (new_sel.cpMax > length)) { - if (new_sel.cpMin > length) - new_sel.cpMin = length; - if (new_sel.cpMax > length) - new_sel.cpMax = length; - SetSelectionRange(new_sel); - } - const bool selection_differs = (new_sel.cpMin != sel_before_change_.cpMin) || - (new_sel.cpMax != sel_before_change_.cpMax); - const bool at_end_of_edit = - (new_sel.cpMin == length) && (new_sel.cpMax == length); - - // See if the text or selection have changed since OnBeforePossibleChange(). - const std::wstring new_text(GetText()); - const bool text_differs = (new_text != text_before_change_); - - // When the user has deleted text, we don't allow inline autocomplete. Make - // sure to not flag cases like selecting part of the text and then pasting - // (or typing) the prefix of that selection. (We detect these by making - // sure the caret, which should be after any insertion, hasn't moved - // forward of the old selection start.) - const bool just_deleted_text = - (text_before_change_.length() > new_text.length()) && - (new_sel.cpMin <= std::min(sel_before_change_.cpMin, - sel_before_change_.cpMax)); - - - const bool something_changed = model_->OnAfterPossibleChange(new_text, - selection_differs, text_differs, just_deleted_text, at_end_of_edit); - - if (something_changed && text_differs) - TextChanged(); - - return something_changed; -} - -void AutocompleteEditView::PasteAndGo(const std::wstring& text) { - if (CanPasteAndGo(text)) - model_->PasteAndGo(); -} - -bool AutocompleteEditView::OverrideAccelerator( - const views::Accelerator& accelerator) { - // Only override <esc>. - if ((accelerator.GetKeyCode() != VK_ESCAPE) || accelerator.IsAltDown()) - return false; - - ScopedFreeze freeze(this, GetTextObjectModel()); - return model_->OnEscapeKeyPressed(); -} - -void AutocompleteEditView::HandleExternalMsg(UINT msg, - UINT flags, - const CPoint& screen_point) { - if (msg == WM_CAPTURECHANGED) { - SendMessage(msg, 0, NULL); - return; - } - - CPoint client_point(screen_point); - ::MapWindowPoints(NULL, m_hWnd, &client_point, 1); - SendMessage(msg, flags, MAKELPARAM(client_point.x, client_point.y)); -} - -bool AutocompleteEditView::IsCommandEnabled(int id) const { - switch (id) { - case IDS_UNDO: return !!CanUndo(); - case IDS_CUT: return !!CanCut(); - case IDS_COPY: return !!CanCopy(); - case IDS_PASTE: return !!CanPaste(); - case IDS_PASTE_AND_GO: return CanPasteAndGo(GetClipboardText()); - case IDS_SELECT_ALL: return !!CanSelectAll(); - case IDS_EDIT_SEARCH_ENGINES: - return command_updater_->IsCommandEnabled(IDC_EDIT_SEARCH_ENGINES); - default: NOTREACHED(); return false; - } -} - -bool AutocompleteEditView::GetContextualLabel(int id, std::wstring* out) const { - if ((id != IDS_PASTE_AND_GO) || - // No need to change the default IDS_PASTE_AND_GO label unless this is a - // search. - !model_->is_paste_and_search()) - return false; - - out->assign(l10n_util::GetString(IDS_PASTE_AND_SEARCH)); - return true; -} - -void AutocompleteEditView::ExecuteCommand(int id) { - ScopedFreeze freeze(this, GetTextObjectModel()); - if (id == IDS_PASTE_AND_GO) { - // This case is separate from the switch() below since we don't want to wrap - // it in OnBefore/AfterPossibleChange() calls. - model_->PasteAndGo(); - return; - } - - OnBeforePossibleChange(); - switch (id) { - case IDS_UNDO: - Undo(); - break; - - case IDS_CUT: - Cut(); - break; - - case IDS_COPY: - Copy(); - break; - - case IDS_PASTE: - Paste(); - break; - - case IDS_SELECT_ALL: - SelectAll(false); - break; - - case IDS_EDIT_SEARCH_ENGINES: - command_updater_->ExecuteCommand(IDC_EDIT_SEARCH_ENGINES); - break; - - default: - NOTREACHED(); - break; - } - OnAfterPossibleChange(); -} - -// static -int CALLBACK AutocompleteEditView::WordBreakProc(LPTSTR edit_text, - int current_pos, - int num_bytes, - int action) { - // TODO(pkasting): http://b/1111308 We should let other people, like ICU and - // GURL, do the work for us here instead of writing all this ourselves. - - // Sadly, even though the MSDN docs claim that the third parameter here is a - // number of characters, they lie. It's a number of bytes. - const int length = num_bytes / sizeof(wchar_t); - - // With no clear guidance from the MSDN docs on how to handle "not found" in - // the "find the nearest xxx..." cases below, I cap the return values at - // [0, length]. Since one of these (0) is also a valid position, the return - // values are thus ambiguous :( - switch (action) { - // Find nearest character before current position that begins a word. - case WB_LEFT: - case WB_MOVEWORDLEFT: { - if (current_pos < 2) { - // Either current_pos == 0, so we have a "not found" case and return 0, - // or current_pos == 1, and the only character before this position is - // at 0. - return 0; - } - - // Look for a delimiter before the previous character; the previous word - // starts immediately after. (If we looked for a delimiter before the - // current character, we could stop on the immediate prior character, - // which would mean we'd return current_pos -- which isn't "before the - // current position".) - const int prev_delim = - WordBreakProc(edit_text, current_pos - 1, num_bytes, WB_LEFTBREAK); - - if ((prev_delim == 0) && - !WordBreakProc(edit_text, 0, num_bytes, WB_ISDELIMITER)) { - // Got back 0, but position 0 isn't a delimiter. This was a "not - // found" 0, so return one of our own. - return 0; - } - - return prev_delim + 1; - } - - // Find nearest character after current position that begins a word. - case WB_RIGHT: - case WB_MOVEWORDRIGHT: { - if (WordBreakProc(edit_text, current_pos, num_bytes, WB_ISDELIMITER)) { - // The current character is a delimiter, so the next character starts - // a new word. Done. - return current_pos + 1; - } - - // Look for a delimiter after the current character; the next word starts - // immediately after. - const int next_delim = - WordBreakProc(edit_text, current_pos, num_bytes, WB_RIGHTBREAK); - if (next_delim == length) { - // Didn't find a delimiter. Return length to signal "not found". - return length; - } - - return next_delim + 1; - } - - // Determine if the current character delimits words. - case WB_ISDELIMITER: - return !!(WordBreakProc(edit_text, current_pos, num_bytes, WB_CLASSIFY) & - WBF_BREAKLINE); - - // Return the classification of the current character. - case WB_CLASSIFY: - if (IsWhitespace(edit_text[current_pos])) { - // Whitespace normally breaks words, but the MSDN docs say that we must - // not break on the CRs in a "CR, LF" or a "CR, CR, LF" sequence. Just - // check for an arbitrarily long sequence of CRs followed by LF and - // report "not a delimiter" for the current CR in that case. - while ((current_pos < (length - 1)) && - (edit_text[current_pos] == 0x13)) { - if (edit_text[++current_pos] == 0x10) - return WBF_ISWHITE; - } - return WBF_BREAKLINE | WBF_ISWHITE; - } - - // Punctuation normally breaks words, but the first two characters in - // "://" (end of scheme) should not be breaks, so that "http://" will be - // treated as one word. - if (ispunct(edit_text[current_pos], std::locale()) && - !SchemeEnd(edit_text, current_pos, length) && - !SchemeEnd(edit_text, current_pos - 1, length)) - return WBF_BREAKLINE; - - // Normal character, no flags. - return 0; - - // Finds nearest delimiter before current position. - case WB_LEFTBREAK: - for (int i = current_pos - 1; i >= 0; --i) { - if (WordBreakProc(edit_text, i, num_bytes, WB_ISDELIMITER)) - return i; - } - return 0; - - // Finds nearest delimiter after current position. - case WB_RIGHTBREAK: - for (int i = current_pos + 1; i < length; ++i) { - if (WordBreakProc(edit_text, i, num_bytes, WB_ISDELIMITER)) - return i; - } - return length; - } - - NOTREACHED(); - return 0; -} - -// static -bool AutocompleteEditView::SchemeEnd(LPTSTR edit_text, - int current_pos, - int length) { - return (current_pos >= 0) && - ((length - current_pos) > 2) && - (edit_text[current_pos] == ':') && - (edit_text[current_pos + 1] == '/') && - (edit_text[current_pos + 2] == '/'); -} - -void AutocompleteEditView::OnChar(TCHAR ch, UINT repeat_count, UINT flags) { - // Don't let alt-enter beep. Not sure this is necessary, as the standard - // alt-enter will hit DiscardWMSysChar() and get thrown away, and - // ctrl-alt-enter doesn't seem to reach here for some reason? At least not on - // my system... still, this is harmless and maybe necessary in other locales. - if (ch == VK_RETURN && (flags & KF_ALTDOWN)) - return; - - // Escape is processed in OnKeyDown. Don't let any WM_CHAR messages propagate - // as we don't want the RichEdit to do anything funky. - if (ch == VK_ESCAPE && !(flags & KF_ALTDOWN)) - return; - - if (ch == VK_TAB) { - // Don't add tabs to the input. - return; - } - - HandleKeystroke(GetCurrentMessage()->message, ch, repeat_count, flags); -} - -void AutocompleteEditView::OnContextMenu(HWND window, const CPoint& point) { - if (point.x == -1 || point.y == -1) { - POINT p; - GetCaretPos(&p); - MapWindowPoints(HWND_DESKTOP, &p, 1); - context_menu_->RunMenuAt(p.x, p.y); - } else { - context_menu_->RunMenuAt(point.x, point.y); - } -} - -void AutocompleteEditView::OnCopy() { - const std::wstring text(GetSelectedText()); - if (text.empty()) - return; - - ScopedClipboardWriter scw(g_browser_process->clipboard_service()); - scw.WriteText(text); - - // Check if the user is copying the whole address bar. If they are, we - // assume they are trying to copy a URL and write this to the clipboard as a - // hyperlink. - if (static_cast<int>(text.length()) < GetTextLength()) - return; - - // The entire control is selected. Let's see what the user typed. We can't - // use model_->CurrentTextIsURL() or model_->GetDataForURLExport() because - // right now the user is probably holding down control to cause the copy, - // which will screw up our calculation of the desired_tld. - GURL url; - if (model_->GetURLForText(text, &url)) - scw.WriteHyperlink(text, url.spec()); -} - -void AutocompleteEditView::OnCut() { - OnCopy(); - - // This replace selection will have no effect (even on the undo stack) if the - // current selection is empty. - ReplaceSel(L"", true); -} - -LRESULT AutocompleteEditView::OnGetObject(UINT uMsg, - WPARAM wparam, - LPARAM lparam) { - // Accessibility readers will send an OBJID_CLIENT message. - if (lparam == OBJID_CLIENT) { - // Re-attach for internal re-usage of accessibility pointer. - autocomplete_accessibility_.Attach(GetIAccessible()); - - if (autocomplete_accessibility_) { - return LresultFromObject(IID_IAccessible, wparam, - autocomplete_accessibility_.p); - } - } - return 0; -} - -LRESULT AutocompleteEditView::OnImeComposition(UINT message, - WPARAM wparam, - LPARAM lparam) { - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - LRESULT result = DefWindowProc(message, wparam, lparam); - if (!OnAfterPossibleChange() && (lparam & GCS_RESULTSTR)) { - // The result string changed, but the text in the popup didn't actually - // change. This means the user finalized the composition. Rerun - // autocomplete so that we can now trigger inline autocomplete if - // applicable. - // - // Note that if we're in the midst of losing focus, UpdatePopup() won't - // actually rerun autocomplete, but will just set local state correctly. - UpdatePopup(); - } - return result; -} - -void AutocompleteEditView::OnKeyDown(TCHAR key, UINT repeat_count, UINT flags) { - if (OnKeyDownAllModes(key, repeat_count, flags) || popup_window_mode_ || - OnKeyDownOnlyWritable(key, repeat_count, flags)) - 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 AutocompleteEditView::OnKeyUp(TCHAR key, UINT repeat_count, UINT flags) { - if (key == VK_CONTROL) - model_->OnControlKeyChanged(false); - - SetMsgHandled(false); -} - -void AutocompleteEditView::OnKillFocus(HWND focus_wnd) { - if (m_hWnd == focus_wnd) { - // Focus isn't actually leaving. - SetMsgHandled(false); - return; - } - - // Close the popup. - ClosePopup(); - - // Save the user's existing selection to restore it later. - GetSelection(saved_selection_for_focus_change_); - - // Tell the model to reset itself. - model_->OnKillFocus(); - - // Let the CRichEditCtrl do its default handling. This will complete any - // in-progress IME composition. We must do this after setting has_focus_ to - // false so that UpdatePopup() will know not to rerun autocomplete. - ScopedFreeze freeze(this, GetTextObjectModel()); - DefWindowProc(WM_KILLFOCUS, reinterpret_cast<WPARAM>(focus_wnd), 0); - - // Hide the "Type to search" hint if necessary. We do this after calling - // DefWindowProc() because processing the resulting IME messages may notify - // the controller that input is in progress, which could cause the visible - // hints to change. (I don't know if there's a real scenario where they - // actually do change, but this is safest.) - if (model_->show_search_hint() || - (model_->is_keyword_hint() && !model_->keyword().empty())) - controller_->OnChanged(); - - // Cancel any user selection and scroll the text back to the beginning of the - // URL. We have to do this after calling DefWindowProc() because otherwise - // an in-progress IME composition will be completed at the new caret position, - // resulting in the string jumping unexpectedly to the front of the edit. - PlaceCaretAt(0); -} - -void AutocompleteEditView::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; - possible_drag_ = false; - - // Modifying the selection counts as accepting any inline autocompletion, so - // track "changes" made by clicking the mouse button. - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - DefWindowProc(WM_LBUTTONDBLCLK, keys, - MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); - OnAfterPossibleChange(); - - gaining_focus_.reset(); // See NOTE in OnMouseActivate(). -} - -void AutocompleteEditView::OnLButtonDown(UINT keys, const CPoint& point) { - if (gaining_focus_.get()) { - // This click is giving us focus, so we need to track how much the mouse - // moves to see if it's a drag or just a click. Clicks should select all - // the text. - tracking_click_ = true; - mouse_down_point_ = point; - - // When Chrome was already the activated app, we haven't reached - // OnSetFocus() yet. When we get there, don't restore the saved selection, - // since it will just screw up the user's interaction with the edit. - saved_selection_for_focus_change_.cpMin = -1; - - // Crazy hack: In this particular case, the CRichEditCtrl seems to have an - // internal flag that discards the next WM_LBUTTONDOWN without processing - // it, so that clicks on the edit when its owning app is not activated are - // eaten rather than processed (despite whatever the return value of - // DefWindowProc(WM_MOUSEACTIVATE, ...) may say). This behavior is - // confusing and we want the click to be treated normally. So, to reset the - // CRichEditCtrl's internal flag, we pass it an extra WM_LBUTTONDOWN here - // (as well as a matching WM_LBUTTONUP, just in case we'd be confusing some - // kind of state tracking otherwise). - DefWindowProc(WM_LBUTTONDOWN, keys, MAKELPARAM(point.x, point.y)); - DefWindowProc(WM_LBUTTONUP, keys, MAKELPARAM(point.x, point.y)); - } - - // 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; - - if (!gaining_focus_.get() && !is_triple_click) - OnPossibleDrag(point); - - - // Modifying the selection counts as accepting any inline autocompletion, so - // track "changes" made by clicking the mouse button. - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - DefWindowProc(WM_LBUTTONDOWN, keys, - MAKELPARAM(ClipXCoordToVisibleText(point.x, is_triple_click), - point.y)); - OnAfterPossibleChange(); - - gaining_focus_.reset(); -} - -void AutocompleteEditView::OnLButtonUp(UINT keys, const CPoint& point) { - // default processing should happen first so we can see the result of the - // selection - ScopedFreeze freeze(this, GetTextObjectModel()); - DefWindowProc(WM_LBUTTONUP, keys, - MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); - - // When the user has clicked and released to give us focus, select all. - if (tracking_click_ && !win_util::IsDrag(mouse_down_point_, point)) { - // Select all in the reverse direction so as not to scroll the caret - // into view and shift the contents jarringly. - SelectAll(true); - possible_drag_ = false; - } - - tracking_click_ = false; - - UpdateDragDone(keys); -} - -LRESULT AutocompleteEditView::OnMouseActivate(HWND window, - UINT hit_test, - UINT mouse_message) { - // First, give other handlers a chance to handle the message to see if we are - // actually going to activate and gain focus. - LRESULT result = DefWindowProc(WM_MOUSEACTIVATE, - reinterpret_cast<WPARAM>(window), - MAKELPARAM(hit_test, mouse_message)); - // Check if we're getting focus from a left click. We have to do this here - // rather than in OnLButtonDown() since in many scenarios OnSetFocus() will be - // reached before OnLButtonDown(), preventing us from detecting this properly - // there. Also in those cases, we need to already know in OnSetFocus() that - // we should not restore the saved selection. - if (!model_->has_focus() && (mouse_message == WM_LBUTTONDOWN) && - (result == MA_ACTIVATE)) { - DCHECK(!gaining_focus_.get()); - gaining_focus_.reset(new ScopedFreeze(this, GetTextObjectModel())); - // NOTE: Despite |mouse_message| being WM_LBUTTONDOWN here, we're not - // guaranteed to call OnLButtonDown() later! Specifically, if this is the - // second click of a double click, we'll reach here but later call - // OnLButtonDblClk(). Make sure |gaining_focus_| gets reset both places, or - // we'll have visual glitchiness and then DCHECK failures. - - // Don't restore saved selection, it will just screw up our interaction - // with this edit. - saved_selection_for_focus_change_.cpMin = -1; - } - return result; -} - -void AutocompleteEditView::OnMouseMove(UINT keys, const CPoint& point) { - if (possible_drag_) { - StartDragIfNecessary(point); - // Don't fall through to default mouse handling, otherwise a second - // drag session may start. - return; - } - - if (tracking_click_ && !win_util::IsDrag(mouse_down_point_, point)) - return; - - tracking_click_ = false; - - // Return quickly if this can't change the selection/cursor, so we don't - // create a ScopedFreeze (and thus do an UpdateWindow()) on every - // WM_MOUSEMOVE. - if (!(keys & MK_LBUTTON)) { - DefWindowProc(WM_MOUSEMOVE, keys, MAKELPARAM(point.x, point.y)); - return; - } - - // 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(); - } -} - -void AutocompleteEditView::OnPaint(HDC bogus_hdc) { - // We need to paint over the top of the edit. If we simply let the edit do - // its default painting, then do ours into the window DC, the screen is - // updated in between and we can get flicker. To avoid this, we force the - // edit to paint into a memory DC, which we also paint onto, then blit the - // whole thing to the screen. - - // Don't paint if not necessary. - CRect paint_clip_rect; - if (!GetUpdateRect(&paint_clip_rect, true)) - return; - - // Begin painting, and create a memory DC for the edit to paint into. - CPaintDC paint_dc(m_hWnd); - CDC memory_dc(CreateCompatibleDC(paint_dc)); - CRect rect; - GetClientRect(&rect); - // NOTE: This next call uses |paint_dc| instead of |memory_dc| because - // |memory_dc| contains a 1x1 monochrome bitmap by default, which will cause - // |memory_bitmap| to be monochrome, which isn't what we want. - CBitmap memory_bitmap(CreateCompatibleBitmap(paint_dc, rect.Width(), - rect.Height())); - HBITMAP old_bitmap = memory_dc.SelectBitmap(memory_bitmap); - - // Tell our intercept functions to supply our memory DC to the edit when it - // tries to call BeginPaint(). - // - // The sane way to do this would be to use WM_PRINTCLIENT to ask the edit to - // paint into our desired DC. Unfortunately, the Rich Edit 3.0 that ships - // with Windows 2000/XP/Vista doesn't handle WM_PRINTCLIENT correctly; it - // treats it just like WM_PAINT and calls BeginPaint(), ignoring our provided - // DC. The Rich Edit 6.0 that ships with Office 2007 handles this better, but - // has other issues, and we can't redistribute that DLL anyway. So instead, - // we use this scary hack. - // - // NOTE: It's possible to get nested paint calls (!) (try setting the - // permanent URL to something longer than the edit width, then selecting the - // contents of the edit, typing a character, and hitting <esc>), so we can't - // DCHECK(!edit_hwnd_) here. Instead, just save off the old HWND, which most - // of the time will be NULL. - HWND old_edit_hwnd = edit_hwnd; - edit_hwnd = m_hWnd; - paint_struct = paint_dc.m_ps; - paint_struct.hdc = memory_dc; - DefWindowProc(WM_PAINT, reinterpret_cast<WPARAM>(bogus_hdc), 0); - - // Make the selection look better. - EraseTopOfSelection(&memory_dc, rect, paint_clip_rect); - - // Draw a slash through the scheme if this is insecure. - if (insecure_scheme_component_.is_nonempty()) - DrawSlashForInsecureScheme(memory_dc, rect, paint_clip_rect); - - // Draw the drop highlight. - if (drop_highlight_position_ != -1) - DrawDropHighlight(memory_dc, rect, paint_clip_rect); - - // Blit the memory DC to the actual paint DC and clean up. - BitBlt(paint_dc, rect.left, rect.top, rect.Width(), rect.Height(), memory_dc, - rect.left, rect.top, SRCCOPY); - memory_dc.SelectBitmap(old_bitmap); - edit_hwnd = old_edit_hwnd; -} - -void AutocompleteEditView::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; - - OnPossibleDrag(point); - - SetMsgHandled(false); -} - -void AutocompleteEditView::OnNonLButtonUp(UINT keys, const CPoint& point) { - UpdateDragDone(keys); - - // Let default handler have a crack at this. - SetMsgHandled(false); -} - -void AutocompleteEditView::OnPaste() { - // Replace the selection if we have something to paste. - const std::wstring text(GetClipboardText()); - if (!text.empty()) { - // If this paste will be replacing all the text, record that, so we can do - // different behaviors in such a case. - if (IsSelectAll()) - model_->on_paste_replacing_all(); - ReplaceSel(text.c_str(), true); - } -} - -void AutocompleteEditView::OnSetFocus(HWND focus_wnd) { - model_->OnSetFocus(GetKeyState(VK_CONTROL) < 0); - - // Notify controller if it needs to show hint UI of some kind. - ScopedFreeze freeze(this, GetTextObjectModel()); - if (model_->show_search_hint() || - (model_->is_keyword_hint() && !model_->keyword().empty())) - controller_->OnChanged(); - - // Restore saved selection if available. - if (saved_selection_for_focus_change_.cpMin != -1) { - SetSelectionRange(saved_selection_for_focus_change_); - saved_selection_for_focus_change_.cpMin = -1; - } - - SetMsgHandled(false); -} - -void AutocompleteEditView::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 AutocompleteEditView::HandleKeystroke(UINT message, - TCHAR key, - UINT repeat_count, - UINT flags) { - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - DefWindowProc(message, key, MAKELPARAM(repeat_count, flags)); - OnAfterPossibleChange(); -} - -bool AutocompleteEditView::OnKeyDownOnlyWritable(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. - - int count = repeat_count; - switch (key) { - case VK_RETURN: - model_->AcceptInput((flags & KF_ALTDOWN) ? - NEW_FOREGROUND_TAB : CURRENT_TAB, false); - return true; - - case VK_UP: - count = -count; - // FALL THROUGH - case VK_DOWN: - if (flags & KF_ALTDOWN) - return false; - - model_->OnUpOrDownKeyPressed(count); - return true; - - // 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. (This is handled - // in OnKeyDownAllModes().) - // 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: - if ((flags & KF_ALTDOWN) || GetKeyState(VK_SHIFT) >= 0) - return false; - if (GetKeyState(VK_CONTROL) >= 0) { - // Cut text if possible. - CHARRANGE selection; - GetSel(selection); - if (selection.cpMin != selection.cpMax) { - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - Cut(); - OnAfterPossibleChange(); - } else if (popup_view_->model()->IsOpen()) { - // This is a bit overloaded, but we hijack Shift-Delete in this - // case to delete the current item from the pop-up. We prefer cutting - // to this when possible since that's the behavior more people expect - // from Shift-Delete, and it's more commonly useful. - popup_view_->model()->TryDeletingCurrentItem(); - } - } - return true; - - case 'X': - if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0)) - return false; - if (GetKeyState(VK_SHIFT) >= 0) { - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - Cut(); - OnAfterPossibleChange(); - } - return true; - - case VK_INSERT: - case 'V': - if ((flags & KF_ALTDOWN) || - (GetKeyState((key == 'V') ? VK_CONTROL : VK_SHIFT) >= 0)) - return false; - if (GetKeyState((key == 'V') ? VK_SHIFT : VK_CONTROL) >= 0) { - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - Paste(); - OnAfterPossibleChange(); - } - return true; - - case VK_BACK: { - if ((flags & KF_ALTDOWN) || model_->is_keyword_hint() || - model_->keyword().empty()) - return false; - - { - CHARRANGE selection; - GetSel(selection); - if ((selection.cpMin != selection.cpMax) || (selection.cpMin != 0)) - return false; - } - - // We're showing a keyword and the user pressed backspace at the beginning - // of the text. Delete the selected keyword. - ScopedFreeze freeze(this, GetTextObjectModel()); - model_->ClearKeyword(GetText()); - return true; - } - - case VK_TAB: { - if (model_->is_keyword_hint() && !model_->keyword().empty()) { - // Accept the keyword. - ScopedFreeze freeze(this, GetTextObjectModel()); - model_->AcceptKeyword(); - } - return true; - } - - case 0xbb: // Ctrl-'='. Triggers subscripting (even in plain text mode). - return true; - - default: - return false; - } -} - -bool AutocompleteEditView::OnKeyDownAllModes(TCHAR key, - UINT repeat_count, - UINT flags) { - // See KF_ALTDOWN comment atop OnKeyDownOnlyWriteable(). - - switch (key) { - case VK_CONTROL: - model_->OnControlKeyChanged(true); - return false; - - case 'C': - // See more detailed comments in OnKeyDownOnlyWriteable(). - if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0)) - return false; - if (GetKeyState(VK_SHIFT) >= 0) - Copy(); - return true; - - default: - return false; - } -} - -void AutocompleteEditView::GetSelection(CHARRANGE& sel) const { - GetSel(sel); - - // See if we need to reverse the direction of the selection. - ITextDocument* const text_object_model = GetTextObjectModel(); - if (!text_object_model) - return; - CComPtr<ITextSelection> selection; - const HRESULT hr = text_object_model->GetSelection(&selection); - DCHECK(hr == S_OK); - long flags; - selection->GetFlags(&flags); - if (flags & tomSelStartActive) - std::swap(sel.cpMin, sel.cpMax); -} - -std::wstring AutocompleteEditView::GetSelectedText() const { - // Figure out the length of the selection. - CHARRANGE sel; - GetSel(sel); - - // Grab the selected text. - std::wstring str; - GetSelText(WriteInto(&str, sel.cpMax - sel.cpMin + 1)); - return str; -} - -void AutocompleteEditView::SetSelection(LONG start, LONG end) { - SetSel(start, end); - - if (start <= end) - return; - - // We need to reverse the direction of the selection. - ITextDocument* const text_object_model = GetTextObjectModel(); - if (!text_object_model) - return; - CComPtr<ITextSelection> selection; - const HRESULT hr = text_object_model->GetSelection(&selection); - DCHECK(hr == S_OK); - selection->SetFlags(tomSelStartActive); -} - -void AutocompleteEditView::PlaceCaretAt(std::wstring::size_type pos) { - SetSelection(static_cast<LONG>(pos), static_cast<LONG>(pos)); -} - -bool AutocompleteEditView::IsSelectAllForRange(const CHARRANGE& sel) const { - const int text_length = GetTextLength(); - return ((sel.cpMin == 0) && (sel.cpMax >= text_length)) || - ((sel.cpMax == 0) && (sel.cpMin >= text_length)); -} - -LONG AutocompleteEditView::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. - RECT r; - GetRect(&r); - const int left_bound = std::max(r.left, PosFromChar(0).x); - if (x < left_bound) - return left_bound; - - // See if we need to clip to the right edge of the text. - const int length = GetTextLength(); - // Asking for the coordinate of any character past the end of the text gets - // the pixel just to the right of the last character. - const int right_bound = std::min(r.right, PosFromChar(length).x); - if ((length == 0) || (x < right_bound)) - return x; - - // 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. - return is_triple_click ? (right_bound - 1) : right_bound; -} - -void AutocompleteEditView::EmphasizeURLComponents() { - ITextDocument* const text_object_model = GetTextObjectModel(); - ScopedFreeze freeze(this, text_object_model); - ScopedSuspendUndo suspend_undo(text_object_model); - - // Save the selection. - CHARRANGE saved_sel; - GetSelection(saved_sel); - - // See whether the contents are a URL with a non-empty host portion, which we - // should emphasize. To check for a URL, rather than using the type returned - // by Parse(), ask the model, which will check the desired page transition for - // this input. This can tell us whether an UNKNOWN input string is going to - // be treated as a search or a navigation, and is the same method the Paste - // And Go system uses. - url_parse::Parsed parts; - AutocompleteInput::Parse(GetText(), model_->GetDesiredTLD(), &parts, NULL); - const bool emphasize = model_->CurrentTextIsURL() && (parts.host.len > 0); - - // Set the baseline emphasis. - CHARFORMAT cf = {0}; - cf.dwMask = CFM_COLOR; - cf.dwEffects = 0; - cf.crTextColor = GetSysColor(emphasize ? COLOR_GRAYTEXT : COLOR_WINDOWTEXT); - SelectAll(false); - SetSelectionCharFormat(cf); - - if (emphasize) { - // We've found a host name, give it more emphasis. - cf.crTextColor = GetSysColor(COLOR_WINDOWTEXT); - SetSelection(parts.host.begin, parts.host.end()); - SetSelectionCharFormat(cf); - } - - // Emphasize the scheme for security UI display purposes (if necessary). - insecure_scheme_component_.reset(); - if (!model_->user_input_in_progress() && parts.scheme.is_nonempty() && - (scheme_security_level_ != ToolbarModel::NORMAL)) { - if (scheme_security_level_ == ToolbarModel::SECURE) { - cf.crTextColor = kSecureSchemeColor; - } else { - insecure_scheme_component_.begin = parts.scheme.begin; - insecure_scheme_component_.len = parts.scheme.len; - cf.crTextColor = kInsecureSchemeColor; - } - SetSelection(parts.scheme.begin, parts.scheme.end()); - SetSelectionCharFormat(cf); - } - - // Restore the selection. - SetSelectionRange(saved_sel); -} - -void AutocompleteEditView::EraseTopOfSelection(CDC* dc, - const CRect& client_rect, - const CRect& paint_clip_rect) { - // Find the area we care about painting. We could calculate the rect - // containing just the selected portion, but there's no harm in simply erasing - // the whole top of the client area, and at least once I saw us manage to - // select the "phantom newline" briefly, which looks very weird if not clipped - // off at the same height. - CRect erase_rect(client_rect.left, client_rect.top, client_rect.right, - client_rect.top + font_y_adjustment_); - erase_rect.IntersectRect(erase_rect, paint_clip_rect); - - // Erase to the background color. - if (!erase_rect.IsRectNull()) - dc->FillSolidRect(&erase_rect, background_color_); -} - -void AutocompleteEditView::DrawSlashForInsecureScheme( - HDC hdc, - const CRect& client_rect, - const CRect& paint_clip_rect) { - DCHECK(insecure_scheme_component_.is_nonempty()); - - // Calculate the rect, in window coordinates, containing the portion of the - // scheme where we'll be drawing the slash. Vertically, we draw across one - // x-height of text, plus an additional 3 stroke diameters (the stroke width - // plus a half-stroke width of space between the stroke and the text, both - // above and below the text). - const int font_top = client_rect.top + font_y_adjustment_; - const SkScalar kStrokeWidthPixels = SkIntToScalar(2); - const int kAdditionalSpaceOutsideFont = - static_cast<int>(ceil(kStrokeWidthPixels * 1.5f)); - const CRect scheme_rect(PosFromChar(insecure_scheme_component_.begin).x, - font_top + font_ascent_ - font_x_height_ - - kAdditionalSpaceOutsideFont, - PosFromChar(insecure_scheme_component_.end()).x, - font_top + font_ascent_ + - kAdditionalSpaceOutsideFont); - - // Clip to the portion we care about and translate to canvas coordinates - // (see the canvas creation below) for use later. - CRect canvas_clip_rect, canvas_paint_clip_rect; - canvas_clip_rect.IntersectRect(scheme_rect, client_rect); - canvas_paint_clip_rect.IntersectRect(canvas_clip_rect, paint_clip_rect); - if (canvas_paint_clip_rect.IsRectNull()) - return; // We don't need to paint any of this region, so just bail early. - canvas_clip_rect.OffsetRect(-scheme_rect.left, -scheme_rect.top); - canvas_paint_clip_rect.OffsetRect(-scheme_rect.left, -scheme_rect.top); - - // Create a paint context for drawing the antialiased stroke. - SkPaint paint; - paint.setAntiAlias(true); - paint.setStrokeWidth(kStrokeWidthPixels); - paint.setStrokeCap(SkPaint::kRound_Cap); - - // Create a canvas as large as |scheme_rect| to do our drawing, and initialize - // it to fully transparent so any antialiasing will look nice when painted - // atop the edit. - ChromeCanvas canvas(scheme_rect.Width(), scheme_rect.Height(), false); - // TODO (jcampan): This const_cast should not be necessary once the SKIA - // API has been changed to return a non-const bitmap. - (const_cast<SkBitmap&>(canvas.getDevice()->accessBitmap(true))). - eraseARGB(0, 0, 0, 0); - - // Calculate the start and end of the stroke, which are just the lower left - // and upper right corners of the canvas, inset by the radius of the endcap - // so we don't clip the endcap off. - const SkScalar kEndCapRadiusPixels = kStrokeWidthPixels / SkIntToScalar(2); - const SkPoint start_point = { - kEndCapRadiusPixels, - SkIntToScalar(scheme_rect.Height()) - kEndCapRadiusPixels }; - const SkPoint end_point = { - SkIntToScalar(scheme_rect.Width()) - kEndCapRadiusPixels, - kEndCapRadiusPixels }; - - // Calculate the selection rectangle in canvas coordinates, which we'll use - // to clip the stroke so we can draw the unselected and selected portions. - CHARRANGE sel; - GetSel(sel); - const SkRect selection_rect = { - SkIntToScalar(PosFromChar(sel.cpMin).x - scheme_rect.left), - SkIntToScalar(0), - SkIntToScalar(PosFromChar(sel.cpMax).x - scheme_rect.left), - SkIntToScalar(scheme_rect.Height()) }; - - // Draw the unselected portion of the stroke. - canvas.save(); - if (selection_rect.isEmpty() || - canvas.clipRect(selection_rect, SkRegion::kDifference_Op)) { - paint.setColor(kSchemeStrikeoutColor); - canvas.drawLine(start_point.fX, start_point.fY, - end_point.fX, end_point.fY, paint); - } - canvas.restore(); - - // Draw the selected portion of the stroke. - if (!selection_rect.isEmpty() && canvas.clipRect(selection_rect)) { - paint.setColor(kSchemeSelectedStrikeoutColor); - canvas.drawLine(start_point.fX, start_point.fY, - end_point.fX, end_point.fY, paint); - } - - // Now copy what we drew to the target HDC. - canvas.getTopPlatformDevice().drawToHDC(hdc, - scheme_rect.left + canvas_paint_clip_rect.left - canvas_clip_rect.left, - std::max(scheme_rect.top, client_rect.top) + canvas_paint_clip_rect.top - - canvas_clip_rect.top, &canvas_paint_clip_rect); -} - -void AutocompleteEditView::DrawDropHighlight(HDC hdc, - const CRect& client_rect, - const CRect& paint_clip_rect) { - DCHECK(drop_highlight_position_ != -1); - - const int highlight_y = client_rect.top + font_y_adjustment_; - const int highlight_x = PosFromChar(drop_highlight_position_).x - 1; - const CRect highlight_rect(highlight_x, - highlight_y, - highlight_x + 1, - highlight_y + font_ascent_ + font_descent_); - - // Clip the highlight to the region being painted. - CRect clip_rect; - clip_rect.IntersectRect(highlight_rect, paint_clip_rect); - if (clip_rect.IsRectNull()) - return; - - HGDIOBJ last_pen = SelectObject(hdc, CreatePen(PS_SOLID, 1, RGB(0, 0, 0))); - MoveToEx(hdc, clip_rect.left, clip_rect.top, NULL); - LineTo(hdc, clip_rect.left, clip_rect.bottom); - DeleteObject(SelectObject(hdc, last_pen)); -} - -void AutocompleteEditView::TextChanged() { - ScopedFreeze freeze(this, GetTextObjectModel()); - EmphasizeURLComponents(); - controller_->OnChanged(); -} - -std::wstring AutocompleteEditView::GetClipboardText() const { - // Try text format. - ClipboardService* clipboard = g_browser_process->clipboard_service(); - if (clipboard->IsFormatAvailable(CF_UNICODETEXT)) { - std::wstring text; - clipboard->ReadText(&text); - - // Note: Unlike in the find popup and textfield view, here we completely - // remove whitespace strings containing newlines. We assume users are - // most likely pasting in URLs that may have been split into multiple - // lines in terminals, email programs, etc., and so linebreaks indicate - // completely bogus whitespace that would just cause the input to be - // invalid. - return CollapseWhitespace(text, true); - } - - // Try bookmark format. - // - // It is tempting to try bookmark format first, but the URL we get out of a - // bookmark has been cannonicalized via GURL. This means if a user copies - // and pastes from the URL bar to itself, the text will get fixed up and - // cannonicalized, which is not what the user expects. By pasting in this - // order, we are sure to paste what the user copied. - if (clipboard->IsFormatAvailable(Clipboard::GetUrlWFormatType())) { - std::string url_str; - clipboard->ReadBookmark(NULL, &url_str); - // pass resulting url string through GURL to normalize - GURL url(url_str); - if (url.is_valid()) - return UTF8ToWide(url.spec()); - } - - return std::wstring(); -} - -bool AutocompleteEditView::CanPasteAndGo(const std::wstring& text) const { - return !popup_window_mode_ && model_->CanPasteAndGo(text); -} - -ITextDocument* AutocompleteEditView::GetTextObjectModel() const { - if (!text_object_model_) { - // This is lazily initialized, instead of being initialized in the - // constructor, in order to avoid hurting startup performance. - CComPtr<IRichEditOle> ole_interface; - ole_interface.Attach(GetOleInterface()); - text_object_model_ = ole_interface; - } - return text_object_model_; -} - -void AutocompleteEditView::StartDragIfNecessary(const CPoint& point) { - if (initiated_drag_ || !win_util::IsDrag(mouse_down_point_, point)) - return; - - scoped_refptr<OSExchangeData> data = new OSExchangeData; - - DWORD supported_modes = DROPEFFECT_COPY; - - CHARRANGE sel; - GetSelection(sel); - - // We're about to start a drag session, but the edit is expecting a mouse up - // that it uses to reset internal state. If we don't send a mouse up now, - // when the mouse moves back into the edit the edit will reset the selection. - // So, we send the event now which resets the selection. We then restore the - // selection and start the drag. We always send lbuttonup as otherwise we - // might trigger a context menu (right up). This seems scary, but doesn't - // seem to cause problems. - { - ScopedFreeze freeze(this, GetTextObjectModel()); - DefWindowProc(WM_LBUTTONUP, 0, - MAKELPARAM(mouse_down_point_.x, mouse_down_point_.y)); - SetSelectionRange(sel); - } - - const std::wstring start_text(GetText()); - if (IsSelectAllForRange(sel)) { - // All the text is selected, export as URL. - GURL url; - std::wstring title; - SkBitmap favicon; - model_->GetDataForURLExport(&url, &title, &favicon); - drag_utils::SetURLAndDragImage(url, title, favicon, data.get()); - data->SetURL(url, title); - supported_modes |= DROPEFFECT_LINK; - UserMetrics::RecordAction(L"Omnibox_DragURL", model_->profile()); - } else { - supported_modes |= DROPEFFECT_MOVE; - UserMetrics::RecordAction(L"Omnibox_DragString", model_->profile()); - } - - data->SetString(GetSelectedText()); - - scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); - DWORD dropped_mode; - in_drag_ = true; - if (DoDragDrop(data, drag_source, supported_modes, &dropped_mode) == - DRAGDROP_S_DROP) { - if ((dropped_mode == DROPEFFECT_MOVE) && (start_text == GetText())) { - ScopedFreeze freeze(this, GetTextObjectModel()); - OnBeforePossibleChange(); - SetSelectionRange(sel); - ReplaceSel(L"", true); - OnAfterPossibleChange(); - } - // else case, not a move or it was a move and the drop was on us. - // If the drop was on us, EditDropTarget took care of the move so that - // we don't have to delete the text. - possible_drag_ = false; - } else { - // Drag was canceled or failed. The mouse may still be down and - // over us, in which case we need possible_drag_ to remain true so - // that we don't forward mouse move events to the edit which will - // start another drag. - // - // NOTE: we didn't use mouse capture during the mouse down as DoDragDrop - // does its own capture. - CPoint cursor_location; - GetCursorPos(&cursor_location); - - CRect client_rect; - GetClientRect(&client_rect); - - CPoint client_origin_on_screen(client_rect.left, client_rect.top); - ClientToScreen(&client_origin_on_screen); - client_rect.MoveToXY(client_origin_on_screen.x, - client_origin_on_screen.y); - possible_drag_ = (client_rect.PtInRect(cursor_location) && - ((GetKeyState(VK_LBUTTON) != 0) || - (GetKeyState(VK_MBUTTON) != 0) || - (GetKeyState(VK_RBUTTON) != 0))); - } - - in_drag_ = false; - initiated_drag_ = true; - tracking_click_ = false; -} - -void AutocompleteEditView::OnPossibleDrag(const CPoint& point) { - if (possible_drag_) - return; - - mouse_down_point_ = point; - initiated_drag_ = false; - - CHARRANGE selection; - GetSel(selection); - if (selection.cpMin != selection.cpMax) { - const POINT min_sel_location(PosFromChar(selection.cpMin)); - const POINT max_sel_location(PosFromChar(selection.cpMax)); - // NOTE: we don't consider the y location here as we always pass a - // y-coordinate in the middle to the default handler which always triggers - // a drag regardless of the y-coordinate. - possible_drag_ = (point.x >= min_sel_location.x) && - (point.x < max_sel_location.x); - } -} - -void AutocompleteEditView::UpdateDragDone(UINT keys) { - possible_drag_ = (possible_drag_ && - ((keys & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) != 0)); -} - -void AutocompleteEditView::RepaintDropHighlight(int position) { - if ((position != -1) && (position <= GetTextLength())) { - const POINT min_loc(PosFromChar(position)); - const RECT highlight_bounds = {min_loc.x - 1, font_y_adjustment_, - min_loc.x + 2, font_ascent_ + font_descent_ + font_y_adjustment_}; - InvalidateRect(&highlight_bounds, false); - } -} diff --git a/chrome/browser/autocomplete/autocomplete_edit.h b/chrome/browser/autocomplete/autocomplete_edit.h index 782f85d..e4bb2af 100644 --- a/chrome/browser/autocomplete/autocomplete_edit.h +++ b/chrome/browser/autocomplete/autocomplete_edit.h @@ -5,34 +5,18 @@ #ifndef CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_EDIT_H_ #define CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_EDIT_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 "base/scoped_ptr.h" #include "chrome/browser/autocomplete/autocomplete.h" -#include "chrome/browser/toolbar_model.h" -#include "chrome/common/gfx/chrome_font.h" #include "chrome/common/page_transition_types.h" -#include "chrome/views/menu.h" +#include "googleurl/src/gurl.h" #include "webkit/glue/window_open_disposition.h" class AutocompletePopupModel; -class CommandUpdater; class Profile; -class TabContents; -namespace views { -class View; -} +class SkBitmap; class AutocompleteEditController; class AutocompleteEditModel; class AutocompleteEditView; -class AutocompletePopupViewWin; -struct AutocompleteEditState; // TODO(pkasting): http://b/1343512 The names and contents of the classes in // this file are temporary. I am in hack-and-slash mode right now. @@ -439,485 +423,4 @@ class AutocompleteEditModel { DISALLOW_COPY_AND_ASSIGN(AutocompleteEditModel); }; -// Provides the implementation of an edit control with a drop-down -// autocomplete box. The box itself is implemented in autocomplete_popup.cc -// This file implements the edit box and management for the popup. -class AutocompleteEditView - : public CWindowImpl<AutocompleteEditView, - CRichEditCtrl, - CWinTraits<WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | - ES_NOHIDESEL> >, - public CRichEditCommands<AutocompleteEditView>, - public Menu::Delegate { - public: - struct State { - State(const CHARRANGE& selection, - const CHARRANGE& saved_selection_for_focus_change) - : selection(selection), - saved_selection_for_focus_change(saved_selection_for_focus_change) { - } - - const CHARRANGE selection; - const CHARRANGE saved_selection_for_focus_change; - }; - - DECLARE_WND_CLASS(L"Chrome_AutocompleteEditView"); - - AutocompleteEditView(const ChromeFont& font, - AutocompleteEditController* controller, - ToolbarModel* toolbar_model, - views::View* parent_view, - HWND hwnd, - Profile* profile, - CommandUpdater* command_updater, - bool popup_window_mode); - ~AutocompleteEditView(); - - AutocompleteEditModel* model() { return model_.get(); } - const AutocompleteEditModel* model() const { return model_.get(); } - - views::View* parent_view() const { return parent_view_; } - - // For use when switching tabs, this saves the current state onto the tab so - // that it can be restored during a later call to Update(). - void SaveStateToTab(TabContents* tab); - - // Called when any LocationBarView state changes. If - // |tab_for_state_restoring| is non-NULL, it points to a TabContents whose - // state we should restore. - void Update(const TabContents* tab_for_state_restoring); - - // Asks the browser to load the specified URL, which is assumed to be one of - // the popup entries, using the supplied disposition and transition type. - // |alternate_nav_url|, if non-empty, contains the alternate navigation URL - // for |url|. See comments on AutocompleteResult::GetAlternateNavURL(). - // - // |selected_line| is passed to SendOpenNotification(); see comments there. - // - // If the URL was expanded from a keyword, |keyword| is that keyword. - // - // This may close the popup. - void OpenURL(const GURL& url, - WindowOpenDisposition disposition, - PageTransition::Type transition, - const GURL& alternate_nav_url, - size_t selected_line, - const std::wstring& keyword); - - // Returns the current text of the edit control, which could be the - // "temporary" text set by the popup, the "permanent" text set by the - // browser, or just whatever the user has currently typed. - std::wstring GetText() const; - - // The user text is the text the user has manually keyed in. When present, - // this is shown in preference to the permanent text; hitting escape will - // revert to the permanent text. - void SetUserText(const std::wstring& text) { SetUserText(text, text, true); } - void SetUserText(const std::wstring& text, - const std::wstring& display_text, - bool update_popup); - - // Sets the window text and the caret position. - void SetWindowTextAndCaretPos(const std::wstring& text, size_t caret_pos); - - // Returns true if all text is selected. - bool IsSelectAll(); - - // 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(bool reversed); - - // Reverts the edit and popup back to their unedited state (permanent text - // showing, popup closed, no user input in progress). - void RevertAll(); - - // Updates the autocomplete popup and other state after the text has been - // changed by the user. - void UpdatePopup(); - - // Closes the autocomplete popup, if it's open. - void ClosePopup(); - - // Exposes custom IAccessible implementation to the overall MSAA hierarchy. - IAccessible* GetIAccessible(); - - void SetDropHighlightPosition(int position); - int drop_highlight_position() const { return drop_highlight_position_; } - - // Returns true if a drag a drop session was initiated by this edit. - bool in_drag() const { return in_drag_; } - - // Moves the selected text to the specified position. - void MoveSelectedText(int new_position); - - // Inserts the text at the specified position. - void InsertText(int position, const std::wstring& text); - - // Called when the temporary text in the model may have changed. - // |display_text| is the new text to show; |save_original_selection| is true - // when there wasn't previously a temporary text and thus we need to save off - // the user's existing selection. - void OnTemporaryTextMaybeChanged(const std::wstring& display_text, - bool save_original_selection); - - // Called when the inline autocomplete text in the model may have changed. - // |display_text| is the new text to show; |user_text_length| is the length of - // the user input portion of that (so, up to but not including the inline - // autocompletion). Returns whether the display text actually changed. - bool OnInlineAutocompleteTextMaybeChanged(const std::wstring& display_text, - size_t user_text_length); - - // Called when the temporary text has been reverted by the user. This will - // reset the user's original selection. - void OnRevertTemporaryText(); - - // 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(); - // OnAfterPossibleChange() returns true if there was a change that caused it - // to call UpdatePopup(). - bool OnAfterPossibleChange(); - - // Invokes CanPasteAndGo with the specified text, and if successful navigates - // to the appropriate URL. The behavior of this is the same as if the user - // typed in the specified text and pressed enter. - void PasteAndGo(const std::wstring& text); - - // Called before an accelerator is processed to give us a chance to override - // it. - bool OverrideAccelerator(const views::Accelerator& accelerator); - - // Handler for external events passed in to us. The View that owns us may - // send us events that we should treat as if they were events on us. - void HandleExternalMsg(UINT msg, UINT flags, const CPoint& screen_point); - - // CWindowImpl - BEGIN_MSG_MAP(AutocompleteEdit) - MSG_WM_CHAR(OnChar) - MSG_WM_CONTEXTMENU(OnContextMenu) - MSG_WM_COPY(OnCopy) - MSG_WM_CUT(OnCut) - MESSAGE_HANDLER_EX(WM_GETOBJECT, OnGetObject) - MESSAGE_HANDLER_EX(WM_IME_COMPOSITION, OnImeComposition) - MSG_WM_KEYDOWN(OnKeyDown) - MSG_WM_KEYUP(OnKeyUp) - MSG_WM_KILLFOCUS(OnKillFocus) - MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk) - MSG_WM_LBUTTONDOWN(OnLButtonDown) - MSG_WM_LBUTTONUP(OnLButtonUp) - MSG_WM_MBUTTONDOWN(OnNonLButtonDown) - MSG_WM_MBUTTONUP(OnNonLButtonUp) - MSG_WM_MOUSEACTIVATE(OnMouseActivate) - MSG_WM_MOUSEMOVE(OnMouseMove) - MSG_WM_PAINT(OnPaint) - MSG_WM_PASTE(OnPaste) - MSG_WM_RBUTTONDOWN(OnNonLButtonDown) - MSG_WM_RBUTTONUP(OnNonLButtonUp) - MSG_WM_SETFOCUS(OnSetFocus) - MSG_WM_SYSCHAR(OnSysChar) // WM_SYSxxx == WM_xxx with ALT down - MSG_WM_SYSKEYDOWN(OnKeyDown) - MSG_WM_SYSKEYUP(OnKeyUp) - DEFAULT_REFLECTION_HANDLER() // avoids black margin area - END_MSG_MAP() - - // Menu::Delegate - virtual bool IsCommandEnabled(int id) const; - virtual bool GetContextualLabel(int id, std::wstring* out) 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(AutocompleteEditView* edit, ITextDocument* text_object_model); - ~ScopedFreeze(); - - private: - AutocompleteEditView* const edit_; - ITextDocument* const text_object_model_; - - DISALLOW_COPY_AND_ASSIGN(ScopedFreeze); - }; - - // This object suspends placing any operations on the edit's undo stack until - // the object is destroyed. If we don't do this, some of the operations we - // perform behind the user's back will be undoable by the user, which feels - // bizarre and confusing. - class ScopedSuspendUndo { - public: - explicit ScopedSuspendUndo(ITextDocument* text_object_model); - ~ScopedSuspendUndo(); - - private: - ITextDocument* const text_object_model_; - - DISALLOW_COPY_AND_ASSIGN(ScopedSuspendUndo); - }; - - // Replacement word-breaking proc for the rich edit control. - static int CALLBACK WordBreakProc(LPTSTR edit_text, - int current_pos, - int num_bytes, - int action); - - // Returns true if |edit_text| starting at |current_pos| is "://". - static bool SchemeEnd(LPTSTR edit_text, int current_pos, int length); - - // Message handlers - void OnChar(TCHAR ch, UINT repeat_count, UINT flags); - void OnContextMenu(HWND window, const CPoint& point); - void OnCopy(); - void OnCut(); - LRESULT OnGetObject(UINT uMsg, WPARAM wparam, LPARAM lparam); - LRESULT OnImeComposition(UINT message, WPARAM wparam, LPARAM lparam); - void OnKeyDown(TCHAR key, UINT repeat_count, UINT flags); - void OnKeyUp(TCHAR key, UINT repeat_count, UINT flags); - void OnKillFocus(HWND focus_wnd); - void OnLButtonDblClk(UINT keys, const CPoint& point); - void OnLButtonDown(UINT keys, const CPoint& point); - void OnLButtonUp(UINT keys, const CPoint& point); - LRESULT OnMouseActivate(HWND window, UINT hit_test, UINT mouse_message); - void OnMouseMove(UINT keys, const CPoint& point); - void OnNonLButtonDown(UINT keys, const CPoint& point); - void OnNonLButtonUp(UINT keys, const CPoint& point); - void OnPaint(HDC bogus_hdc); - void OnPaste(); - void OnSetFocus(HWND focus_wnd); - 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); - - // Helper functions for OnKeyDown() that handle accelerators applicable when - // we're not read-only and all the time, respectively. These return true if - // they handled the key. - bool OnKeyDownOnlyWritable(TCHAR key, UINT repeat_count, UINT flags); - bool OnKeyDownAllModes(TCHAR key, UINT repeat_count, UINT flags); - - // Like GetSel(), but returns a range where |cpMin| will be larger than - // |cpMax| if the cursor is at the start rather than the end of the selection - // (in other words, tracks selection direction as well as offsets). - // Note the non-Google-style "non-const-ref" argument, which matches GetSel(). - void GetSelection(CHARRANGE& sel) const; - - // Returns the currently selected text of the edit control. - std::wstring GetSelectedText() const; - - // Like SetSel(), but respects the selection direction implied by |start| and - // |end|: if |end| < |start|, the effective cursor will be placed at the - // beginning of the selection. - void SetSelection(LONG start, LONG end); - - // Like SetSelection(), but takes a CHARRANGE. - void SetSelectionRange(const CHARRANGE& sel) { - SetSelection(sel.cpMin, sel.cpMax); - } - - // Places the caret at the given position. This clears any selection. - void PlaceCaretAt(size_t pos); - - // Returns true if |sel| represents a forward or backward selection of all the - // text. - bool IsSelectAllForRange(const CHARRANGE& sel) const; - - // 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; - - // Parses the contents of the control for the scheme and the host name. - // Highlights the scheme in green or red depending on it security level. - // If a host name is found, it makes it visually stronger. - void EmphasizeURLComponents(); - - // Erases the portion of the selection in the font's y-adjustment area. For - // some reason the edit draws the selection rect here even though it's not - // part of the font. - void EraseTopOfSelection(CDC* dc, - const CRect& client_rect, - const CRect& paint_clip_rect); - - // Draws a slash across the scheme if desired. - void DrawSlashForInsecureScheme(HDC hdc, - const CRect& client_rect, - const CRect& paint_clip_rect); - - // Renders the drop highlight. - void DrawDropHighlight(HDC hdc, - const CRect& client_rect, - const CRect& paint_clip_rect); - - // Internally invoked whenever the text changes in some way. - void TextChanged(); - - // Returns the current clipboard contents as a string that can be pasted in. - // In addition to just getting CF_UNICODETEXT out, this can also extract URLs - // from bookmarks on the clipboard. - std::wstring GetClipboardText() const; - - // Determines whether the user can "paste and go", given the specified text. - bool CanPasteAndGo(const std::wstring& text) const; - - // Getter for the text_object_model_, used by the ScopedXXX classes. Note - // that the pointer returned here is only valid as long as the - // AutocompleteEdit is still alive. Also, if the underlying call fails, this - // may return NULL. - ITextDocument* GetTextObjectModel() const; - - // Invoked during a mouse move. As necessary starts a drag and drop session. - void StartDragIfNecessary(const CPoint& point); - - // Invoked during a mouse down. If the mouse location is over the selection - // this sets possible_drag_ to true to indicate a drag should start if the - // user moves the mouse far enough to start a drag. - void OnPossibleDrag(const CPoint& point); - - // Invoked when a mouse button is released. If none of the buttons are still - // down, this sets possible_drag_ to false. - void UpdateDragDone(UINT keys); - - // Redraws the necessary region for a drop highlight at the specified - // position. This does nothing if position is beyond the bounds of the - // text. - void RepaintDropHighlight(int position); - - scoped_ptr<AutocompleteEditModel> model_; - - scoped_ptr<AutocompletePopupViewWin> popup_view_; - - AutocompleteEditController* controller_; - - // The parent view for the edit, used to align the popup and for - // accessibility. - views::View* parent_view_; - - ToolbarModel* toolbar_model_; - - // The object that handles additional command functionality exposed on the - // edit, such as invoking the keyword editor. - CommandUpdater* command_updater_; - - // When true, the location bar view is read only and also is has a slightly - // different presentation (font size / color). This is used for popups. - bool popup_window_mode_; - - // Non-null when the edit is gaining focus from a left click. This is only - // needed between when WM_MOUSEACTIVATE and WM_LBUTTONDOWN get processed. It - // serves two purposes: first, by communicating to OnLButtonDown() that we're - // gaining focus from a left click, it allows us to work even with the - // inconsistent order in which various Windows messages get sent (see comments - // in OnMouseActivate()). Second, by holding the edit frozen, it ensures that - // when we process WM_SETFOCUS the edit won't first redraw itself with the - // caret at the beginning, and then have it blink to where the mouse cursor - // really is shortly afterward. - scoped_ptr<ScopedFreeze> gaining_focus_; - - // When the user clicks to give us focus, we watch to see if they're clicking - // or dragging. When they're clicking, we select nothing until mouseup, then - // select all the text in the edit. During this process, tracking_click_ is - // true and mouse_down_point_ holds the original click location. At other - // times, tracking_click_ is false, and the contents of mouse_down_point_ - // should be ignored. - bool tracking_click_; - CPoint mouse_down_point_; - - // 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_; - - // Variables for tracking state before and after a possible change. - std::wstring text_before_change_; - CHARRANGE sel_before_change_; - - // Set at the same time the model's original_* members are set, and valid in - // the same cases. - CHARRANGE original_selection_; - - // Holds the user's selection across focus changes. cpMin holds -1 when - // there is no saved selection. - CHARRANGE saved_selection_for_focus_change_; - - // The context menu for the edit. - scoped_ptr<Menu> context_menu_; - - // Font we're using. We keep a reference to make sure the font supplied to - // the constructor doesn't go away before we do. - ChromeFont font_; - - // Metrics about the font, which we keep so we don't need to recalculate them - // every time we paint. |font_y_adjustment_| is the number of pixels we need - // to shift the font vertically in order to make its baseline be at our - // desired baseline in the edit. - int font_ascent_; - int font_descent_; - int font_x_height_; - int font_y_adjustment_; - - // If true, indicates the mouse is down and if the mouse is moved enough we - // should start a drag. - bool possible_drag_; - - // If true, we're in a call to DoDragDrop. - bool in_drag_; - - // If true indicates we've run a drag and drop session. This is used to - // avoid starting two drag and drop sessions if the drag is canceled while - // the mouse is still down. - bool initiated_drag_; - - // Position of the drop highlight. If this is -1, there is no drop highlight. - int drop_highlight_position_; - - // Security UI-related data. - COLORREF background_color_; - ToolbarModel::SecurityLevel scheme_security_level_; - - // This interface is useful for accessing the CRichEditCtrl at a low level. - mutable CComQIPtr<ITextDocument> text_object_model_; - - // This contains the scheme char start and stop indexes that should be - // striken-out when displaying an insecure scheme. - url_parse::Component insecure_scheme_component_; - - // Instance of accessibility information and handling. - mutable CComPtr<IAccessible> autocomplete_accessibility_; - - DISALLOW_COPY_AND_ASSIGN(AutocompleteEditView); -}; - -// The AutocompleteEditState struct contains enough information about the -// AutocompleteEditModel and AutocompleteEditView to save/restore a user's -// typing, caret position, etc. across tab changes. We explicitly don't -// preserve things like whether the popup was open as this might be weird. -struct AutocompleteEditState { - AutocompleteEditState(const AutocompleteEditModel::State model_state, - const AutocompleteEditView::State view_state) - : model_state(model_state), - view_state(view_state) { - } - - const AutocompleteEditModel::State model_state; - const AutocompleteEditView::State view_state; -}; - #endif // CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_EDIT_H_ diff --git a/chrome/browser/autocomplete/autocomplete_edit_view.h b/chrome/browser/autocomplete/autocomplete_edit_view.h new file mode 100644 index 0000000..93982fe --- /dev/null +++ b/chrome/browser/autocomplete/autocomplete_edit_view.h @@ -0,0 +1,117 @@ +// Copyright (c) 2006-2008 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. + +// This file defines the interface class AutocompleteEditView. Each toolkit +// will implement the edit view differently, so that code is inheriently +// platform specific. However, the AutocompleteEditModel needs to do some +// communication with the view. Since the model is shared between platforms, +// we need to define an interface that all view implementations will share. + +#ifndef CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_EDIT_VIEW_H_ +#define CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_EDIT_VIEW_H_ + +#include <string> + +#include "chrome/common/page_transition_types.h" +#include "webkit/glue/window_open_disposition.h" + +class AutocompleteEditModel; +class GURL; +class TabContents; + +class AutocompleteEditView { + public: + // Used by the automation system for getting at the model from the view. + virtual AutocompleteEditModel* model() = 0; + virtual const AutocompleteEditModel* model() const = 0; + + // For use when switching tabs, this saves the current state onto the tab so + // that it can be restored during a later call to Update(). + virtual void SaveStateToTab(TabContents* tab) = 0; + + // Called when any LocationBarView state changes. If + // |tab_for_state_restoring| is non-NULL, it points to a TabContents whose + // state we should restore. + virtual void Update(const TabContents* tab_for_state_restoring) = 0; + + // Asks the browser to load the specified URL, which is assumed to be one of + // the popup entries, using the supplied disposition and transition type. + // |alternate_nav_url|, if non-empty, contains the alternate navigation URL + // for |url|. See comments on AutocompleteResult::GetAlternateNavURL(). + // + // |selected_line| is passed to SendOpenNotification(); see comments there. + // + // If the URL was expanded from a keyword, |keyword| is that keyword. + // + // This may close the popup. + virtual void OpenURL(const GURL& url, + WindowOpenDisposition disposition, + PageTransition::Type transition, + const GURL& alternate_nav_url, + size_t selected_line, + const std::wstring& keyword) = 0; + + // Returns the current text of the edit control, which could be the + // "temporary" text set by the popup, the "permanent" text set by the + // browser, or just whatever the user has currently typed. + virtual std::wstring GetText() const = 0; + + // The user text is the text the user has manually keyed in. When present, + // this is shown in preference to the permanent text; hitting escape will + // revert to the permanent text. + virtual void SetUserText(const std::wstring& text) = 0; + virtual void SetUserText(const std::wstring& text, + const std::wstring& display_text, + bool update_popup) = 0; + + // Sets the window text and the caret position. + virtual void SetWindowTextAndCaretPos(const std::wstring& text, + size_t caret_pos) = 0; + + // Returns true if all text is selected. + virtual bool IsSelectAll() = 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(bool reversed) = 0; + + // Reverts the edit and popup back to their unedited state (permanent text + // showing, popup closed, no user input in progress). + virtual void RevertAll() = 0; + + // Updates the autocomplete popup and other state after the text has been + // changed by the user. + virtual void UpdatePopup() = 0; + + // Closes the autocomplete popup, if it's open. + virtual void ClosePopup() = 0; + + // Called when the temporary text in the model may have changed. + // |display_text| is the new text to show; |save_original_selection| is true + // when there wasn't previously a temporary text and thus we need to save off + // the user's existing selection. + virtual void OnTemporaryTextMaybeChanged(const std::wstring& display_text, + bool save_original_selection) = 0; + + // Called when the inline autocomplete text in the model may have changed. + // |display_text| is the new text to show; |user_text_length| is the length of + // the user input portion of that (so, up to but not including the inline + // autocompletion). Returns whether the display text actually changed. + virtual bool OnInlineAutocompleteTextMaybeChanged( + const std::wstring& display_text, size_t user_text_length) = 0; + + // Called when the temporary text has been reverted by the user. This will + // reset the user's original selection. + virtual void OnRevertTemporaryText() = 0; + + // 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. + virtual void OnBeforePossibleChange() = 0; + // OnAfterPossibleChange() returns true if there was a change that caused it + // to call UpdatePopup(). + virtual bool OnAfterPossibleChange() = 0; +}; + +#endif // CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_EDIT_VIEW_H_ diff --git a/chrome/browser/autocomplete/autocomplete_edit_view_win.cc b/chrome/browser/autocomplete/autocomplete_edit_view_win.cc new file mode 100644 index 0000000..339fc22 --- /dev/null +++ b/chrome/browser/autocomplete/autocomplete_edit_view_win.cc @@ -0,0 +1,2185 @@ +// Copyright (c) 2006-2008 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 "chrome/browser/autocomplete/autocomplete_edit_view_win.h" + +#include <locale> + +#include "base/base_drag_source.h" +#include "base/base_drop_target.h" +#include "base/basictypes.h" +#include "base/clipboard.h" +#include "base/iat_patch.h" +#include "base/lazy_instance.h" +#include "base/ref_counted.h" +#include "base/scoped_clipboard_writer.h" +#include "base/string_util.h" +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/browser/autocomplete/autocomplete_accessibility.h" +#include "chrome/browser/autocomplete/autocomplete_popup_model.h" +#include "chrome/browser/autocomplete/autocomplete_popup_view_win.h" +#include "chrome/browser/autocomplete/edit_drop_target.h" +#include "chrome/browser/autocomplete/keyword_provider.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/command_updater.h" +#include "chrome/browser/drag_utils.h" +#include "chrome/browser/metrics/user_metrics.h" +#include "chrome/browser/net/url_fixer_upper.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/browser/search_engines/template_url_model.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/views/location_bar_view.h" +#include "chrome/common/clipboard_service.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/utils.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/l10n_util_win.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/os_exchange_data.h" +#include "chrome/common/win_util.h" +#include "googleurl/src/url_util.h" +#include "grit/generated_resources.h" +#include "skia/ext/skia_utils_win.h" + +#pragma comment(lib, "oleacc.lib") // Needed for accessibility support. + +/////////////////////////////////////////////////////////////////////////////// +// AutocompleteEditModel + +// A single AutocompleteController used solely for making synchronous calls to +// determine how to deal with the clipboard contents for Paste And Go +// functionality. We avoid using the popup's controller here because we don't +// want to interrupt in-progress queries or modify the popup state just +// because the user right-clicked the edit. We don't need a controller for +// every edit because this will always be accessed on the main thread, so we +// won't have thread-safety problems. +static AutocompleteController* paste_and_go_controller = NULL; +static int paste_and_go_controller_refcount = 0; + +namespace { + +// EditDropTarget is the IDropTarget implementation installed on +// AutocompleteEditViewWin. EditDropTarget prefers URL over plain text. A drop +// of a URL replaces all the text of the edit and navigates immediately to the +// URL. A drop of plain text from the same edit either copies or moves the +// selected text, and a drop of plain text from a source other than the edit +// does a paste and go. +class EditDropTarget : public BaseDropTarget { + public: + explicit EditDropTarget(AutocompleteEditViewWin* edit); + + protected: + virtual DWORD OnDragEnter(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect); + virtual DWORD OnDragOver(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect); + virtual void OnDragLeave(IDataObject* data_object); + virtual DWORD OnDrop(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect); + + private: + // If dragging a string, the drop highlight position of the edit is reset + // based on the mouse position. + void UpdateDropHighlightPosition(const POINT& cursor_screen_position); + + // Resets the visual drop indicates we install on the edit. + void ResetDropHighlights(); + + // The edit we're the drop target for. + AutocompleteEditViewWin* edit_; + + // If true, the drag session contains a URL. + bool drag_has_url_; + + // If true, the drag session contains a string. If drag_has_url_ is true, + // this is false regardless of whether the clipboard has a string. + bool drag_has_string_; + + DISALLOW_EVIL_CONSTRUCTORS(EditDropTarget); +}; + +// A helper method for determining a valid DROPEFFECT given the allowed +// DROPEFFECTS. We prefer copy over link. +DWORD CopyOrLinkDropEffect(DWORD effect) { + if (effect & DROPEFFECT_COPY) + return DROPEFFECT_COPY; + if (effect & DROPEFFECT_LINK) + return DROPEFFECT_LINK; + return DROPEFFECT_NONE; +} + +EditDropTarget::EditDropTarget(AutocompleteEditViewWin* edit) + : BaseDropTarget(edit->m_hWnd), + edit_(edit), + drag_has_url_(false), + drag_has_string_(false) { +} + +DWORD EditDropTarget::OnDragEnter(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + OSExchangeData os_data(data_object); + drag_has_url_ = os_data.HasURL(); + drag_has_string_ = !drag_has_url_ && os_data.HasString(); + if (drag_has_url_) { + if (edit_->in_drag()) { + // The edit we're associated with originated the drag. No point in + // allowing the user to drop back on us. + drag_has_url_ = false; + } + // NOTE: it would be nice to visually show all the text is going to + // be replaced by selecting all, but this caused painting problems. In + // particular the flashing caret would appear outside the edit! For now + // we stick with no visual indicator other than that shown own the mouse + // cursor. + } + return OnDragOver(data_object, key_state, cursor_position, effect); +} + +DWORD EditDropTarget::OnDragOver(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + if (drag_has_url_) + return CopyOrLinkDropEffect(effect); + + if (drag_has_string_) { + UpdateDropHighlightPosition(cursor_position); + if (edit_->drop_highlight_position() == -1 && edit_->in_drag()) + return DROPEFFECT_NONE; + if (edit_->in_drag()) { + // The edit we're associated with originated the drag. Do the normal drag + // behavior. + DCHECK((effect & DROPEFFECT_COPY) && (effect & DROPEFFECT_MOVE)); + return (key_state & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE; + } + // Our edit didn't originate the drag, only allow link or copy. + return CopyOrLinkDropEffect(effect); + } + + return DROPEFFECT_NONE; +} + +void EditDropTarget::OnDragLeave(IDataObject* data_object) { + ResetDropHighlights(); +} + +DWORD EditDropTarget::OnDrop(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + OSExchangeData os_data(data_object); + + if (drag_has_url_) { + GURL url; + std::wstring title; + if (os_data.GetURLAndTitle(&url, &title)) { + edit_->SetUserText(UTF8ToWide(url.spec())); + edit_->model()->AcceptInput(CURRENT_TAB, true); + return CopyOrLinkDropEffect(effect); + } + } else if (drag_has_string_) { + int string_drop_position = edit_->drop_highlight_position(); + std::wstring text; + if ((string_drop_position != -1 || !edit_->in_drag()) && + os_data.GetString(&text)) { + DCHECK(string_drop_position == -1 || + ((string_drop_position >= 0) && + (string_drop_position <= edit_->GetTextLength()))); + const DWORD drop_operation = + OnDragOver(data_object, key_state, cursor_position, effect); + if (edit_->in_drag()) { + if (drop_operation == DROPEFFECT_MOVE) + edit_->MoveSelectedText(string_drop_position); + else + edit_->InsertText(string_drop_position, text); + } else { + edit_->PasteAndGo(CollapseWhitespace(text, true)); + } + ResetDropHighlights(); + return drop_operation; + } + } + + ResetDropHighlights(); + + return DROPEFFECT_NONE; +} + +void EditDropTarget::UpdateDropHighlightPosition( + const POINT& cursor_screen_position) { + if (drag_has_string_) { + POINT client_position = cursor_screen_position; + ScreenToClient(edit_->m_hWnd, &client_position); + int drop_position = edit_->CharFromPos(client_position); + if (edit_->in_drag()) { + // Our edit originated the drag, don't allow a drop if over the selected + // region. + LONG sel_start, sel_end; + edit_->GetSel(sel_start, sel_end); + if ((sel_start != sel_end) && (drop_position >= sel_start) && + (drop_position <= sel_end)) + drop_position = -1; + } else { + // A drop from a source other than the edit replaces all the text, so + // we don't show the drop location. See comment in OnDragEnter as to why + // we don't try and select all here. + drop_position = -1; + } + edit_->SetDropHighlightPosition(drop_position); + } +} + +void EditDropTarget::ResetDropHighlights() { + if (drag_has_string_) + edit_->SetDropHighlightPosition(-1); +} + +// The AutocompleteEditState struct contains enough information about the +// AutocompleteEditModel and AutocompleteEditViewWin to save/restore a user's +// typing, caret position, etc. across tab changes. We explicitly don't +// preserve things like whether the popup was open as this might be weird. +struct AutocompleteEditState { + AutocompleteEditState(const AutocompleteEditModel::State model_state, + const AutocompleteEditViewWin::State view_state) + : model_state(model_state), + view_state(view_state) { + } + + const AutocompleteEditModel::State model_state; + const AutocompleteEditViewWin::State view_state; +}; + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +// Helper classes + +AutocompleteEditViewWin::ScopedFreeze::ScopedFreeze( + AutocompleteEditViewWin* 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); + } +} + +AutocompleteEditViewWin::ScopedFreeze::~ScopedFreeze() { + // Unfreeze the screen. + // NOTE: If this destructor is reached while the edit is being destroyed (for + // example, because we double-clicked the edit of a popup and caused it to + // transform to an unconstrained window), it will no longer have an HWND, and + // text_object_model_ may point to a destroyed object, so do nothing here. + if (edit_->IsWindow() && 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(); + } + } +} + +AutocompleteEditViewWin::ScopedSuspendUndo::ScopedSuspendUndo( + ITextDocument* text_object_model) + : text_object_model_(text_object_model) { + // Suspend Undo processing. + if (text_object_model_) + text_object_model_->Undo(tomSuspend, NULL); +} + +AutocompleteEditViewWin::ScopedSuspendUndo::~ScopedSuspendUndo() { + // Resume Undo processing. + if (text_object_model_) + text_object_model_->Undo(tomResume, NULL); +} + +/////////////////////////////////////////////////////////////////////////////// +// AutocompleteEditViewWin + +// TODO (jcampan): these colors should be derived from the system colors to +// ensure they show properly. Bug #948807. +// Colors used to emphasize the scheme in the URL. + +namespace { + +const COLORREF kSecureSchemeColor = RGB(0, 150, 20); +const COLORREF kInsecureSchemeColor = RGB(200, 0, 0); + +// Colors used to strike-out the scheme when it is insecure. +const SkColor kSchemeStrikeoutColor = SkColorSetRGB(210, 0, 0); +const SkColor kSchemeSelectedStrikeoutColor = + SkColorSetRGB(255, 255, 255); + +// These are used to hook the CRichEditCtrl's calls to BeginPaint() and +// EndPaint() and provide a memory DC instead. See OnPaint(). +HWND edit_hwnd = NULL; +PAINTSTRUCT paint_struct; + +HDC BeginPaintIntercept(HWND hWnd, LPPAINTSTRUCT lpPaint) { + if (!edit_hwnd || (hWnd != edit_hwnd)) + return ::BeginPaint(hWnd, lpPaint); + + *lpPaint = paint_struct; + return paint_struct.hdc; +} + +BOOL EndPaintIntercept(HWND hWnd, const PAINTSTRUCT* lpPaint) { + return (edit_hwnd && (hWnd == edit_hwnd)) ? + true : ::EndPaint(hWnd, lpPaint); +} + +// Returns a lazily initialized property bag accessor for saving our state in a +// TabContents. +PropertyAccessor<AutocompleteEditState>* GetStateAccessor() { + static PropertyAccessor<AutocompleteEditState> state; + return &state; +} + +class PaintPatcher { + public: + PaintPatcher() : refcount_(0) { } + ~PaintPatcher() { DCHECK(refcount_ == 0); } + + void RefPatch() { + if (refcount_ == 0) { + DCHECK(!begin_paint_.is_patched()); + DCHECK(!end_paint_.is_patched()); + begin_paint_.Patch(L"riched20.dll", "user32.dll", "BeginPaint", + &BeginPaintIntercept); + end_paint_.Patch(L"riched20.dll", "user32.dll", "EndPaint", + &EndPaintIntercept); + } + ++refcount_; + } + + void DerefPatch() { + DCHECK(begin_paint_.is_patched()); + DCHECK(end_paint_.is_patched()); + --refcount_; + if (refcount_ == 0) { + begin_paint_.Unpatch(); + end_paint_.Unpatch(); + } + } + + private: + size_t refcount_; + iat_patch::IATPatchFunction begin_paint_; + iat_patch::IATPatchFunction end_paint_; + + DISALLOW_COPY_AND_ASSIGN(PaintPatcher); +}; + +base::LazyInstance<PaintPatcher> g_paint_patcher(base::LINKER_INITIALIZED); + +} // namespace + +AutocompleteEditViewWin::AutocompleteEditViewWin( + const ChromeFont& font, + AutocompleteEditController* controller, + ToolbarModel* toolbar_model, + views::View* parent_view, + HWND hwnd, + Profile* profile, + CommandUpdater* command_updater, + bool popup_window_mode) + : model_(new AutocompleteEditModel(this, controller, profile)), + popup_view_(new AutocompletePopupViewWin(font, this, model_.get(), + profile)), + controller_(controller), + parent_view_(parent_view), + toolbar_model_(toolbar_model), + command_updater_(command_updater), + popup_window_mode_(popup_window_mode), + tracking_click_(false), + tracking_double_click_(false), + double_click_time_(0), + can_discard_mousemove_(false), + font_(font), + possible_drag_(false), + in_drag_(false), + initiated_drag_(false), + drop_highlight_position_(-1), + background_color_(0), + scheme_security_level_(ToolbarModel::NORMAL) { + model_->set_popup_model(popup_view_->model()); + + saved_selection_for_focus_change_.cpMin = -1; + + g_paint_patcher.Pointer()->RefPatch(); + + Create(hwnd, 0, 0, 0, l10n_util::GetExtendedStyles()); + SetReadOnly(popup_window_mode_); + SetFont(font_.hfont()); + + // NOTE: Do not use SetWordBreakProcEx() here, that is no longer supported as + // of Rich Edit 2.0 onward. + SendMessage(m_hWnd, EM_SETWORDBREAKPROC, 0, + reinterpret_cast<LPARAM>(&WordBreakProc)); + + // Get the metrics for the font. + HDC dc = ::GetDC(NULL); + SelectObject(dc, font_.hfont()); + TEXTMETRIC tm = {0}; + GetTextMetrics(dc, &tm); + font_ascent_ = tm.tmAscent; + const float kXHeightRatio = 0.7f; // The ratio of a font's x-height to its + // cap height. Sadly, Windows doesn't + // provide a true value for a font's + // x-height in its text metrics, so we + // approximate. + font_x_height_ = static_cast<int>((static_cast<float>(font_ascent_ - + tm.tmInternalLeading) * kXHeightRatio) + 0.5); + // The distance from the top of the field to the desired baseline of the + // rendered text. + const int kTextBaseline = popup_window_mode_ ? 15 : 18; + font_y_adjustment_ = kTextBaseline - font_ascent_; + font_descent_ = tm.tmDescent; + + // Get the number of twips per pixel, which we need below to offset our text + // by the desired number of pixels. + const long kTwipsPerPixel = kTwipsPerInch / GetDeviceCaps(dc, LOGPIXELSY); + ::ReleaseDC(NULL, dc); + + // Set the default character style -- adjust to our desired baseline and make + // text grey. + CHARFORMAT cf = {0}; + cf.dwMask = CFM_OFFSET | CFM_COLOR; + cf.yOffset = -font_y_adjustment_ * kTwipsPerPixel; + cf.crTextColor = GetSysColor(COLOR_GRAYTEXT); + SetDefaultCharFormat(cf); + + // Set up context menu. + context_menu_.reset(new Menu(this, Menu::TOPLEFT, m_hWnd)); + if (popup_window_mode_) { + context_menu_->AppendMenuItemWithLabel(IDS_COPY, + l10n_util::GetString(IDS_COPY)); + } else { + context_menu_->AppendMenuItemWithLabel(IDS_UNDO, + l10n_util::GetString(IDS_UNDO)); + context_menu_->AppendSeparator(); + context_menu_->AppendMenuItemWithLabel(IDS_CUT, + l10n_util::GetString(IDS_CUT)); + context_menu_->AppendMenuItemWithLabel(IDS_COPY, + l10n_util::GetString(IDS_COPY)); + context_menu_->AppendMenuItemWithLabel(IDS_PASTE, + l10n_util::GetString(IDS_PASTE)); + // GetContextualLabel() will override this next label with the + // IDS_PASTE_AND_SEARCH label as needed. + context_menu_->AppendMenuItemWithLabel( + IDS_PASTE_AND_GO, l10n_util::GetString(IDS_PASTE_AND_GO)); + context_menu_->AppendSeparator(); + context_menu_->AppendMenuItemWithLabel( + IDS_SELECT_ALL, l10n_util::GetString(IDS_SELECT_ALL)); + context_menu_->AppendSeparator(); + context_menu_->AppendMenuItemWithLabel( + IDS_EDIT_SEARCH_ENGINES, l10n_util::GetString(IDS_EDIT_SEARCH_ENGINES)); + } + + // By default RichEdit has a drop target. Revoke it so that we can install our + // own. Revoke takes care of deleting the existing one. + RevokeDragDrop(m_hWnd); + + // Register our drop target. RichEdit appears to invoke RevokeDropTarget when + // done so that we don't have to explicitly. + if (!popup_window_mode_) { + scoped_refptr<EditDropTarget> drop_target = new EditDropTarget(this); + RegisterDragDrop(m_hWnd, drop_target.get()); + } +} + +AutocompleteEditViewWin::~AutocompleteEditViewWin() { + NotificationService::current()->Notify( + NotificationType::AUTOCOMPLETE_EDIT_DESTROYED, + Source<AutocompleteEditViewWin>(this), + NotificationService::NoDetails()); + + // We balance our reference count and unpatch when the last instance has + // been destroyed. This prevents us from relying on the AtExit or static + // destructor sequence to do our unpatching, which is generally fragile. + g_paint_patcher.Pointer()->DerefPatch(); +} + +void AutocompleteEditViewWin::SaveStateToTab(TabContents* tab) { + DCHECK(tab); + + const AutocompleteEditModel::State model_state( + model_->GetStateForTabSwitch()); + + CHARRANGE selection; + GetSelection(selection); + GetStateAccessor()->SetProperty(tab->property_bag(), + AutocompleteEditState( + model_state, + State(selection, saved_selection_for_focus_change_))); +} + +void AutocompleteEditViewWin::Update( + const TabContents* tab_for_state_restoring) { + const bool visibly_changed_permanent_text = + model_->UpdatePermanentText(toolbar_model_->GetText()); + + const ToolbarModel::SecurityLevel security_level = + toolbar_model_->GetSchemeSecurityLevel(); + const COLORREF background_color = + LocationBarView::kBackgroundColorByLevel[security_level]; + const bool changed_security_level = + (security_level != scheme_security_level_); + + // Bail early when no visible state will actually change (prevents an + // unnecessary ScopedFreeze, and thus UpdateWindow()). + if ((background_color == background_color_) && !changed_security_level && + !visibly_changed_permanent_text && !tab_for_state_restoring) + return; + + // Update our local state as desired. We set scheme_security_level_ here so + // it will already be correct before we get to any RevertAll()s below and use + // it. + ScopedFreeze freeze(this, GetTextObjectModel()); + if (background_color_ != background_color) { + background_color_ = background_color; + SetBackgroundColor(background_color_); + } + scheme_security_level_ = security_level; + + // When we're switching to a new tab, restore its state, if any. + if (tab_for_state_restoring) { + // Make sure we reset our own state first. The new tab may not have any + // saved state, or it may not have had input in progress, in which case we + // won't overwrite all our local state. + RevertAll(); + + const AutocompleteEditState* state = GetStateAccessor()->GetProperty( + tab_for_state_restoring->property_bag()); + if (state) { + model_->RestoreState(state->model_state); + + // Restore user's selection. We do this after restoring the user_text + // above so we're selecting in the correct string. + SetSelectionRange(state->view_state.selection); + saved_selection_for_focus_change_ = + state->view_state.saved_selection_for_focus_change; + } + } else if (visibly_changed_permanent_text) { + // Not switching tabs, just updating the permanent text. (In the case where + // we _were_ switching tabs, the RevertAll() above already drew the new + // permanent text.) + + // Tweak: if the edit was previously nonempty and had all the text selected, + // select all the new text. This makes one particular case better: the + // user clicks in the box to change it right before the permanent URL is + // changed. Since the new URL is still fully selected, the user's typing + // will replace the edit contents as they'd intended. + // + // NOTE: The selection can be longer than the text length if the edit is in + // in rich text mode and the user has selected the "phantom newline" at the + // end, so use ">=" instead of "==" to see if all the text is selected. In + // theory we prevent this case from ever occurring, but this is still safe. + CHARRANGE sel; + GetSelection(sel); + const bool was_reversed = (sel.cpMin > sel.cpMax); + const bool was_sel_all = (sel.cpMin != sel.cpMax) && + IsSelectAllForRange(sel); + + RevertAll(); + + if (was_sel_all) + SelectAll(was_reversed); + } else if (changed_security_level) { + // Only the security style changed, nothing else. Redraw our text using it. + EmphasizeURLComponents(); + } +} + +void AutocompleteEditViewWin::OpenURL(const GURL& url, + WindowOpenDisposition disposition, + PageTransition::Type transition, + const GURL& alternate_nav_url, + size_t selected_line, + const std::wstring& keyword) { + if (!url.is_valid()) + return; + + model_->SendOpenNotification(selected_line, keyword); + + ScopedFreeze freeze(this, GetTextObjectModel()); + if (disposition != NEW_BACKGROUND_TAB) + RevertAll(); // Revert the box to its unedited state + controller_->OnAutocompleteAccept(url, disposition, transition, + alternate_nav_url); +} + +std::wstring AutocompleteEditViewWin::GetText() const { + const int len = GetTextLength() + 1; + std::wstring str; + GetWindowText(WriteInto(&str, len), len); + return str; +} + +void AutocompleteEditViewWin::SetUserText(const std::wstring& text, + const std::wstring& display_text, + bool update_popup) { + ScopedFreeze freeze(this, GetTextObjectModel()); + model_->SetUserText(text); + saved_selection_for_focus_change_.cpMin = -1; + SetWindowTextAndCaretPos(display_text, display_text.length()); + if (update_popup) + UpdatePopup(); + TextChanged(); +} + +void AutocompleteEditViewWin::SetWindowTextAndCaretPos(const std::wstring& text, + size_t caret_pos) { + HIMC imm_context = ImmGetContext(m_hWnd); + if (imm_context) { + // In Windows Vista, SetWindowText() automatically completes any ongoing + // IME composition, and updates the text of the underlying edit control. + // In Windows XP, however, SetWindowText() gets applied to the IME + // composition string if it exists, and doesn't update the underlying edit + // control. To avoid this, we force the IME to complete any outstanding + // compositions here. This is harmless in Vista and in cases where the IME + // isn't composing. + ImmNotifyIME(imm_context, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); + ImmReleaseContext(m_hWnd, imm_context); + } + + SetWindowText(text.c_str()); + PlaceCaretAt(caret_pos); +} + +bool AutocompleteEditViewWin::IsSelectAll() { + CHARRANGE selection; + GetSel(selection); + return IsSelectAllForRange(selection); +} + +void AutocompleteEditViewWin::SelectAll(bool reversed) { + if (reversed) + SetSelection(GetTextLength(), 0); + else + SetSelection(0, GetTextLength()); +} + +void AutocompleteEditViewWin::RevertAll() { + ScopedFreeze freeze(this, GetTextObjectModel()); + ClosePopup(); + model_->Revert(); + saved_selection_for_focus_change_.cpMin = -1; + TextChanged(); +} + +void AutocompleteEditViewWin::UpdatePopup() { + ScopedFreeze freeze(this, GetTextObjectModel()); + model_->SetInputInProgress(true); + + if (!model_->has_focus()) { + // When we're in the midst of losing focus, don't rerun autocomplete. This + // can happen when losing focus causes the IME to cancel/finalize a + // composition. We still want to note that user input is in progress, we + // just don't want to do anything else. + // + // Note that in this case the ScopedFreeze above was unnecessary; however, + // we're inside the callstack of OnKillFocus(), which has already frozen the + // edit, so this will never result in an unnecessary UpdateWindow() call. + return; + } + + // Figure out whether the user is trying to compose something in an IME. + bool ime_composing = false; + HIMC context = ImmGetContext(m_hWnd); + if (context) { + ime_composing = !!ImmGetCompositionString(context, GCS_COMPSTR, NULL, 0); + ImmReleaseContext(m_hWnd, context); + } + + // Don't inline autocomplete when: + // * The user is deleting text + // * The caret/selection isn't at the end of the text + // * The user has just pasted in something that replaced all the text + // * The user is trying to compose something in an IME + CHARRANGE sel; + GetSel(sel); + model_->StartAutocomplete((sel.cpMax < GetTextLength()) || ime_composing); +} + +void AutocompleteEditViewWin::ClosePopup() { + popup_view_->model()->StopAutocomplete(); +} + +IAccessible* AutocompleteEditViewWin::GetIAccessible() { + if (!autocomplete_accessibility_) { + CComObject<AutocompleteAccessibility>* accessibility = NULL; + if (!SUCCEEDED(CComObject<AutocompleteAccessibility>::CreateInstance( + &accessibility)) || !accessibility) + return NULL; + + // Wrap the created object in a smart pointer so it won't leak. + CComPtr<IAccessible> accessibility_comptr(accessibility); + if (!SUCCEEDED(accessibility->Initialize(this))) + return NULL; + + // Copy to the class smart pointer, and notify that an instance of + // IAccessible was allocated for m_hWnd. + autocomplete_accessibility_ = accessibility_comptr; + NotifyWinEvent(EVENT_OBJECT_CREATE, m_hWnd, OBJID_CLIENT, CHILDID_SELF); + } + // Detach to leave ref counting to the caller. + return autocomplete_accessibility_.Detach(); +} + +void AutocompleteEditViewWin::SetDropHighlightPosition(int position) { + if (drop_highlight_position_ != position) { + RepaintDropHighlight(drop_highlight_position_); + drop_highlight_position_ = position; + RepaintDropHighlight(drop_highlight_position_); + } +} + +void AutocompleteEditViewWin::MoveSelectedText(int new_position) { + const std::wstring selected_text(GetSelectedText()); + CHARRANGE sel; + GetSel(sel); + DCHECK((sel.cpMax != sel.cpMin) && (new_position >= 0) && + (new_position <= GetTextLength())); + + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + + // Nuke the selected text. + ReplaceSel(L"", TRUE); + + // And insert it into the new location. + if (new_position >= sel.cpMin) + new_position -= (sel.cpMax - sel.cpMin); + PlaceCaretAt(new_position); + ReplaceSel(selected_text.c_str(), TRUE); + + OnAfterPossibleChange(); +} + +void AutocompleteEditViewWin::InsertText(int position, + const std::wstring& text) { + DCHECK((position >= 0) && (position <= GetTextLength())); + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + SetSelection(position, position); + ReplaceSel(text.c_str()); + OnAfterPossibleChange(); +} + +void AutocompleteEditViewWin::OnTemporaryTextMaybeChanged( + const std::wstring& display_text, + bool save_original_selection) { + if (save_original_selection) + GetSelection(original_selection_); + + // Set new text and cursor position. Sometimes this does extra work (e.g. + // when the new text and the old text are identical), but it's only called + // when the user manually changes the selected line in the popup, so that's + // not really a problem. Also, even when the text hasn't changed we'd want to + // update the caret, because if the user had the cursor in the middle of the + // text and then arrowed to another entry with the same text, we'd still want + // to move the caret. + ScopedFreeze freeze(this, GetTextObjectModel()); + SetWindowTextAndCaretPos(display_text, display_text.length()); + TextChanged(); +} + +bool AutocompleteEditViewWin::OnInlineAutocompleteTextMaybeChanged( + const std::wstring& display_text, + size_t user_text_length) { + // Update the text and selection. Because this can be called repeatedly while + // typing, we've careful not to freeze the edit unless we really need to. + // Also, unlike in the temporary text case above, here we don't want to update + // the caret/selection unless we have to, since this might make the user's + // caret position change without warning during typing. + if (display_text == GetText()) + return false; + + ScopedFreeze freeze(this, GetTextObjectModel()); + // NOTE: We don't need the IME composition hack in SetWindowTextAndCaretPos() + // here, because UpdatePopup() disables inline autocomplete when a + // composition is in progress, thus preventing us from reaching this code. + SetWindowText(display_text.c_str()); + // Set a reversed selection to keep the caret in the same position, which + // avoids scrolling the user's text. + SetSelection(static_cast<LONG>(display_text.length()), + static_cast<LONG>(user_text_length)); + TextChanged(); + return true; +} + +void AutocompleteEditViewWin::OnRevertTemporaryText() { + SetSelectionRange(original_selection_); + TextChanged(); +} + +void AutocompleteEditViewWin::OnBeforePossibleChange() { + // Record our state. + text_before_change_ = GetText(); + GetSelection(sel_before_change_); +} + +bool AutocompleteEditViewWin::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; + GetSelection(new_sel); + const int length = GetTextLength(); + if ((new_sel.cpMin > length) || (new_sel.cpMax > length)) { + if (new_sel.cpMin > length) + new_sel.cpMin = length; + if (new_sel.cpMax > length) + new_sel.cpMax = length; + SetSelectionRange(new_sel); + } + const bool selection_differs = (new_sel.cpMin != sel_before_change_.cpMin) || + (new_sel.cpMax != sel_before_change_.cpMax); + const bool at_end_of_edit = + (new_sel.cpMin == length) && (new_sel.cpMax == length); + + // See if the text or selection have changed since OnBeforePossibleChange(). + const std::wstring new_text(GetText()); + const bool text_differs = (new_text != text_before_change_); + + // When the user has deleted text, we don't allow inline autocomplete. Make + // sure to not flag cases like selecting part of the text and then pasting + // (or typing) the prefix of that selection. (We detect these by making + // sure the caret, which should be after any insertion, hasn't moved + // forward of the old selection start.) + const bool just_deleted_text = + (text_before_change_.length() > new_text.length()) && + (new_sel.cpMin <= std::min(sel_before_change_.cpMin, + sel_before_change_.cpMax)); + + + const bool something_changed = model_->OnAfterPossibleChange(new_text, + selection_differs, text_differs, just_deleted_text, at_end_of_edit); + + if (something_changed && text_differs) + TextChanged(); + + return something_changed; +} + +void AutocompleteEditViewWin::PasteAndGo(const std::wstring& text) { + if (CanPasteAndGo(text)) + model_->PasteAndGo(); +} + +bool AutocompleteEditViewWin::OverrideAccelerator( + const views::Accelerator& accelerator) { + // Only override <esc>. + if ((accelerator.GetKeyCode() != VK_ESCAPE) || accelerator.IsAltDown()) + return false; + + ScopedFreeze freeze(this, GetTextObjectModel()); + return model_->OnEscapeKeyPressed(); +} + +void AutocompleteEditViewWin::HandleExternalMsg(UINT msg, + UINT flags, + const CPoint& screen_point) { + if (msg == WM_CAPTURECHANGED) { + SendMessage(msg, 0, NULL); + return; + } + + CPoint client_point(screen_point); + ::MapWindowPoints(NULL, m_hWnd, &client_point, 1); + SendMessage(msg, flags, MAKELPARAM(client_point.x, client_point.y)); +} + +bool AutocompleteEditViewWin::IsCommandEnabled(int id) const { + switch (id) { + case IDS_UNDO: return !!CanUndo(); + case IDS_CUT: return !!CanCut(); + case IDS_COPY: return !!CanCopy(); + case IDS_PASTE: return !!CanPaste(); + case IDS_PASTE_AND_GO: return CanPasteAndGo(GetClipboardText()); + case IDS_SELECT_ALL: return !!CanSelectAll(); + case IDS_EDIT_SEARCH_ENGINES: + return command_updater_->IsCommandEnabled(IDC_EDIT_SEARCH_ENGINES); + default: NOTREACHED(); return false; + } +} + +bool AutocompleteEditViewWin::GetContextualLabel(int id, + std::wstring* out) const { + if ((id != IDS_PASTE_AND_GO) || + // No need to change the default IDS_PASTE_AND_GO label unless this is a + // search. + !model_->is_paste_and_search()) + return false; + + out->assign(l10n_util::GetString(IDS_PASTE_AND_SEARCH)); + return true; +} + +void AutocompleteEditViewWin::ExecuteCommand(int id) { + ScopedFreeze freeze(this, GetTextObjectModel()); + if (id == IDS_PASTE_AND_GO) { + // This case is separate from the switch() below since we don't want to wrap + // it in OnBefore/AfterPossibleChange() calls. + model_->PasteAndGo(); + return; + } + + OnBeforePossibleChange(); + switch (id) { + case IDS_UNDO: + Undo(); + break; + + case IDS_CUT: + Cut(); + break; + + case IDS_COPY: + Copy(); + break; + + case IDS_PASTE: + Paste(); + break; + + case IDS_SELECT_ALL: + SelectAll(false); + break; + + case IDS_EDIT_SEARCH_ENGINES: + command_updater_->ExecuteCommand(IDC_EDIT_SEARCH_ENGINES); + break; + + default: + NOTREACHED(); + break; + } + OnAfterPossibleChange(); +} + +// static +int CALLBACK AutocompleteEditViewWin::WordBreakProc(LPTSTR edit_text, + int current_pos, + int num_bytes, + int action) { + // TODO(pkasting): http://b/1111308 We should let other people, like ICU and + // GURL, do the work for us here instead of writing all this ourselves. + + // Sadly, even though the MSDN docs claim that the third parameter here is a + // number of characters, they lie. It's a number of bytes. + const int length = num_bytes / sizeof(wchar_t); + + // With no clear guidance from the MSDN docs on how to handle "not found" in + // the "find the nearest xxx..." cases below, I cap the return values at + // [0, length]. Since one of these (0) is also a valid position, the return + // values are thus ambiguous :( + switch (action) { + // Find nearest character before current position that begins a word. + case WB_LEFT: + case WB_MOVEWORDLEFT: { + if (current_pos < 2) { + // Either current_pos == 0, so we have a "not found" case and return 0, + // or current_pos == 1, and the only character before this position is + // at 0. + return 0; + } + + // Look for a delimiter before the previous character; the previous word + // starts immediately after. (If we looked for a delimiter before the + // current character, we could stop on the immediate prior character, + // which would mean we'd return current_pos -- which isn't "before the + // current position".) + const int prev_delim = + WordBreakProc(edit_text, current_pos - 1, num_bytes, WB_LEFTBREAK); + + if ((prev_delim == 0) && + !WordBreakProc(edit_text, 0, num_bytes, WB_ISDELIMITER)) { + // Got back 0, but position 0 isn't a delimiter. This was a "not + // found" 0, so return one of our own. + return 0; + } + + return prev_delim + 1; + } + + // Find nearest character after current position that begins a word. + case WB_RIGHT: + case WB_MOVEWORDRIGHT: { + if (WordBreakProc(edit_text, current_pos, num_bytes, WB_ISDELIMITER)) { + // The current character is a delimiter, so the next character starts + // a new word. Done. + return current_pos + 1; + } + + // Look for a delimiter after the current character; the next word starts + // immediately after. + const int next_delim = + WordBreakProc(edit_text, current_pos, num_bytes, WB_RIGHTBREAK); + if (next_delim == length) { + // Didn't find a delimiter. Return length to signal "not found". + return length; + } + + return next_delim + 1; + } + + // Determine if the current character delimits words. + case WB_ISDELIMITER: + return !!(WordBreakProc(edit_text, current_pos, num_bytes, WB_CLASSIFY) & + WBF_BREAKLINE); + + // Return the classification of the current character. + case WB_CLASSIFY: + if (IsWhitespace(edit_text[current_pos])) { + // Whitespace normally breaks words, but the MSDN docs say that we must + // not break on the CRs in a "CR, LF" or a "CR, CR, LF" sequence. Just + // check for an arbitrarily long sequence of CRs followed by LF and + // report "not a delimiter" for the current CR in that case. + while ((current_pos < (length - 1)) && + (edit_text[current_pos] == 0x13)) { + if (edit_text[++current_pos] == 0x10) + return WBF_ISWHITE; + } + return WBF_BREAKLINE | WBF_ISWHITE; + } + + // Punctuation normally breaks words, but the first two characters in + // "://" (end of scheme) should not be breaks, so that "http://" will be + // treated as one word. + if (ispunct(edit_text[current_pos], std::locale()) && + !SchemeEnd(edit_text, current_pos, length) && + !SchemeEnd(edit_text, current_pos - 1, length)) + return WBF_BREAKLINE; + + // Normal character, no flags. + return 0; + + // Finds nearest delimiter before current position. + case WB_LEFTBREAK: + for (int i = current_pos - 1; i >= 0; --i) { + if (WordBreakProc(edit_text, i, num_bytes, WB_ISDELIMITER)) + return i; + } + return 0; + + // Finds nearest delimiter after current position. + case WB_RIGHTBREAK: + for (int i = current_pos + 1; i < length; ++i) { + if (WordBreakProc(edit_text, i, num_bytes, WB_ISDELIMITER)) + return i; + } + return length; + } + + NOTREACHED(); + return 0; +} + +// static +bool AutocompleteEditViewWin::SchemeEnd(LPTSTR edit_text, + int current_pos, + int length) { + return (current_pos >= 0) && + ((length - current_pos) > 2) && + (edit_text[current_pos] == ':') && + (edit_text[current_pos + 1] == '/') && + (edit_text[current_pos + 2] == '/'); +} + +void AutocompleteEditViewWin::OnChar(TCHAR ch, UINT repeat_count, UINT flags) { + // Don't let alt-enter beep. Not sure this is necessary, as the standard + // alt-enter will hit DiscardWMSysChar() and get thrown away, and + // ctrl-alt-enter doesn't seem to reach here for some reason? At least not on + // my system... still, this is harmless and maybe necessary in other locales. + if (ch == VK_RETURN && (flags & KF_ALTDOWN)) + return; + + // Escape is processed in OnKeyDown. Don't let any WM_CHAR messages propagate + // as we don't want the RichEdit to do anything funky. + if (ch == VK_ESCAPE && !(flags & KF_ALTDOWN)) + return; + + if (ch == VK_TAB) { + // Don't add tabs to the input. + return; + } + + HandleKeystroke(GetCurrentMessage()->message, ch, repeat_count, flags); +} + +void AutocompleteEditViewWin::OnContextMenu(HWND window, const CPoint& point) { + if (point.x == -1 || point.y == -1) { + POINT p; + GetCaretPos(&p); + MapWindowPoints(HWND_DESKTOP, &p, 1); + context_menu_->RunMenuAt(p.x, p.y); + } else { + context_menu_->RunMenuAt(point.x, point.y); + } +} + +void AutocompleteEditViewWin::OnCopy() { + const std::wstring text(GetSelectedText()); + if (text.empty()) + return; + + ScopedClipboardWriter scw(g_browser_process->clipboard_service()); + scw.WriteText(text); + + // Check if the user is copying the whole address bar. If they are, we + // assume they are trying to copy a URL and write this to the clipboard as a + // hyperlink. + if (static_cast<int>(text.length()) < GetTextLength()) + return; + + // The entire control is selected. Let's see what the user typed. We can't + // use model_->CurrentTextIsURL() or model_->GetDataForURLExport() because + // right now the user is probably holding down control to cause the copy, + // which will screw up our calculation of the desired_tld. + GURL url; + if (model_->GetURLForText(text, &url)) + scw.WriteHyperlink(text, url.spec()); +} + +void AutocompleteEditViewWin::OnCut() { + OnCopy(); + + // This replace selection will have no effect (even on the undo stack) if the + // current selection is empty. + ReplaceSel(L"", true); +} + +LRESULT AutocompleteEditViewWin::OnGetObject(UINT uMsg, + WPARAM wparam, + LPARAM lparam) { + // Accessibility readers will send an OBJID_CLIENT message. + if (lparam == OBJID_CLIENT) { + // Re-attach for internal re-usage of accessibility pointer. + autocomplete_accessibility_.Attach(GetIAccessible()); + + if (autocomplete_accessibility_) { + return LresultFromObject(IID_IAccessible, wparam, + autocomplete_accessibility_.p); + } + } + return 0; +} + +LRESULT AutocompleteEditViewWin::OnImeComposition(UINT message, + WPARAM wparam, + LPARAM lparam) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + LRESULT result = DefWindowProc(message, wparam, lparam); + if (!OnAfterPossibleChange() && (lparam & GCS_RESULTSTR)) { + // The result string changed, but the text in the popup didn't actually + // change. This means the user finalized the composition. Rerun + // autocomplete so that we can now trigger inline autocomplete if + // applicable. + // + // Note that if we're in the midst of losing focus, UpdatePopup() won't + // actually rerun autocomplete, but will just set local state correctly. + UpdatePopup(); + } + return result; +} + +void AutocompleteEditViewWin::OnKeyDown(TCHAR key, + UINT repeat_count, + UINT flags) { + if (OnKeyDownAllModes(key, repeat_count, flags) || popup_window_mode_ || + OnKeyDownOnlyWritable(key, repeat_count, flags)) + 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 AutocompleteEditViewWin::OnKeyUp(TCHAR key, + UINT repeat_count, + UINT flags) { + if (key == VK_CONTROL) + model_->OnControlKeyChanged(false); + + SetMsgHandled(false); +} + +void AutocompleteEditViewWin::OnKillFocus(HWND focus_wnd) { + if (m_hWnd == focus_wnd) { + // Focus isn't actually leaving. + SetMsgHandled(false); + return; + } + + // Close the popup. + ClosePopup(); + + // Save the user's existing selection to restore it later. + GetSelection(saved_selection_for_focus_change_); + + // Tell the model to reset itself. + model_->OnKillFocus(); + + // Let the CRichEditCtrl do its default handling. This will complete any + // in-progress IME composition. We must do this after setting has_focus_ to + // false so that UpdatePopup() will know not to rerun autocomplete. + ScopedFreeze freeze(this, GetTextObjectModel()); + DefWindowProc(WM_KILLFOCUS, reinterpret_cast<WPARAM>(focus_wnd), 0); + + // Hide the "Type to search" hint if necessary. We do this after calling + // DefWindowProc() because processing the resulting IME messages may notify + // the controller that input is in progress, which could cause the visible + // hints to change. (I don't know if there's a real scenario where they + // actually do change, but this is safest.) + if (model_->show_search_hint() || + (model_->is_keyword_hint() && !model_->keyword().empty())) + controller_->OnChanged(); + + // Cancel any user selection and scroll the text back to the beginning of the + // URL. We have to do this after calling DefWindowProc() because otherwise + // an in-progress IME composition will be completed at the new caret position, + // resulting in the string jumping unexpectedly to the front of the edit. + PlaceCaretAt(0); +} + +void AutocompleteEditViewWin::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; + possible_drag_ = false; + + // Modifying the selection counts as accepting any inline autocompletion, so + // track "changes" made by clicking the mouse button. + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + DefWindowProc(WM_LBUTTONDBLCLK, keys, + MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); + OnAfterPossibleChange(); + + gaining_focus_.reset(); // See NOTE in OnMouseActivate(). +} + +void AutocompleteEditViewWin::OnLButtonDown(UINT keys, const CPoint& point) { + if (gaining_focus_.get()) { + // This click is giving us focus, so we need to track how much the mouse + // moves to see if it's a drag or just a click. Clicks should select all + // the text. + tracking_click_ = true; + mouse_down_point_ = point; + + // When Chrome was already the activated app, we haven't reached + // OnSetFocus() yet. When we get there, don't restore the saved selection, + // since it will just screw up the user's interaction with the edit. + saved_selection_for_focus_change_.cpMin = -1; + + // Crazy hack: In this particular case, the CRichEditCtrl seems to have an + // internal flag that discards the next WM_LBUTTONDOWN without processing + // it, so that clicks on the edit when its owning app is not activated are + // eaten rather than processed (despite whatever the return value of + // DefWindowProc(WM_MOUSEACTIVATE, ...) may say). This behavior is + // confusing and we want the click to be treated normally. So, to reset the + // CRichEditCtrl's internal flag, we pass it an extra WM_LBUTTONDOWN here + // (as well as a matching WM_LBUTTONUP, just in case we'd be confusing some + // kind of state tracking otherwise). + DefWindowProc(WM_LBUTTONDOWN, keys, MAKELPARAM(point.x, point.y)); + DefWindowProc(WM_LBUTTONUP, keys, MAKELPARAM(point.x, point.y)); + } + + // 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; + + if (!gaining_focus_.get() && !is_triple_click) + OnPossibleDrag(point); + + + // Modifying the selection counts as accepting any inline autocompletion, so + // track "changes" made by clicking the mouse button. + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + DefWindowProc(WM_LBUTTONDOWN, keys, + MAKELPARAM(ClipXCoordToVisibleText(point.x, is_triple_click), + point.y)); + OnAfterPossibleChange(); + + gaining_focus_.reset(); +} + +void AutocompleteEditViewWin::OnLButtonUp(UINT keys, const CPoint& point) { + // default processing should happen first so we can see the result of the + // selection + ScopedFreeze freeze(this, GetTextObjectModel()); + DefWindowProc(WM_LBUTTONUP, keys, + MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); + + // When the user has clicked and released to give us focus, select all. + if (tracking_click_ && !win_util::IsDrag(mouse_down_point_, point)) { + // Select all in the reverse direction so as not to scroll the caret + // into view and shift the contents jarringly. + SelectAll(true); + possible_drag_ = false; + } + + tracking_click_ = false; + + UpdateDragDone(keys); +} + +LRESULT AutocompleteEditViewWin::OnMouseActivate(HWND window, + UINT hit_test, + UINT mouse_message) { + // First, give other handlers a chance to handle the message to see if we are + // actually going to activate and gain focus. + LRESULT result = DefWindowProc(WM_MOUSEACTIVATE, + reinterpret_cast<WPARAM>(window), + MAKELPARAM(hit_test, mouse_message)); + // Check if we're getting focus from a left click. We have to do this here + // rather than in OnLButtonDown() since in many scenarios OnSetFocus() will be + // reached before OnLButtonDown(), preventing us from detecting this properly + // there. Also in those cases, we need to already know in OnSetFocus() that + // we should not restore the saved selection. + if (!model_->has_focus() && (mouse_message == WM_LBUTTONDOWN) && + (result == MA_ACTIVATE)) { + DCHECK(!gaining_focus_.get()); + gaining_focus_.reset(new ScopedFreeze(this, GetTextObjectModel())); + // NOTE: Despite |mouse_message| being WM_LBUTTONDOWN here, we're not + // guaranteed to call OnLButtonDown() later! Specifically, if this is the + // second click of a double click, we'll reach here but later call + // OnLButtonDblClk(). Make sure |gaining_focus_| gets reset both places, or + // we'll have visual glitchiness and then DCHECK failures. + + // Don't restore saved selection, it will just screw up our interaction + // with this edit. + saved_selection_for_focus_change_.cpMin = -1; + } + return result; +} + +void AutocompleteEditViewWin::OnMouseMove(UINT keys, const CPoint& point) { + if (possible_drag_) { + StartDragIfNecessary(point); + // Don't fall through to default mouse handling, otherwise a second + // drag session may start. + return; + } + + if (tracking_click_ && !win_util::IsDrag(mouse_down_point_, point)) + return; + + tracking_click_ = false; + + // Return quickly if this can't change the selection/cursor, so we don't + // create a ScopedFreeze (and thus do an UpdateWindow()) on every + // WM_MOUSEMOVE. + if (!(keys & MK_LBUTTON)) { + DefWindowProc(WM_MOUSEMOVE, keys, MAKELPARAM(point.x, point.y)); + return; + } + + // 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(); + } +} + +void AutocompleteEditViewWin::OnPaint(HDC bogus_hdc) { + // We need to paint over the top of the edit. If we simply let the edit do + // its default painting, then do ours into the window DC, the screen is + // updated in between and we can get flicker. To avoid this, we force the + // edit to paint into a memory DC, which we also paint onto, then blit the + // whole thing to the screen. + + // Don't paint if not necessary. + CRect paint_clip_rect; + if (!GetUpdateRect(&paint_clip_rect, true)) + return; + + // Begin painting, and create a memory DC for the edit to paint into. + CPaintDC paint_dc(m_hWnd); + CDC memory_dc(CreateCompatibleDC(paint_dc)); + CRect rect; + GetClientRect(&rect); + // NOTE: This next call uses |paint_dc| instead of |memory_dc| because + // |memory_dc| contains a 1x1 monochrome bitmap by default, which will cause + // |memory_bitmap| to be monochrome, which isn't what we want. + CBitmap memory_bitmap(CreateCompatibleBitmap(paint_dc, rect.Width(), + rect.Height())); + HBITMAP old_bitmap = memory_dc.SelectBitmap(memory_bitmap); + + // Tell our intercept functions to supply our memory DC to the edit when it + // tries to call BeginPaint(). + // + // The sane way to do this would be to use WM_PRINTCLIENT to ask the edit to + // paint into our desired DC. Unfortunately, the Rich Edit 3.0 that ships + // with Windows 2000/XP/Vista doesn't handle WM_PRINTCLIENT correctly; it + // treats it just like WM_PAINT and calls BeginPaint(), ignoring our provided + // DC. The Rich Edit 6.0 that ships with Office 2007 handles this better, but + // has other issues, and we can't redistribute that DLL anyway. So instead, + // we use this scary hack. + // + // NOTE: It's possible to get nested paint calls (!) (try setting the + // permanent URL to something longer than the edit width, then selecting the + // contents of the edit, typing a character, and hitting <esc>), so we can't + // DCHECK(!edit_hwnd_) here. Instead, just save off the old HWND, which most + // of the time will be NULL. + HWND old_edit_hwnd = edit_hwnd; + edit_hwnd = m_hWnd; + paint_struct = paint_dc.m_ps; + paint_struct.hdc = memory_dc; + DefWindowProc(WM_PAINT, reinterpret_cast<WPARAM>(bogus_hdc), 0); + + // Make the selection look better. + EraseTopOfSelection(&memory_dc, rect, paint_clip_rect); + + // Draw a slash through the scheme if this is insecure. + if (insecure_scheme_component_.is_nonempty()) + DrawSlashForInsecureScheme(memory_dc, rect, paint_clip_rect); + + // Draw the drop highlight. + if (drop_highlight_position_ != -1) + DrawDropHighlight(memory_dc, rect, paint_clip_rect); + + // Blit the memory DC to the actual paint DC and clean up. + BitBlt(paint_dc, rect.left, rect.top, rect.Width(), rect.Height(), memory_dc, + rect.left, rect.top, SRCCOPY); + memory_dc.SelectBitmap(old_bitmap); + edit_hwnd = old_edit_hwnd; +} + +void AutocompleteEditViewWin::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; + + OnPossibleDrag(point); + + SetMsgHandled(false); +} + +void AutocompleteEditViewWin::OnNonLButtonUp(UINT keys, const CPoint& point) { + UpdateDragDone(keys); + + // Let default handler have a crack at this. + SetMsgHandled(false); +} + +void AutocompleteEditViewWin::OnPaste() { + // Replace the selection if we have something to paste. + const std::wstring text(GetClipboardText()); + if (!text.empty()) { + // If this paste will be replacing all the text, record that, so we can do + // different behaviors in such a case. + if (IsSelectAll()) + model_->on_paste_replacing_all(); + ReplaceSel(text.c_str(), true); + } +} + +void AutocompleteEditViewWin::OnSetFocus(HWND focus_wnd) { + model_->OnSetFocus(GetKeyState(VK_CONTROL) < 0); + + // Notify controller if it needs to show hint UI of some kind. + ScopedFreeze freeze(this, GetTextObjectModel()); + if (model_->show_search_hint() || + (model_->is_keyword_hint() && !model_->keyword().empty())) + controller_->OnChanged(); + + // Restore saved selection if available. + if (saved_selection_for_focus_change_.cpMin != -1) { + SetSelectionRange(saved_selection_for_focus_change_); + saved_selection_for_focus_change_.cpMin = -1; + } + + SetMsgHandled(false); +} + +void AutocompleteEditViewWin::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 AutocompleteEditViewWin::HandleKeystroke(UINT message, + TCHAR key, + UINT repeat_count, + UINT flags) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + DefWindowProc(message, key, MAKELPARAM(repeat_count, flags)); + OnAfterPossibleChange(); +} + +bool AutocompleteEditViewWin::OnKeyDownOnlyWritable(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. + + int count = repeat_count; + switch (key) { + case VK_RETURN: + model_->AcceptInput((flags & KF_ALTDOWN) ? + NEW_FOREGROUND_TAB : CURRENT_TAB, false); + return true; + + case VK_UP: + count = -count; + // FALL THROUGH + case VK_DOWN: + if (flags & KF_ALTDOWN) + return false; + + model_->OnUpOrDownKeyPressed(count); + return true; + + // 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. (This is handled + // in OnKeyDownAllModes().) + // 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: + if ((flags & KF_ALTDOWN) || GetKeyState(VK_SHIFT) >= 0) + return false; + if (GetKeyState(VK_CONTROL) >= 0) { + // Cut text if possible. + CHARRANGE selection; + GetSel(selection); + if (selection.cpMin != selection.cpMax) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + Cut(); + OnAfterPossibleChange(); + } else if (popup_view_->model()->IsOpen()) { + // This is a bit overloaded, but we hijack Shift-Delete in this + // case to delete the current item from the pop-up. We prefer cutting + // to this when possible since that's the behavior more people expect + // from Shift-Delete, and it's more commonly useful. + popup_view_->model()->TryDeletingCurrentItem(); + } + } + return true; + + case 'X': + if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0)) + return false; + if (GetKeyState(VK_SHIFT) >= 0) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + Cut(); + OnAfterPossibleChange(); + } + return true; + + case VK_INSERT: + case 'V': + if ((flags & KF_ALTDOWN) || + (GetKeyState((key == 'V') ? VK_CONTROL : VK_SHIFT) >= 0)) + return false; + if (GetKeyState((key == 'V') ? VK_SHIFT : VK_CONTROL) >= 0) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + Paste(); + OnAfterPossibleChange(); + } + return true; + + case VK_BACK: { + if ((flags & KF_ALTDOWN) || model_->is_keyword_hint() || + model_->keyword().empty()) + return false; + + { + CHARRANGE selection; + GetSel(selection); + if ((selection.cpMin != selection.cpMax) || (selection.cpMin != 0)) + return false; + } + + // We're showing a keyword and the user pressed backspace at the beginning + // of the text. Delete the selected keyword. + ScopedFreeze freeze(this, GetTextObjectModel()); + model_->ClearKeyword(GetText()); + return true; + } + + case VK_TAB: { + if (model_->is_keyword_hint() && !model_->keyword().empty()) { + // Accept the keyword. + ScopedFreeze freeze(this, GetTextObjectModel()); + model_->AcceptKeyword(); + } + return true; + } + + case 0xbb: // Ctrl-'='. Triggers subscripting (even in plain text mode). + return true; + + default: + return false; + } +} + +bool AutocompleteEditViewWin::OnKeyDownAllModes(TCHAR key, + UINT repeat_count, + UINT flags) { + // See KF_ALTDOWN comment atop OnKeyDownOnlyWriteable(). + + switch (key) { + case VK_CONTROL: + model_->OnControlKeyChanged(true); + return false; + + case 'C': + // See more detailed comments in OnKeyDownOnlyWriteable(). + if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0)) + return false; + if (GetKeyState(VK_SHIFT) >= 0) + Copy(); + return true; + + default: + return false; + } +} + +void AutocompleteEditViewWin::GetSelection(CHARRANGE& sel) const { + GetSel(sel); + + // See if we need to reverse the direction of the selection. + ITextDocument* const text_object_model = GetTextObjectModel(); + if (!text_object_model) + return; + CComPtr<ITextSelection> selection; + const HRESULT hr = text_object_model->GetSelection(&selection); + DCHECK(hr == S_OK); + long flags; + selection->GetFlags(&flags); + if (flags & tomSelStartActive) + std::swap(sel.cpMin, sel.cpMax); +} + +std::wstring AutocompleteEditViewWin::GetSelectedText() const { + // Figure out the length of the selection. + CHARRANGE sel; + GetSel(sel); + + // Grab the selected text. + std::wstring str; + GetSelText(WriteInto(&str, sel.cpMax - sel.cpMin + 1)); + return str; +} + +void AutocompleteEditViewWin::SetSelection(LONG start, LONG end) { + SetSel(start, end); + + if (start <= end) + return; + + // We need to reverse the direction of the selection. + ITextDocument* const text_object_model = GetTextObjectModel(); + if (!text_object_model) + return; + CComPtr<ITextSelection> selection; + const HRESULT hr = text_object_model->GetSelection(&selection); + DCHECK(hr == S_OK); + selection->SetFlags(tomSelStartActive); +} + +void AutocompleteEditViewWin::PlaceCaretAt(std::wstring::size_type pos) { + SetSelection(static_cast<LONG>(pos), static_cast<LONG>(pos)); +} + +bool AutocompleteEditViewWin::IsSelectAllForRange(const CHARRANGE& sel) const { + const int text_length = GetTextLength(); + return ((sel.cpMin == 0) && (sel.cpMax >= text_length)) || + ((sel.cpMax == 0) && (sel.cpMin >= text_length)); +} + +LONG AutocompleteEditViewWin::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. + RECT r; + GetRect(&r); + const int left_bound = std::max(r.left, PosFromChar(0).x); + if (x < left_bound) + return left_bound; + + // See if we need to clip to the right edge of the text. + const int length = GetTextLength(); + // Asking for the coordinate of any character past the end of the text gets + // the pixel just to the right of the last character. + const int right_bound = std::min(r.right, PosFromChar(length).x); + if ((length == 0) || (x < right_bound)) + return x; + + // 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. + return is_triple_click ? (right_bound - 1) : right_bound; +} + +void AutocompleteEditViewWin::EmphasizeURLComponents() { + ITextDocument* const text_object_model = GetTextObjectModel(); + ScopedFreeze freeze(this, text_object_model); + ScopedSuspendUndo suspend_undo(text_object_model); + + // Save the selection. + CHARRANGE saved_sel; + GetSelection(saved_sel); + + // See whether the contents are a URL with a non-empty host portion, which we + // should emphasize. To check for a URL, rather than using the type returned + // by Parse(), ask the model, which will check the desired page transition for + // this input. This can tell us whether an UNKNOWN input string is going to + // be treated as a search or a navigation, and is the same method the Paste + // And Go system uses. + url_parse::Parsed parts; + AutocompleteInput::Parse(GetText(), model_->GetDesiredTLD(), &parts, NULL); + const bool emphasize = model_->CurrentTextIsURL() && (parts.host.len > 0); + + // Set the baseline emphasis. + CHARFORMAT cf = {0}; + cf.dwMask = CFM_COLOR; + cf.dwEffects = 0; + cf.crTextColor = GetSysColor(emphasize ? COLOR_GRAYTEXT : COLOR_WINDOWTEXT); + SelectAll(false); + SetSelectionCharFormat(cf); + + if (emphasize) { + // We've found a host name, give it more emphasis. + cf.crTextColor = GetSysColor(COLOR_WINDOWTEXT); + SetSelection(parts.host.begin, parts.host.end()); + SetSelectionCharFormat(cf); + } + + // Emphasize the scheme for security UI display purposes (if necessary). + insecure_scheme_component_.reset(); + if (!model_->user_input_in_progress() && parts.scheme.is_nonempty() && + (scheme_security_level_ != ToolbarModel::NORMAL)) { + if (scheme_security_level_ == ToolbarModel::SECURE) { + cf.crTextColor = kSecureSchemeColor; + } else { + insecure_scheme_component_.begin = parts.scheme.begin; + insecure_scheme_component_.len = parts.scheme.len; + cf.crTextColor = kInsecureSchemeColor; + } + SetSelection(parts.scheme.begin, parts.scheme.end()); + SetSelectionCharFormat(cf); + } + + // Restore the selection. + SetSelectionRange(saved_sel); +} + +void AutocompleteEditViewWin::EraseTopOfSelection( + CDC* dc, const CRect& client_rect, const CRect& paint_clip_rect) { + // Find the area we care about painting. We could calculate the rect + // containing just the selected portion, but there's no harm in simply erasing + // the whole top of the client area, and at least once I saw us manage to + // select the "phantom newline" briefly, which looks very weird if not clipped + // off at the same height. + CRect erase_rect(client_rect.left, client_rect.top, client_rect.right, + client_rect.top + font_y_adjustment_); + erase_rect.IntersectRect(erase_rect, paint_clip_rect); + + // Erase to the background color. + if (!erase_rect.IsRectNull()) + dc->FillSolidRect(&erase_rect, background_color_); +} + +void AutocompleteEditViewWin::DrawSlashForInsecureScheme( + HDC hdc, + const CRect& client_rect, + const CRect& paint_clip_rect) { + DCHECK(insecure_scheme_component_.is_nonempty()); + + // Calculate the rect, in window coordinates, containing the portion of the + // scheme where we'll be drawing the slash. Vertically, we draw across one + // x-height of text, plus an additional 3 stroke diameters (the stroke width + // plus a half-stroke width of space between the stroke and the text, both + // above and below the text). + const int font_top = client_rect.top + font_y_adjustment_; + const SkScalar kStrokeWidthPixels = SkIntToScalar(2); + const int kAdditionalSpaceOutsideFont = + static_cast<int>(ceil(kStrokeWidthPixels * 1.5f)); + const CRect scheme_rect(PosFromChar(insecure_scheme_component_.begin).x, + font_top + font_ascent_ - font_x_height_ - + kAdditionalSpaceOutsideFont, + PosFromChar(insecure_scheme_component_.end()).x, + font_top + font_ascent_ + + kAdditionalSpaceOutsideFont); + + // Clip to the portion we care about and translate to canvas coordinates + // (see the canvas creation below) for use later. + CRect canvas_clip_rect, canvas_paint_clip_rect; + canvas_clip_rect.IntersectRect(scheme_rect, client_rect); + canvas_paint_clip_rect.IntersectRect(canvas_clip_rect, paint_clip_rect); + if (canvas_paint_clip_rect.IsRectNull()) + return; // We don't need to paint any of this region, so just bail early. + canvas_clip_rect.OffsetRect(-scheme_rect.left, -scheme_rect.top); + canvas_paint_clip_rect.OffsetRect(-scheme_rect.left, -scheme_rect.top); + + // Create a paint context for drawing the antialiased stroke. + SkPaint paint; + paint.setAntiAlias(true); + paint.setStrokeWidth(kStrokeWidthPixels); + paint.setStrokeCap(SkPaint::kRound_Cap); + + // Create a canvas as large as |scheme_rect| to do our drawing, and initialize + // it to fully transparent so any antialiasing will look nice when painted + // atop the edit. + ChromeCanvas canvas(scheme_rect.Width(), scheme_rect.Height(), false); + // TODO (jcampan): This const_cast should not be necessary once the SKIA + // API has been changed to return a non-const bitmap. + (const_cast<SkBitmap&>(canvas.getDevice()->accessBitmap(true))). + eraseARGB(0, 0, 0, 0); + + // Calculate the start and end of the stroke, which are just the lower left + // and upper right corners of the canvas, inset by the radius of the endcap + // so we don't clip the endcap off. + const SkScalar kEndCapRadiusPixels = kStrokeWidthPixels / SkIntToScalar(2); + const SkPoint start_point = { + kEndCapRadiusPixels, + SkIntToScalar(scheme_rect.Height()) - kEndCapRadiusPixels }; + const SkPoint end_point = { + SkIntToScalar(scheme_rect.Width()) - kEndCapRadiusPixels, + kEndCapRadiusPixels }; + + // Calculate the selection rectangle in canvas coordinates, which we'll use + // to clip the stroke so we can draw the unselected and selected portions. + CHARRANGE sel; + GetSel(sel); + const SkRect selection_rect = { + SkIntToScalar(PosFromChar(sel.cpMin).x - scheme_rect.left), + SkIntToScalar(0), + SkIntToScalar(PosFromChar(sel.cpMax).x - scheme_rect.left), + SkIntToScalar(scheme_rect.Height()) }; + + // Draw the unselected portion of the stroke. + canvas.save(); + if (selection_rect.isEmpty() || + canvas.clipRect(selection_rect, SkRegion::kDifference_Op)) { + paint.setColor(kSchemeStrikeoutColor); + canvas.drawLine(start_point.fX, start_point.fY, + end_point.fX, end_point.fY, paint); + } + canvas.restore(); + + // Draw the selected portion of the stroke. + if (!selection_rect.isEmpty() && canvas.clipRect(selection_rect)) { + paint.setColor(kSchemeSelectedStrikeoutColor); + canvas.drawLine(start_point.fX, start_point.fY, + end_point.fX, end_point.fY, paint); + } + + // Now copy what we drew to the target HDC. + canvas.getTopPlatformDevice().drawToHDC(hdc, + scheme_rect.left + canvas_paint_clip_rect.left - canvas_clip_rect.left, + std::max(scheme_rect.top, client_rect.top) + canvas_paint_clip_rect.top - + canvas_clip_rect.top, &canvas_paint_clip_rect); +} + +void AutocompleteEditViewWin::DrawDropHighlight( + HDC hdc, const CRect& client_rect, const CRect& paint_clip_rect) { + DCHECK(drop_highlight_position_ != -1); + + const int highlight_y = client_rect.top + font_y_adjustment_; + const int highlight_x = PosFromChar(drop_highlight_position_).x - 1; + const CRect highlight_rect(highlight_x, + highlight_y, + highlight_x + 1, + highlight_y + font_ascent_ + font_descent_); + + // Clip the highlight to the region being painted. + CRect clip_rect; + clip_rect.IntersectRect(highlight_rect, paint_clip_rect); + if (clip_rect.IsRectNull()) + return; + + HGDIOBJ last_pen = SelectObject(hdc, CreatePen(PS_SOLID, 1, RGB(0, 0, 0))); + MoveToEx(hdc, clip_rect.left, clip_rect.top, NULL); + LineTo(hdc, clip_rect.left, clip_rect.bottom); + DeleteObject(SelectObject(hdc, last_pen)); +} + +void AutocompleteEditViewWin::TextChanged() { + ScopedFreeze freeze(this, GetTextObjectModel()); + EmphasizeURLComponents(); + controller_->OnChanged(); +} + +std::wstring AutocompleteEditViewWin::GetClipboardText() const { + // Try text format. + ClipboardService* clipboard = g_browser_process->clipboard_service(); + if (clipboard->IsFormatAvailable(CF_UNICODETEXT)) { + std::wstring text; + clipboard->ReadText(&text); + + // Note: Unlike in the find popup and textfield view, here we completely + // remove whitespace strings containing newlines. We assume users are + // most likely pasting in URLs that may have been split into multiple + // lines in terminals, email programs, etc., and so linebreaks indicate + // completely bogus whitespace that would just cause the input to be + // invalid. + return CollapseWhitespace(text, true); + } + + // Try bookmark format. + // + // It is tempting to try bookmark format first, but the URL we get out of a + // bookmark has been cannonicalized via GURL. This means if a user copies + // and pastes from the URL bar to itself, the text will get fixed up and + // cannonicalized, which is not what the user expects. By pasting in this + // order, we are sure to paste what the user copied. + if (clipboard->IsFormatAvailable(Clipboard::GetUrlWFormatType())) { + std::string url_str; + clipboard->ReadBookmark(NULL, &url_str); + // pass resulting url string through GURL to normalize + GURL url(url_str); + if (url.is_valid()) + return UTF8ToWide(url.spec()); + } + + return std::wstring(); +} + +bool AutocompleteEditViewWin::CanPasteAndGo(const std::wstring& text) const { + return !popup_window_mode_ && model_->CanPasteAndGo(text); +} + +ITextDocument* AutocompleteEditViewWin::GetTextObjectModel() const { + if (!text_object_model_) { + // This is lazily initialized, instead of being initialized in the + // constructor, in order to avoid hurting startup performance. + CComPtr<IRichEditOle> ole_interface; + ole_interface.Attach(GetOleInterface()); + text_object_model_ = ole_interface; + } + return text_object_model_; +} + +void AutocompleteEditViewWin::StartDragIfNecessary(const CPoint& point) { + if (initiated_drag_ || !win_util::IsDrag(mouse_down_point_, point)) + return; + + scoped_refptr<OSExchangeData> data = new OSExchangeData; + + DWORD supported_modes = DROPEFFECT_COPY; + + CHARRANGE sel; + GetSelection(sel); + + // We're about to start a drag session, but the edit is expecting a mouse up + // that it uses to reset internal state. If we don't send a mouse up now, + // when the mouse moves back into the edit the edit will reset the selection. + // So, we send the event now which resets the selection. We then restore the + // selection and start the drag. We always send lbuttonup as otherwise we + // might trigger a context menu (right up). This seems scary, but doesn't + // seem to cause problems. + { + ScopedFreeze freeze(this, GetTextObjectModel()); + DefWindowProc(WM_LBUTTONUP, 0, + MAKELPARAM(mouse_down_point_.x, mouse_down_point_.y)); + SetSelectionRange(sel); + } + + const std::wstring start_text(GetText()); + if (IsSelectAllForRange(sel)) { + // All the text is selected, export as URL. + GURL url; + std::wstring title; + SkBitmap favicon; + model_->GetDataForURLExport(&url, &title, &favicon); + drag_utils::SetURLAndDragImage(url, title, favicon, data.get()); + data->SetURL(url, title); + supported_modes |= DROPEFFECT_LINK; + UserMetrics::RecordAction(L"Omnibox_DragURL", model_->profile()); + } else { + supported_modes |= DROPEFFECT_MOVE; + UserMetrics::RecordAction(L"Omnibox_DragString", model_->profile()); + } + + data->SetString(GetSelectedText()); + + scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); + DWORD dropped_mode; + in_drag_ = true; + if (DoDragDrop(data, drag_source, supported_modes, &dropped_mode) == + DRAGDROP_S_DROP) { + if ((dropped_mode == DROPEFFECT_MOVE) && (start_text == GetText())) { + ScopedFreeze freeze(this, GetTextObjectModel()); + OnBeforePossibleChange(); + SetSelectionRange(sel); + ReplaceSel(L"", true); + OnAfterPossibleChange(); + } + // else case, not a move or it was a move and the drop was on us. + // If the drop was on us, EditDropTarget took care of the move so that + // we don't have to delete the text. + possible_drag_ = false; + } else { + // Drag was canceled or failed. The mouse may still be down and + // over us, in which case we need possible_drag_ to remain true so + // that we don't forward mouse move events to the edit which will + // start another drag. + // + // NOTE: we didn't use mouse capture during the mouse down as DoDragDrop + // does its own capture. + CPoint cursor_location; + GetCursorPos(&cursor_location); + + CRect client_rect; + GetClientRect(&client_rect); + + CPoint client_origin_on_screen(client_rect.left, client_rect.top); + ClientToScreen(&client_origin_on_screen); + client_rect.MoveToXY(client_origin_on_screen.x, + client_origin_on_screen.y); + possible_drag_ = (client_rect.PtInRect(cursor_location) && + ((GetKeyState(VK_LBUTTON) != 0) || + (GetKeyState(VK_MBUTTON) != 0) || + (GetKeyState(VK_RBUTTON) != 0))); + } + + in_drag_ = false; + initiated_drag_ = true; + tracking_click_ = false; +} + +void AutocompleteEditViewWin::OnPossibleDrag(const CPoint& point) { + if (possible_drag_) + return; + + mouse_down_point_ = point; + initiated_drag_ = false; + + CHARRANGE selection; + GetSel(selection); + if (selection.cpMin != selection.cpMax) { + const POINT min_sel_location(PosFromChar(selection.cpMin)); + const POINT max_sel_location(PosFromChar(selection.cpMax)); + // NOTE: we don't consider the y location here as we always pass a + // y-coordinate in the middle to the default handler which always triggers + // a drag regardless of the y-coordinate. + possible_drag_ = (point.x >= min_sel_location.x) && + (point.x < max_sel_location.x); + } +} + +void AutocompleteEditViewWin::UpdateDragDone(UINT keys) { + possible_drag_ = (possible_drag_ && + ((keys & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) != 0)); +} + +void AutocompleteEditViewWin::RepaintDropHighlight(int position) { + if ((position != -1) && (position <= GetTextLength())) { + const POINT min_loc(PosFromChar(position)); + const RECT highlight_bounds = {min_loc.x - 1, font_y_adjustment_, + min_loc.x + 2, font_ascent_ + font_descent_ + font_y_adjustment_}; + InvalidateRect(&highlight_bounds, false); + } +} diff --git a/chrome/browser/autocomplete/autocomplete_edit_view_win.h b/chrome/browser/autocomplete/autocomplete_edit_view_win.h new file mode 100644 index 0000000..9905f1c --- /dev/null +++ b/chrome/browser/autocomplete/autocomplete_edit_view_win.h @@ -0,0 +1,458 @@ +// Copyright (c) 2006-2008 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 CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_EDIT_VIEW_WIN_H_ +#define CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_EDIT_VIEW_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 "base/scoped_ptr.h" +#include "chrome/browser/autocomplete/autocomplete.h" +#include "chrome/browser/autocomplete/autocomplete_edit_view.h" +#include "chrome/browser/toolbar_model.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/page_transition_types.h" +#include "chrome/views/menu.h" +#include "webkit/glue/window_open_disposition.h" + +class AutocompletePopupModel; +class CommandUpdater; +class Profile; +class TabContents; +namespace views { +class View; +} + +class AutocompleteEditController; +class AutocompleteEditModel; +class AutocompleteEditView; +class AutocompletePopupViewWin; + +// Provides the implementation of an edit control with a drop-down +// autocomplete box. The box itself is implemented in autocomplete_popup.cc +// This file implements the edit box and management for the popup. +class AutocompleteEditViewWin + : public CWindowImpl<AutocompleteEditViewWin, + CRichEditCtrl, + CWinTraits<WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | + ES_NOHIDESEL> >, + public CRichEditCommands<AutocompleteEditViewWin>, + public Menu::Delegate, + public AutocompleteEditView { + public: + struct State { + State(const CHARRANGE& selection, + const CHARRANGE& saved_selection_for_focus_change) + : selection(selection), + saved_selection_for_focus_change(saved_selection_for_focus_change) { + } + + const CHARRANGE selection; + const CHARRANGE saved_selection_for_focus_change; + }; + + DECLARE_WND_CLASS(L"Chrome_AutocompleteEditView"); + + AutocompleteEditViewWin(const ChromeFont& font, + AutocompleteEditController* controller, + ToolbarModel* toolbar_model, + views::View* parent_view, + HWND hwnd, + Profile* profile, + CommandUpdater* command_updater, + bool popup_window_mode); + ~AutocompleteEditViewWin(); + + views::View* parent_view() const { return parent_view_; } + + // Implement the AutocompleteEditView interface. + virtual AutocompleteEditModel* model() { return model_.get(); } + virtual const AutocompleteEditModel* model() const { return model_.get(); } + + virtual void SaveStateToTab(TabContents* tab); + + virtual void Update(const TabContents* tab_for_state_restoring); + + virtual void OpenURL(const GURL& url, + WindowOpenDisposition disposition, + PageTransition::Type transition, + const GURL& alternate_nav_url, + size_t selected_line, + const std::wstring& keyword); + + virtual std::wstring GetText() const; + + virtual void SetUserText(const std::wstring& text) { + SetUserText(text, text, true); + } + virtual void SetUserText(const std::wstring& text, + const std::wstring& display_text, + bool update_popup); + + virtual void SetWindowTextAndCaretPos(const std::wstring& text, + size_t caret_pos); + + virtual bool IsSelectAll(); + virtual void SelectAll(bool reversed); + virtual void RevertAll(); + + virtual void UpdatePopup(); + virtual void ClosePopup(); + + virtual void OnTemporaryTextMaybeChanged(const std::wstring& display_text, + bool save_original_selection); + virtual bool OnInlineAutocompleteTextMaybeChanged( + const std::wstring& display_text, size_t user_text_length); + virtual void OnRevertTemporaryText(); + virtual void OnBeforePossibleChange(); + virtual bool OnAfterPossibleChange(); + + // Exposes custom IAccessible implementation to the overall MSAA hierarchy. + IAccessible* GetIAccessible(); + + void SetDropHighlightPosition(int position); + int drop_highlight_position() const { return drop_highlight_position_; } + + // Returns true if a drag a drop session was initiated by this edit. + bool in_drag() const { return in_drag_; } + + // Moves the selected text to the specified position. + void MoveSelectedText(int new_position); + + // Inserts the text at the specified position. + void InsertText(int position, const std::wstring& text); + + // Invokes CanPasteAndGo with the specified text, and if successful navigates + // to the appropriate URL. The behavior of this is the same as if the user + // typed in the specified text and pressed enter. + void PasteAndGo(const std::wstring& text); + + // Called before an accelerator is processed to give us a chance to override + // it. + bool OverrideAccelerator(const views::Accelerator& accelerator); + + // Handler for external events passed in to us. The View that owns us may + // send us events that we should treat as if they were events on us. + void HandleExternalMsg(UINT msg, UINT flags, const CPoint& screen_point); + + // CWindowImpl + BEGIN_MSG_MAP(AutocompleteEdit) + MSG_WM_CHAR(OnChar) + MSG_WM_CONTEXTMENU(OnContextMenu) + MSG_WM_COPY(OnCopy) + MSG_WM_CUT(OnCut) + MESSAGE_HANDLER_EX(WM_GETOBJECT, OnGetObject) + MESSAGE_HANDLER_EX(WM_IME_COMPOSITION, OnImeComposition) + MSG_WM_KEYDOWN(OnKeyDown) + MSG_WM_KEYUP(OnKeyUp) + MSG_WM_KILLFOCUS(OnKillFocus) + MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk) + MSG_WM_LBUTTONDOWN(OnLButtonDown) + MSG_WM_LBUTTONUP(OnLButtonUp) + MSG_WM_MBUTTONDOWN(OnNonLButtonDown) + MSG_WM_MBUTTONUP(OnNonLButtonUp) + MSG_WM_MOUSEACTIVATE(OnMouseActivate) + MSG_WM_MOUSEMOVE(OnMouseMove) + MSG_WM_PAINT(OnPaint) + MSG_WM_PASTE(OnPaste) + MSG_WM_RBUTTONDOWN(OnNonLButtonDown) + MSG_WM_RBUTTONUP(OnNonLButtonUp) + MSG_WM_SETFOCUS(OnSetFocus) + MSG_WM_SYSCHAR(OnSysChar) // WM_SYSxxx == WM_xxx with ALT down + MSG_WM_SYSKEYDOWN(OnKeyDown) + MSG_WM_SYSKEYUP(OnKeyUp) + DEFAULT_REFLECTION_HANDLER() // avoids black margin area + END_MSG_MAP() + + // Menu::Delegate + virtual bool IsCommandEnabled(int id) const; + virtual bool GetContextualLabel(int id, std::wstring* out) 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(AutocompleteEditViewWin* edit, + ITextDocument* text_object_model); + ~ScopedFreeze(); + + private: + AutocompleteEditViewWin* const edit_; + ITextDocument* const text_object_model_; + + DISALLOW_COPY_AND_ASSIGN(ScopedFreeze); + }; + + // This object suspends placing any operations on the edit's undo stack until + // the object is destroyed. If we don't do this, some of the operations we + // perform behind the user's back will be undoable by the user, which feels + // bizarre and confusing. + class ScopedSuspendUndo { + public: + explicit ScopedSuspendUndo(ITextDocument* text_object_model); + ~ScopedSuspendUndo(); + + private: + ITextDocument* const text_object_model_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSuspendUndo); + }; + + // Replacement word-breaking proc for the rich edit control. + static int CALLBACK WordBreakProc(LPTSTR edit_text, + int current_pos, + int num_bytes, + int action); + + // Returns true if |edit_text| starting at |current_pos| is "://". + static bool SchemeEnd(LPTSTR edit_text, int current_pos, int length); + + // Message handlers + void OnChar(TCHAR ch, UINT repeat_count, UINT flags); + void OnContextMenu(HWND window, const CPoint& point); + void OnCopy(); + void OnCut(); + LRESULT OnGetObject(UINT uMsg, WPARAM wparam, LPARAM lparam); + LRESULT OnImeComposition(UINT message, WPARAM wparam, LPARAM lparam); + void OnKeyDown(TCHAR key, UINT repeat_count, UINT flags); + void OnKeyUp(TCHAR key, UINT repeat_count, UINT flags); + void OnKillFocus(HWND focus_wnd); + void OnLButtonDblClk(UINT keys, const CPoint& point); + void OnLButtonDown(UINT keys, const CPoint& point); + void OnLButtonUp(UINT keys, const CPoint& point); + LRESULT OnMouseActivate(HWND window, UINT hit_test, UINT mouse_message); + void OnMouseMove(UINT keys, const CPoint& point); + void OnNonLButtonDown(UINT keys, const CPoint& point); + void OnNonLButtonUp(UINT keys, const CPoint& point); + void OnPaint(HDC bogus_hdc); + void OnPaste(); + void OnSetFocus(HWND focus_wnd); + 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); + + // Helper functions for OnKeyDown() that handle accelerators applicable when + // we're not read-only and all the time, respectively. These return true if + // they handled the key. + bool OnKeyDownOnlyWritable(TCHAR key, UINT repeat_count, UINT flags); + bool OnKeyDownAllModes(TCHAR key, UINT repeat_count, UINT flags); + + // Like GetSel(), but returns a range where |cpMin| will be larger than + // |cpMax| if the cursor is at the start rather than the end of the selection + // (in other words, tracks selection direction as well as offsets). + // Note the non-Google-style "non-const-ref" argument, which matches GetSel(). + void GetSelection(CHARRANGE& sel) const; + + // Returns the currently selected text of the edit control. + std::wstring GetSelectedText() const; + + // Like SetSel(), but respects the selection direction implied by |start| and + // |end|: if |end| < |start|, the effective cursor will be placed at the + // beginning of the selection. + void SetSelection(LONG start, LONG end); + + // Like SetSelection(), but takes a CHARRANGE. + void SetSelectionRange(const CHARRANGE& sel) { + SetSelection(sel.cpMin, sel.cpMax); + } + + // Places the caret at the given position. This clears any selection. + void PlaceCaretAt(size_t pos); + + // Returns true if |sel| represents a forward or backward selection of all the + // text. + bool IsSelectAllForRange(const CHARRANGE& sel) const; + + // 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; + + // Parses the contents of the control for the scheme and the host name. + // Highlights the scheme in green or red depending on it security level. + // If a host name is found, it makes it visually stronger. + void EmphasizeURLComponents(); + + // Erases the portion of the selection in the font's y-adjustment area. For + // some reason the edit draws the selection rect here even though it's not + // part of the font. + void EraseTopOfSelection(CDC* dc, + const CRect& client_rect, + const CRect& paint_clip_rect); + + // Draws a slash across the scheme if desired. + void DrawSlashForInsecureScheme(HDC hdc, + const CRect& client_rect, + const CRect& paint_clip_rect); + + // Renders the drop highlight. + void DrawDropHighlight(HDC hdc, + const CRect& client_rect, + const CRect& paint_clip_rect); + + // Internally invoked whenever the text changes in some way. + void TextChanged(); + + // Returns the current clipboard contents as a string that can be pasted in. + // In addition to just getting CF_UNICODETEXT out, this can also extract URLs + // from bookmarks on the clipboard. + std::wstring GetClipboardText() const; + + // Determines whether the user can "paste and go", given the specified text. + bool CanPasteAndGo(const std::wstring& text) const; + + // Getter for the text_object_model_, used by the ScopedXXX classes. Note + // that the pointer returned here is only valid as long as the + // AutocompleteEdit is still alive. Also, if the underlying call fails, this + // may return NULL. + ITextDocument* GetTextObjectModel() const; + + // Invoked during a mouse move. As necessary starts a drag and drop session. + void StartDragIfNecessary(const CPoint& point); + + // Invoked during a mouse down. If the mouse location is over the selection + // this sets possible_drag_ to true to indicate a drag should start if the + // user moves the mouse far enough to start a drag. + void OnPossibleDrag(const CPoint& point); + + // Invoked when a mouse button is released. If none of the buttons are still + // down, this sets possible_drag_ to false. + void UpdateDragDone(UINT keys); + + // Redraws the necessary region for a drop highlight at the specified + // position. This does nothing if position is beyond the bounds of the + // text. + void RepaintDropHighlight(int position); + + scoped_ptr<AutocompleteEditModel> model_; + + scoped_ptr<AutocompletePopupViewWin> popup_view_; + + AutocompleteEditController* controller_; + + // The parent view for the edit, used to align the popup and for + // accessibility. + views::View* parent_view_; + + ToolbarModel* toolbar_model_; + + // The object that handles additional command functionality exposed on the + // edit, such as invoking the keyword editor. + CommandUpdater* command_updater_; + + // When true, the location bar view is read only and also is has a slightly + // different presentation (font size / color). This is used for popups. + bool popup_window_mode_; + + // Non-null when the edit is gaining focus from a left click. This is only + // needed between when WM_MOUSEACTIVATE and WM_LBUTTONDOWN get processed. It + // serves two purposes: first, by communicating to OnLButtonDown() that we're + // gaining focus from a left click, it allows us to work even with the + // inconsistent order in which various Windows messages get sent (see comments + // in OnMouseActivate()). Second, by holding the edit frozen, it ensures that + // when we process WM_SETFOCUS the edit won't first redraw itself with the + // caret at the beginning, and then have it blink to where the mouse cursor + // really is shortly afterward. + scoped_ptr<ScopedFreeze> gaining_focus_; + + // When the user clicks to give us focus, we watch to see if they're clicking + // or dragging. When they're clicking, we select nothing until mouseup, then + // select all the text in the edit. During this process, tracking_click_ is + // true and mouse_down_point_ holds the original click location. At other + // times, tracking_click_ is false, and the contents of mouse_down_point_ + // should be ignored. + bool tracking_click_; + CPoint mouse_down_point_; + + // 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_; + + // Variables for tracking state before and after a possible change. + std::wstring text_before_change_; + CHARRANGE sel_before_change_; + + // Set at the same time the model's original_* members are set, and valid in + // the same cases. + CHARRANGE original_selection_; + + // Holds the user's selection across focus changes. cpMin holds -1 when + // there is no saved selection. + CHARRANGE saved_selection_for_focus_change_; + + // The context menu for the edit. + scoped_ptr<Menu> context_menu_; + + // Font we're using. We keep a reference to make sure the font supplied to + // the constructor doesn't go away before we do. + ChromeFont font_; + + // Metrics about the font, which we keep so we don't need to recalculate them + // every time we paint. |font_y_adjustment_| is the number of pixels we need + // to shift the font vertically in order to make its baseline be at our + // desired baseline in the edit. + int font_ascent_; + int font_descent_; + int font_x_height_; + int font_y_adjustment_; + + // If true, indicates the mouse is down and if the mouse is moved enough we + // should start a drag. + bool possible_drag_; + + // If true, we're in a call to DoDragDrop. + bool in_drag_; + + // If true indicates we've run a drag and drop session. This is used to + // avoid starting two drag and drop sessions if the drag is canceled while + // the mouse is still down. + bool initiated_drag_; + + // Position of the drop highlight. If this is -1, there is no drop highlight. + int drop_highlight_position_; + + // Security UI-related data. + COLORREF background_color_; + ToolbarModel::SecurityLevel scheme_security_level_; + + // This interface is useful for accessing the CRichEditCtrl at a low level. + mutable CComQIPtr<ITextDocument> text_object_model_; + + // This contains the scheme char start and stop indexes that should be + // striken-out when displaying an insecure scheme. + url_parse::Component insecure_scheme_component_; + + // Instance of accessibility information and handling. + mutable CComPtr<IAccessible> autocomplete_accessibility_; + + DISALLOW_COPY_AND_ASSIGN(AutocompleteEditViewWin); +}; + +#endif // CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_EDIT_VIEW_WIN_H_ diff --git a/chrome/browser/autocomplete/autocomplete_popup_view_win.cc b/chrome/browser/autocomplete/autocomplete_popup_view_win.cc index a583154..1c13a6c 100755 --- a/chrome/browser/autocomplete/autocomplete_popup_view_win.cc +++ b/chrome/browser/autocomplete/autocomplete_popup_view_win.cc @@ -185,7 +185,7 @@ COLORREF AutocompletePopupViewWin::DrawLineInfo::AlphaBlend( AutocompletePopupViewWin::AutocompletePopupViewWin( const ChromeFont& font, - AutocompleteEditView* edit_view, + AutocompleteEditViewWin* edit_view, AutocompleteEditModel* edit_model, Profile* profile) : model_(new AutocompletePopupModel(this, edit_model, profile)), diff --git a/chrome/browser/autocomplete/autocomplete_popup_view_win.h b/chrome/browser/autocomplete/autocomplete_popup_view_win.h index dcfd505..18c6449 100755 --- a/chrome/browser/autocomplete/autocomplete_popup_view_win.h +++ b/chrome/browser/autocomplete/autocomplete_popup_view_win.h @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#ifndef CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_POPUP_VIEW_WIN_H_ +#define CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_POPUP_VIEW_WIN_H_ + #include <atlbase.h> #include <atlapp.h> #include <atlcrack.h> @@ -18,7 +21,7 @@ class AutocompletePopupModel; class AutocompleteEditModel; -class AutocompleteEditView; +class AutocompleteEditViewWin; class Profile; class SkBitmap; @@ -46,7 +49,7 @@ class AutocompletePopupViewWin END_MSG_MAP() AutocompletePopupViewWin(const ChromeFont& font, - AutocompleteEditView* edit_view, + AutocompleteEditViewWin* edit_view, AutocompleteEditModel* edit_model, Profile* profile); @@ -183,7 +186,7 @@ class AutocompletePopupViewWin scoped_ptr<AutocompletePopupModel> model_; - AutocompleteEditView* edit_view_; + AutocompleteEditViewWin* edit_view_; // Cached GDI information for drawing. DrawLineInfo line_info_; @@ -203,3 +206,5 @@ class AutocompletePopupViewWin DISALLOW_COPY_AND_ASSIGN(AutocompletePopupViewWin); }; + +#endif // CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_POPUP_VIEW_WIN_H_ diff --git a/chrome/browser/autocomplete/edit_drop_target.cc b/chrome/browser/autocomplete/edit_drop_target.cc deleted file mode 100644 index abe1566..0000000 --- a/chrome/browser/autocomplete/edit_drop_target.cc +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) 2006-2008 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 "chrome/browser/autocomplete/edit_drop_target.h" - -#include "base/string_util.h" -#include "chrome/browser/autocomplete/autocomplete_edit.h" -#include "chrome/common/os_exchange_data.h" -#include "googleurl/src/gurl.h" - -namespace { - -// A helper method for determining a valid DROPEFFECT given the allowed -// DROPEFFECTS. We prefer copy over link. -DWORD CopyOrLinkDropEffect(DWORD effect) { - if (effect & DROPEFFECT_COPY) - return DROPEFFECT_COPY; - if (effect & DROPEFFECT_LINK) - return DROPEFFECT_LINK; - return DROPEFFECT_NONE; -} - -} - -EditDropTarget::EditDropTarget(AutocompleteEditView* edit) - : BaseDropTarget(edit->m_hWnd), - edit_(edit), - drag_has_url_(false), - drag_has_string_(false) { -} - -DWORD EditDropTarget::OnDragEnter(IDataObject* data_object, - DWORD key_state, - POINT cursor_position, - DWORD effect) { - OSExchangeData os_data(data_object); - drag_has_url_ = os_data.HasURL(); - drag_has_string_ = !drag_has_url_ && os_data.HasString(); - if (drag_has_url_) { - if (edit_->in_drag()) { - // The edit we're associated with originated the drag. No point in - // allowing the user to drop back on us. - drag_has_url_ = false; - } - // NOTE: it would be nice to visually show all the text is going to - // be replaced by selecting all, but this caused painting problems. In - // particular the flashing caret would appear outside the edit! For now - // we stick with no visual indicator other than that shown own the mouse - // cursor. - } - return OnDragOver(data_object, key_state, cursor_position, effect); -} - -DWORD EditDropTarget::OnDragOver(IDataObject* data_object, - DWORD key_state, - POINT cursor_position, - DWORD effect) { - if (drag_has_url_) - return CopyOrLinkDropEffect(effect); - - if (drag_has_string_) { - UpdateDropHighlightPosition(cursor_position); - if (edit_->drop_highlight_position() == -1 && edit_->in_drag()) - return DROPEFFECT_NONE; - if (edit_->in_drag()) { - // The edit we're associated with originated the drag. Do the normal drag - // behavior. - DCHECK((effect & DROPEFFECT_COPY) && (effect & DROPEFFECT_MOVE)); - return (key_state & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE; - } - // Our edit didn't originate the drag, only allow link or copy. - return CopyOrLinkDropEffect(effect); - } - - return DROPEFFECT_NONE; -} - -void EditDropTarget::OnDragLeave(IDataObject* data_object) { - ResetDropHighlights(); -} - -DWORD EditDropTarget::OnDrop(IDataObject* data_object, - DWORD key_state, - POINT cursor_position, - DWORD effect) { - OSExchangeData os_data(data_object); - - if (drag_has_url_) { - GURL url; - std::wstring title; - if (os_data.GetURLAndTitle(&url, &title)) { - edit_->SetUserText(UTF8ToWide(url.spec())); - edit_->model()->AcceptInput(CURRENT_TAB, true); - return CopyOrLinkDropEffect(effect); - } - } else if (drag_has_string_) { - int string_drop_position = edit_->drop_highlight_position(); - std::wstring text; - if ((string_drop_position != -1 || !edit_->in_drag()) && - os_data.GetString(&text)) { - DCHECK(string_drop_position == -1 || - ((string_drop_position >= 0) && - (string_drop_position <= edit_->GetTextLength()))); - const DWORD drop_operation = - OnDragOver(data_object, key_state, cursor_position, effect); - if (edit_->in_drag()) { - if (drop_operation == DROPEFFECT_MOVE) - edit_->MoveSelectedText(string_drop_position); - else - edit_->InsertText(string_drop_position, text); - } else { - edit_->PasteAndGo(CollapseWhitespace(text, true)); - } - ResetDropHighlights(); - return drop_operation; - } - } - - ResetDropHighlights(); - - return DROPEFFECT_NONE; -} - -void EditDropTarget::UpdateDropHighlightPosition( - const POINT& cursor_screen_position) { - if (drag_has_string_) { - POINT client_position = cursor_screen_position; - ScreenToClient(edit_->m_hWnd, &client_position); - int drop_position = edit_->CharFromPos(client_position); - if (edit_->in_drag()) { - // Our edit originated the drag, don't allow a drop if over the selected - // region. - LONG sel_start, sel_end; - edit_->GetSel(sel_start, sel_end); - if ((sel_start != sel_end) && (drop_position >= sel_start) && - (drop_position <= sel_end)) - drop_position = -1; - } else { - // A drop from a source other than the edit replaces all the text, so - // we don't show the drop location. See comment in OnDragEnter as to why - // we don't try and select all here. - drop_position = -1; - } - edit_->SetDropHighlightPosition(drop_position); - } -} - -void EditDropTarget::ResetDropHighlights() { - if (drag_has_string_) - edit_->SetDropHighlightPosition(-1); -} diff --git a/chrome/browser/autocomplete/edit_drop_target.h b/chrome/browser/autocomplete/edit_drop_target.h deleted file mode 100644 index c032544..0000000 --- a/chrome/browser/autocomplete/edit_drop_target.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2006-2008 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 CHROME_BROWSER_AUTOCOMPLETE_EDIT_DROP_TARGET_H_ -#define CHROME_BROWSER_AUTOCOMPLETE_EDIT_DROP_TARGET_H_ - -#include "base/base_drop_target.h" - -class AutocompleteEditView; - -// EditDropTarget is the IDropTarget implementation installed on -// AutocompleteEditView. EditDropTarget prefers URL over plain text. A drop of a -// URL replaces all the text of the edit and navigates immediately to the URL. A -// drop of plain text from the same edit either copies or moves the selected -// text, and a drop of plain text from a source other than the edit does a paste -// and go. -class EditDropTarget : public BaseDropTarget { - public: - explicit EditDropTarget(AutocompleteEditView* edit); - - protected: - virtual DWORD OnDragEnter(IDataObject* data_object, - DWORD key_state, - POINT cursor_position, - DWORD effect); - virtual DWORD OnDragOver(IDataObject* data_object, - DWORD key_state, - POINT cursor_position, - DWORD effect); - virtual void OnDragLeave(IDataObject* data_object); - virtual DWORD OnDrop(IDataObject* data_object, - DWORD key_state, - POINT cursor_position, - DWORD effect); - - private: - // If dragging a string, the drop highlight position of the edit is reset - // based on the mouse position. - void UpdateDropHighlightPosition(const POINT& cursor_screen_position); - - // Resets the visual drop indicates we install on the edit. - void ResetDropHighlights(); - - // The edit we're the drop target for. - AutocompleteEditView* edit_; - - // If true, the drag session contains a URL. - bool drag_has_url_; - - // If true, the drag session contains a string. If drag_has_url_ is true, - // this is false regardless of whether the clipboard has a string. - bool drag_has_string_; - - DISALLOW_EVIL_CONSTRUCTORS(EditDropTarget); -}; - -#endif // CHROME_BROWSER_AUTOCOMPLETE_EDIT_DROP_TARGET_H_ |