Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9278)

Unified Diff: chrome/browser/ui/views/omnibox/omnibox_view_win.cc

Issue 12566007: Wrap calls to GetTouchInputInfo (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/ui/views/omnibox/omnibox_view_win.cc
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_win.cc b/chrome/browser/ui/views/omnibox/omnibox_view_win.cc
index 5780aecd429a83743d65e12899e00cf0644787a2..b34699ef76118f8aa25a785cbc6704f9bad1e2b3 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_win.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_win.cc
@@ -1,2851 +1,2852 @@
-// Copyright 2012 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/ui/views/omnibox/omnibox_view_win.h"
-
-#include <algorithm>
-#include <locale>
-#include <string>
-
-#include <richedit.h>
-#include <textserv.h>
-
-#include "base/auto_reset.h"
-#include "base/basictypes.h"
-#include "base/bind.h"
-#include "base/i18n/rtl.h"
-#include "base/lazy_instance.h"
-#include "base/memory/ref_counted.h"
-#include "base/string_util.h"
-#include "base/utf_string_conversions.h"
-#include "base/win/iat_patch_function.h"
-#include "base/win/metro.h"
-#include "base/win/scoped_hdc.h"
-#include "base/win/scoped_select_object.h"
-#include "base/win/windows_version.h"
-#include "chrome/app/chrome_command_ids.h"
-#include "chrome/browser/autocomplete/autocomplete_input.h"
-#include "chrome/browser/autocomplete/autocomplete_match.h"
-#include "chrome/browser/autocomplete/keyword_provider.h"
-#include "chrome/browser/bookmarks/bookmark_node_data.h"
-#include "chrome/browser/command_updater.h"
-#include "chrome/browser/instant/search.h"
-#include "chrome/browser/net/url_fixer_upper.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/omnibox/omnibox_edit_controller.h"
-#include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
-#include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
-#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
-#include "chrome/browser/ui/views/missing_system_file_dialog_win.h"
-#include "chrome/browser/ui/views/omnibox/omnibox_view_views.h"
-#include "chrome/common/chrome_notification_types.h"
-#include "content/public/browser/user_metrics.h"
-#include "content/public/browser/web_contents.h"
-#include "googleurl/src/url_util.h"
-#include "grit/generated_resources.h"
-#include "net/base/escape.h"
-#include "skia/ext/skia_utils_win.h"
-#include "ui/base/accessibility/accessible_view_state.h"
-#include "ui/base/clipboard/clipboard.h"
-#include "ui/base/clipboard/scoped_clipboard_writer.h"
-#include "ui/base/dragdrop/drag_drop_types.h"
-#include "ui/base/dragdrop/drag_source.h"
-#include "ui/base/dragdrop/drop_target.h"
-#include "ui/base/dragdrop/os_exchange_data.h"
-#include "ui/base/dragdrop/os_exchange_data_provider_win.h"
-#include "ui/base/events/event.h"
-#include "ui/base/events/event_constants.h"
-#include "ui/base/ime/win/tsf_bridge.h"
-#include "ui/base/ime/win/tsf_event_router.h"
-#include "ui/base/keycodes/keyboard_codes.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/l10n/l10n_util_win.h"
-#include "ui/base/win/mouse_wheel_util.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/image/image.h"
-#include "ui/views/button_drag_utils.h"
-#include "ui/views/controls/menu/menu_item_view.h"
-#include "ui/views/controls/menu/menu_model_adapter.h"
-#include "ui/views/controls/menu/menu_runner.h"
-#include "ui/views/controls/textfield/native_textfield_win.h"
-#include "ui/views/widget/widget.h"
-#include "win8/util/win8_util.h"
-
-#pragma comment(lib, "oleacc.lib") // Needed for accessibility support.
-
-using content::UserMetricsAction;
-using content::WebContents;
-
-namespace {
-
-const char kAutocompleteEditStateKey[] = "AutocompleteEditState";
-
-// msftedit.dll is RichEdit ver 4.1.
-// This version is available from WinXP SP1 and has TSF support.
-const wchar_t* kRichEditDLLName = L"msftedit.dll";
-
-// 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;
-}
-
-// A helper method for determining a valid drag operation given the allowed
-// operation. We prefer copy over link.
-int CopyOrLinkDragOperation(int drag_operation) {
- if (drag_operation & ui::DragDropTypes::DRAG_COPY)
- return ui::DragDropTypes::DRAG_COPY;
- if (drag_operation & ui::DragDropTypes::DRAG_LINK)
- return ui::DragDropTypes::DRAG_LINK;
- return ui::DragDropTypes::DRAG_NONE;
-}
-
-// The AutocompleteEditState struct contains enough information about the
-// OmniboxEditModel and OmniboxViewWin 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 : public base::SupportsUserData::Data {
- AutocompleteEditState(const OmniboxEditModel::State& model_state,
- const OmniboxViewWin::State& view_state)
- : model_state(model_state),
- view_state(view_state) {
- }
- virtual ~AutocompleteEditState() {}
-
- const OmniboxEditModel::State model_state;
- const OmniboxViewWin::State view_state;
-};
-
-// Returns true if the current point is far enough from the origin that it
-// would be considered a drag.
-bool IsDrag(const POINT& origin, const POINT& current) {
- return views::View::ExceededDragThreshold(
- gfx::Point(current) - gfx::Point(origin));
-}
-
-// Copies |selected_text| as text to the primary clipboard.
-void DoCopyText(const string16& selected_text, Profile* profile) {
- ui::ScopedClipboardWriter scw(ui::Clipboard::GetForCurrentThread(),
- ui::Clipboard::BUFFER_STANDARD,
- content::BrowserContext::
- GetMarkerForOffTheRecordContext(profile));
- scw.WriteText(selected_text);
-}
-
-// Writes |url| and |text| to the clipboard as a well-formed URL.
-void DoCopyURL(const GURL& url, const string16& text, Profile* profile) {
- BookmarkNodeData data;
- data.ReadFromTuple(url, text);
- data.WriteToClipboard(profile);
-}
-
-} // namespace
-
-// EditDropTarget is the IDropTarget implementation installed on
-// OmniboxViewWin. 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 OmniboxViewWin::EditDropTarget : public ui::DropTarget {
- public:
- explicit EditDropTarget(OmniboxViewWin* 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.
- OmniboxViewWin* 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_COPY_AND_ASSIGN(EditDropTarget);
-};
-
-OmniboxViewWin::EditDropTarget::EditDropTarget(OmniboxViewWin* edit)
- : ui::DropTarget(edit->m_hWnd),
- edit_(edit),
- drag_has_url_(false),
- drag_has_string_(false) {
-}
-
-DWORD OmniboxViewWin::EditDropTarget::OnDragEnter(IDataObject* data_object,
- DWORD key_state,
- POINT cursor_position,
- DWORD effect) {
- ui::OSExchangeData os_data(new ui::OSExchangeDataProviderWin(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 OmniboxViewWin::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 OmniboxViewWin::EditDropTarget::OnDragLeave(IDataObject* data_object) {
- ResetDropHighlights();
-}
-
-DWORD OmniboxViewWin::EditDropTarget::OnDrop(IDataObject* data_object,
- DWORD key_state,
- POINT cursor_position,
- DWORD effect) {
- effect = OnDragOver(data_object, key_state, cursor_position, effect);
-
- ui::OSExchangeData os_data(new ui::OSExchangeDataProviderWin(data_object));
- gfx::Point point(cursor_position.x, cursor_position.y);
- ui::DropTargetEvent event(
- os_data, point, point,
- ui::DragDropTypes::DropEffectToDragOperation(effect));
-
- int drag_operation = edit_->OnPerformDropImpl(event, edit_->in_drag());
-
- if (!drag_has_url_)
- ResetDropHighlights();
-
- return ui::DragDropTypes::DragOperationToDropEffect(drag_operation);
-}
-
-void OmniboxViewWin::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 OmniboxViewWin::EditDropTarget::ResetDropHighlights() {
- if (drag_has_string_)
- edit_->SetDropHighlightPosition(-1);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Helper classes
-
-OmniboxViewWin::ScopedFreeze::ScopedFreeze(OmniboxViewWin* 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);
- }
-}
-
-OmniboxViewWin::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 in addition to 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_->InvalidateRect(NULL, false);
- edit_->UpdateWindow();
- }
- }
-}
-
-OmniboxViewWin::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);
-}
-
-OmniboxViewWin::ScopedSuspendUndo::~ScopedSuspendUndo() {
- // Resume Undo processing.
- if (text_object_model_)
- text_object_model_->Undo(tomResume, NULL);
-}
-
-// A subclass of NativeViewHost that provides accessibility info for the
-// underlying Omnibox view.
-class OmniboxViewWrapper : public views::NativeViewHost {
- public:
- explicit OmniboxViewWrapper(OmniboxViewWin* omnibox_view_win)
- : omnibox_view_win_(omnibox_view_win) {}
-
- gfx::NativeViewAccessible GetNativeViewAccessible() {
- // This forces it to use NativeViewAccessibilityWin rather than
- // any accessibility provided natively by the HWND.
- return View::GetNativeViewAccessible();
- }
-
- // views::View
- virtual void GetAccessibleState(ui::AccessibleViewState* state) {
- views::NativeViewHost::GetAccessibleState(state);
- state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_LOCATION);
- state->role = ui::AccessibilityTypes::ROLE_TEXT;
- state->value = omnibox_view_win_->GetText();
- state->state = ui::AccessibilityTypes::STATE_EDITABLE;
- size_t sel_start;
- size_t sel_end;
- omnibox_view_win_->GetSelectionBounds(&sel_start, &sel_end);
- state->selection_start = sel_start;
- state->selection_end = sel_end;
- }
-
- private:
- OmniboxViewWin* omnibox_view_win_;
-
- DISALLOW_COPY_AND_ASSIGN(OmniboxViewWrapper);
-};
-
-///////////////////////////////////////////////////////////////////////////////
-// OmniboxViewWin
-
-namespace {
-
-// 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;
-
-// Intercepted method for BeginPaint(). Must use __stdcall convention.
-HDC WINAPI BeginPaintIntercept(HWND hWnd, LPPAINTSTRUCT lpPaint) {
- if (!edit_hwnd || (hWnd != edit_hwnd))
- return ::BeginPaint(hWnd, lpPaint);
-
- *lpPaint = paint_struct;
- return paint_struct.hdc;
-}
-
-// Intercepted method for EndPaint(). Must use __stdcall convention.
-BOOL WINAPI EndPaintIntercept(HWND hWnd, const PAINTSTRUCT* lpPaint) {
- return (edit_hwnd && (hWnd == edit_hwnd)) || ::EndPaint(hWnd, lpPaint);
-}
-
-class PaintPatcher {
- public:
- PaintPatcher();
- ~PaintPatcher();
-
- void RefPatch();
- void DerefPatch();
-
- private:
- size_t refcount_;
- base::win::IATPatchFunction begin_paint_;
- base::win::IATPatchFunction end_paint_;
-
- DISALLOW_COPY_AND_ASSIGN(PaintPatcher);
-};
-
-PaintPatcher::PaintPatcher() : refcount_(0) {
-}
-
-PaintPatcher::~PaintPatcher() {
- DCHECK_EQ(0U, refcount_);
-}
-
-void PaintPatcher::RefPatch() {
- if (refcount_ == 0) {
- DCHECK(!begin_paint_.is_patched());
- DCHECK(!end_paint_.is_patched());
- begin_paint_.Patch(kRichEditDLLName, "user32.dll", "BeginPaint",
- &BeginPaintIntercept);
- end_paint_.Patch(kRichEditDLLName, "user32.dll", "EndPaint",
- &EndPaintIntercept);
- }
- ++refcount_;
-}
-
-void PaintPatcher::DerefPatch() {
- DCHECK(begin_paint_.is_patched());
- DCHECK(end_paint_.is_patched());
- --refcount_;
- if (refcount_ == 0) {
- begin_paint_.Unpatch();
- end_paint_.Unpatch();
- }
-}
-
-base::LazyInstance<PaintPatcher> g_paint_patcher = LAZY_INSTANCE_INITIALIZER;
-
-// twips are a unit of type measurement, and RichEdit controls use them
-// to set offsets.
-const int kTwipsPerInch = 1440;
-
-} // namespace
-
-HMODULE OmniboxViewWin::loaded_library_module_ = NULL;
-
-OmniboxViewWin::OmniboxViewWin(OmniboxEditController* controller,
- ToolbarModel* toolbar_model,
- LocationBarView* parent_view,
- CommandUpdater* command_updater,
- bool popup_window_mode,
- views::View* location_bar)
- : OmniboxView(parent_view->profile(), controller, toolbar_model,
- command_updater),
- popup_view_(OmniboxPopupContentsView::Create(
- parent_view->font(), this, model(), location_bar)),
- parent_view_(parent_view),
- popup_window_mode_(popup_window_mode),
- force_hidden_(false),
- tracking_click_(),
- tracking_double_click_(false),
- double_click_time_(0),
- can_discard_mousemove_(false),
- ignore_ime_messages_(false),
- delete_at_end_pressed_(false),
- font_(parent_view->font()),
- possible_drag_(false),
- in_drag_(false),
- initiated_drag_(false),
- drop_highlight_position_(-1),
- ime_candidate_window_open_(false),
- background_color_(skia::SkColorToCOLORREF(parent_view->GetColor(
- ToolbarModel::NONE, LocationBarView::BACKGROUND))),
- security_level_(ToolbarModel::NONE),
- text_object_model_(NULL),
- ALLOW_THIS_IN_INITIALIZER_LIST(
- tsf_event_router_(base::win::IsTSFAwareRequired() ?
- new ui::TSFEventRouter(this) : NULL)) {
- if (!loaded_library_module_)
- loaded_library_module_ = LoadLibrary(kRichEditDLLName);
-
- if (!loaded_library_module_) {
- // RichEdit DLL is not available. This is a rare error.
- MissingSystemFileDialog::ShowDialog(
- GetAncestor(location_bar->GetWidget()->GetNativeView(), GA_ROOT),
- parent_view_->profile());
- }
-
- saved_selection_for_focus_change_.cpMin = -1;
-
- g_paint_patcher.Pointer()->RefPatch();
-
- Create(location_bar->GetWidget()->GetNativeView(), 0, 0, 0,
- l10n_util::GetExtendedStyles());
- SetReadOnly(popup_window_mode_);
- SetFont(font_.GetNativeFont());
-
- // Disable auto font changing. Otherwise, characters come from
- // auto-completion and characters come from keyboard may be rendered with
- // different fonts. See http://crbug.com/168480 for details.
- const LRESULT lang_option = SendMessage(m_hWnd, EM_GETLANGOPTIONS, 0, 0);
- SendMessage(m_hWnd, EM_SETLANGOPTIONS, 0, lang_option & ~IMF_AUTOFONT);
-
- // 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.
- base::win::ScopedGetDC screen_dc(NULL);
- base::win::ScopedSelectObject font_in_dc(screen_dc, font_.GetNativeFont());
- TEXTMETRIC tm = {0};
- GetTextMetrics(screen_dc, &tm);
- int cap_height = font_.GetBaseline() - tm.tmInternalLeading;
- // 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.
- const float kXHeightRatio = 0.7f;
- font_x_height_ = static_cast<int>(
- (static_cast<float>(cap_height) * kXHeightRatio) + 0.5);
-
- // We set font_y_adjustment_ so that the ascender of the font gets
- // centered on the available height of the view.
- font_y_adjustment_ =
- (parent_view->GetInternalHeight(true) - cap_height) / 2 -
- tm.tmInternalLeading;
-
- // 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(screen_dc, LOGPIXELSY);
-
- // Set the default character style -- adjust to our desired baseline.
- CHARFORMAT cf = {0};
- cf.dwMask = CFM_OFFSET;
- cf.yOffset = -font_y_adjustment_ * kTwipsPerPixel;
- SetDefaultCharFormat(cf);
-
- SetBackgroundColor(background_color_);
-
- if (!popup_window_mode_) {
- // Non-read-only edit controls have a drop target. Revoke it so that we can
- // install our own. Revoking automatically deletes the existing one.
- HRESULT hr = RevokeDragDrop(m_hWnd);
- DCHECK_EQ(S_OK, hr);
-
- // Register our drop target. The scoped_refptr here will delete the drop
- // target if it fails to register itself correctly on |m_hWnd|. Otherwise,
- // the edit control will invoke RevokeDragDrop when it's being destroyed, so
- // we don't have to do so.
- scoped_refptr<EditDropTarget> drop_target(new EditDropTarget(this));
- }
-}
-
-OmniboxViewWin::~OmniboxViewWin() {
- // Explicitly release the text object model now that we're done with it, and
- // before we free the library. If the library gets unloaded before this
- // released, it becomes garbage. Note that since text_object_model_ is lazy
- // initialized, it may still be null.
- if (text_object_model_)
- text_object_model_->Release();
-
- // 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();
-}
-
-views::View* OmniboxViewWin::parent_view() const {
- return parent_view_;
-}
-
-void OmniboxViewWin::SaveStateToTab(WebContents* tab) {
- DCHECK(tab);
-
- const OmniboxEditModel::State model_state(model()->GetStateForTabSwitch());
-
- CHARRANGE selection;
- GetSelection(selection);
- tab->SetUserData(
- kAutocompleteEditStateKey,
- new AutocompleteEditState(
- model_state,
- State(selection, saved_selection_for_focus_change_)));
-}
-
-void OmniboxViewWin::Update(const WebContents* tab_for_state_restoring) {
- const bool visibly_changed_permanent_text =
- model()->UpdatePermanentText(toolbar_model()->GetText(true));
-
- const ToolbarModel::SecurityLevel security_level =
- toolbar_model()->GetSecurityLevel();
- const bool changed_security_level = (security_level != security_level_);
-
- // Bail early when no visible state will actually change (prevents an
- // unnecessary ScopedFreeze, and thus UpdateWindow()).
- if (!changed_security_level && !visibly_changed_permanent_text &&
- !tab_for_state_restoring)
- return;
-
- // Update our local state as desired. We set security_level_ here so it will
- // already be correct before we get to any RevertAll()s below and use it.
- security_level_ = security_level;
-
- // When we're switching to a new tab, restore its state, if any.
- ScopedFreeze freeze(this, GetTextObjectModel());
- 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 = static_cast<AutocompleteEditState*>(
- tab_for_state_restoring->GetUserData(&kAutocompleteEditStateKey));
- 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 OmniboxViewWin::OpenMatch(const AutocompleteMatch& match,
- WindowOpenDisposition disposition,
- const GURL& alternate_nav_url,
- size_t selected_line) {
- // When we navigate, we first revert to the unedited state, then if necessary
- // synchronously change the permanent text to the new URL. If we don't freeze
- // here, the user could potentially see a flicker of the current URL before
- // the new one reappears, which would look glitchy.
- ScopedFreeze freeze(this, GetTextObjectModel());
- OmniboxView::OpenMatch(match, disposition, alternate_nav_url, selected_line);
-}
-
-string16 OmniboxViewWin::GetText() const {
- const int len = GetTextLength() + 1;
- string16 str;
- if (len > 1)
- GetWindowText(WriteInto(&str, len), len);
- return str;
-}
-
-void OmniboxViewWin::SetUserText(const string16& text,
- const string16& display_text,
- bool update_popup) {
- ScopedFreeze freeze(this, GetTextObjectModel());
- saved_selection_for_focus_change_.cpMin = -1;
- OmniboxView::SetUserText(text, display_text, update_popup);
-}
-
-void OmniboxViewWin::SetWindowTextAndCaretPos(const string16& text,
- size_t caret_pos,
- bool update_popup,
- bool notify_text_changed) {
- SetWindowText(text.c_str());
- PlaceCaretAt(caret_pos);
-
- if (update_popup)
- UpdatePopup();
-
- if (notify_text_changed)
- TextChanged();
-}
-
-void OmniboxViewWin::SetForcedQuery() {
- const string16 current_text(GetText());
- const size_t start = current_text.find_first_not_of(kWhitespaceWide);
- if (start == string16::npos || (current_text[start] != '?'))
- OmniboxView::SetUserText(L"?");
- else
- SetSelection(current_text.length(), start + 1);
-}
-
-bool OmniboxViewWin::IsSelectAll() const {
- CHARRANGE selection;
- GetSel(selection);
- return IsSelectAllForRange(selection);
-}
-
-bool OmniboxViewWin::DeleteAtEndPressed() {
- return delete_at_end_pressed_;
-}
-
-void OmniboxViewWin::GetSelectionBounds(string16::size_type* start,
- string16::size_type* end) const {
- CHARRANGE selection;
- GetSel(selection);
- *start = static_cast<size_t>(selection.cpMin);
- *end = static_cast<size_t>(selection.cpMax);
-}
-
-void OmniboxViewWin::SelectAll(bool reversed) {
- if (reversed)
- SetSelection(GetTextLength(), 0);
- else
- SetSelection(0, GetTextLength());
-}
-
-void OmniboxViewWin::RevertAll() {
- ScopedFreeze freeze(this, GetTextObjectModel());
- saved_selection_for_focus_change_.cpMin = -1;
- OmniboxView::RevertAll();
-}
-
-void OmniboxViewWin::UpdatePopup() {
- ScopedFreeze freeze(this, GetTextObjectModel());
- model()->SetInputInProgress(true);
-
- // Don't allow the popup to open while the candidate window is open, so
- // they don't overlap.
- if (ime_candidate_window_open_)
- return;
-
- 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;
- }
-
- // 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 != sel.cpMin,
- (sel.cpMax < GetTextLength()) || IsImeComposing());
-}
-
-void OmniboxViewWin::SetFocus() {
- ::SetFocus(m_hWnd);
- // Restore caret visibility if focus is explicitly requested. This is
- // necessary because if we already have invisible focus, the ::SetFocus()
- // call above will short-circuit, preventing us from reaching
- // OmniboxEditModel::OnSetFocus(), which handles restoring visibility when the
- // omnibox regains focus after losing focus.
- model()->SetCaretVisibility(true);
-}
-
-void OmniboxViewWin::ApplyCaretVisibility() {
- // We hide the caret just before destroying it, since destroying a caret that
- // is in the "solid" phase of its blinking will leave a solid vertical bar.
- // We even hide and destroy the caret if we're going to create it again below.
- // If the caret was already visible on entry to this function, the
- // CreateCaret() call (which first destroys the old caret) might leave a solid
- // vertical bar for the same reason as above. Unconditionally hiding prevents
- // this. The caret could be visible on entry to this function if the
- // underlying edit control had re-created it automatically (see comments in
- // OnPaint()).
- HideCaret();
- // We use DestroyCaret()/CreateCaret() instead of simply HideCaret()/
- // ShowCaret() because HideCaret() is not sticky across paint events, e.g. a
- // window resize will effectively restore caret visibility, regardless of
- // whether HideCaret() was called before. While we do catch and handle these
- // paint events (see OnPaint()), it doesn't seem to be enough to simply call
- // HideCaret() while handling them because of the unpredictability of this
- // Windows API. According to the documentation, it should be a cumulative call
- // e.g. 5 hide calls should be balanced by 5 show calls. We have not found
- // this to be true, which may be explained by the fact that this API is called
- // internally in Windows, as well.
- ::DestroyCaret();
- if (model()->is_caret_visible()) {
- ::CreateCaret(m_hWnd, (HBITMAP) NULL, 1, font_.GetHeight());
- // According to the Windows API documentation, a newly created caret needs
- // ShowCaret to be visible.
- ShowCaret();
- }
-}
-
-void OmniboxViewWin::SetDropHighlightPosition(int position) {
- if (drop_highlight_position_ != position) {
- RepaintDropHighlight(drop_highlight_position_);
- drop_highlight_position_ = position;
- RepaintDropHighlight(drop_highlight_position_);
- }
-}
-
-void OmniboxViewWin::MoveSelectedText(int new_position) {
- const string16 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 OmniboxViewWin::InsertText(int position, const string16& text) {
- DCHECK((position >= 0) && (position <= GetTextLength()));
- ScopedFreeze freeze(this, GetTextObjectModel());
- OnBeforePossibleChange();
- SetSelection(position, position);
- ReplaceSel(text.c_str());
- OnAfterPossibleChange();
-}
-
-void OmniboxViewWin::OnTemporaryTextMaybeChanged(const string16& display_text,
- bool save_original_selection,
- bool notify_text_changed) {
- 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(), false,
- notify_text_changed);
-}
-
-bool OmniboxViewWin::OnInlineAutocompleteTextMaybeChanged(
- const string16& 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());
- 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 OmniboxViewWin::OnRevertTemporaryText() {
- SetSelectionRange(original_selection_);
-}
-
-void OmniboxViewWin::OnBeforePossibleChange() {
- // Record our state.
- text_before_change_ = GetText();
- GetSelection(sel_before_change_);
-}
-
-bool OmniboxViewWin::OnAfterPossibleChange() {
- return OnAfterPossibleChangeInternal(false);
-}
-
-bool OmniboxViewWin::OnAfterPossibleChangeInternal(bool force_text_changed) {
- // 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 != new_sel.cpMax) ||
- (sel_before_change_.cpMin != sel_before_change_.cpMax)) &&
- ((new_sel.cpMin != sel_before_change_.cpMin) ||
- (new_sel.cpMax != sel_before_change_.cpMax));
-
- // See if the text or selection have changed since OnBeforePossibleChange().
- const string16 new_text(GetText());
- const bool text_differs = (new_text != text_before_change_) ||
- force_text_changed;
-
- // 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(
- text_before_change_, new_text, new_sel.cpMin, new_sel.cpMax,
- selection_differs, text_differs, just_deleted_text, !IsImeComposing());
-
- if (selection_differs)
- controller()->OnSelectionBoundsChanged();
-
- if (something_changed && text_differs)
- TextChanged();
-
- if (text_differs) {
- // Note that a TEXT_CHANGED event implies that the cursor/selection
- // probably changed too, so we don't need to send both.
- native_view_host_->GetWidget()->NotifyAccessibilityEvent(
- native_view_host_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true);
- } else if (selection_differs) {
- // Notify assistive technology that the cursor or selection changed.
- native_view_host_->GetWidget()->NotifyAccessibilityEvent(
- native_view_host_,
- ui::AccessibilityTypes::EVENT_SELECTION_CHANGED,
- true);
- } else if (delete_at_end_pressed_) {
- model()->OnChanged();
- }
-
- return something_changed;
-}
-
-void OmniboxViewWin::OnCandidateWindowCountChanged(size_t window_count) {
- ime_candidate_window_open_ = (window_count != 0);
- if (ime_candidate_window_open_) {
- CloseOmniboxPopup();
- } else if (model()->user_input_in_progress()) {
- // UpdatePopup assumes user input is in progress, so only call it if
- // that's the case. Otherwise, autocomplete may run on an empty user
- // text.
- UpdatePopup();
- }
-}
-
-void OmniboxViewWin::OnTextUpdated(const ui::Range& /*composition_range*/) {
- if (ignore_ime_messages_)
- return;
- OnAfterPossibleChangeInternal(true);
- // Call OnBeforePossibleChange function here to get correct diff in next IME
- // update. The Text Services Framework does not provide any notification
- // before entering edit session, therefore we don't have good place to call
- // OnBeforePossibleChange.
- OnBeforePossibleChange();
-}
-
-gfx::NativeView OmniboxViewWin::GetNativeView() const {
- return m_hWnd;
-}
-
-// static
-gfx::NativeView OmniboxViewWin::GetRelativeWindowForNativeView(
- gfx::NativeView edit_native_view) {
- // When an IME is attached to the rich-edit control, retrieve its window
- // handle, and the popup window of OmniboxPopupView will be shown under the
- // IME windows.
- // Otherwise, the popup window will be shown under top-most windows.
- // TODO(hbono): http://b/1111369 if we exclude this popup window from the
- // display area of IME windows, this workaround becomes unnecessary.
- HWND ime_window = ImmGetDefaultIMEWnd(edit_native_view);
- return ime_window ? ime_window : HWND_NOTOPMOST;
-}
-
-gfx::NativeView OmniboxViewWin::GetRelativeWindowForPopup() const {
- return GetRelativeWindowForNativeView(GetNativeView());
-}
-
-void OmniboxViewWin::SetInstantSuggestion(const string16& suggestion) {
- parent_view_->SetInstantSuggestion(suggestion);
-}
-
-int OmniboxViewWin::TextWidth() const {
- return WidthNeededToDisplay(GetText());
-}
-
-string16 OmniboxViewWin::GetInstantSuggestion() const {
- return parent_view_->GetInstantSuggestion();
-}
-
-bool OmniboxViewWin::IsImeComposing() const {
- if (tsf_event_router_)
- return tsf_event_router_->IsImeComposing();
- bool ime_composing = false;
- HIMC context = ImmGetContext(m_hWnd);
- if (context) {
- ime_composing = !!ImmGetCompositionString(context, GCS_COMPSTR, NULL, 0);
- ImmReleaseContext(m_hWnd, context);
- }
- return ime_composing;
-}
-
-int OmniboxViewWin::GetMaxEditWidth(int entry_width) const {
- RECT formatting_rect;
- GetRect(&formatting_rect);
- RECT edit_bounds;
- GetClientRect(&edit_bounds);
- return entry_width - formatting_rect.left -
- (edit_bounds.right - formatting_rect.right);
-}
-
-views::View* OmniboxViewWin::AddToView(views::View* parent) {
- native_view_host_ = new OmniboxViewWrapper(this);
- parent->AddChildView(native_view_host_);
- native_view_host_->set_focus_view(parent);
- native_view_host_->Attach(GetNativeView());
- return native_view_host_;
-}
-
-int OmniboxViewWin::OnPerformDrop(const ui::DropTargetEvent& event) {
- return OnPerformDropImpl(event, false);
-}
-
-gfx::Font OmniboxViewWin::GetFont() {
- return font_;
-}
-
-int OmniboxViewWin::WidthOfTextAfterCursor() {
- CHARRANGE selection;
- GetSelection(selection);
- // See comments in LocationBarView::Layout as to why this uses -1.
- const int start = std::max(0, static_cast<int>(selection.cpMax - 1));
- return WidthNeededToDisplay(GetText().substr(start));
-}
-
-int OmniboxViewWin::OnPerformDropImpl(const ui::DropTargetEvent& event,
- bool in_drag) {
- const ui::OSExchangeData& data = event.data();
-
- if (data.HasURL()) {
- GURL url;
- string16 title;
- if (data.GetURLAndTitle(&url, &title)) {
- string16 text(StripJavascriptSchemas(UTF8ToUTF16(url.spec())));
- OmniboxView::SetUserText(text);
- model()->AcceptInput(CURRENT_TAB, true);
- return CopyOrLinkDragOperation(event.source_operations());
- }
- } else if (data.HasString()) {
- int string_drop_position = drop_highlight_position();
- string16 text;
- if ((string_drop_position != -1 || !in_drag) && data.GetString(&text)) {
- DCHECK(string_drop_position == -1 ||
- ((string_drop_position >= 0) &&
- (string_drop_position <= GetTextLength())));
- if (in_drag) {
- if (event.source_operations()== ui::DragDropTypes::DRAG_MOVE)
- MoveSelectedText(string_drop_position);
- else
- InsertText(string_drop_position, text);
- } else {
- string16 collapsed_text(CollapseWhitespace(text, true));
- if (model()->CanPasteAndGo(collapsed_text))
- model()->PasteAndGo(collapsed_text);
- }
- return CopyOrLinkDragOperation(event.source_operations());
- }
- }
-
- return ui::DragDropTypes::DRAG_NONE;
-}
-
-void OmniboxViewWin::CopyURL() {
- DoCopyURL(toolbar_model()->GetURL(),
- toolbar_model()->GetText(false),
- model()->profile());
-}
-
-bool OmniboxViewWin::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) {
- ui::KeyboardCode key = event.key_code();
- // We don't process ALT + numpad digit as accelerators, they are used for
- // entering special characters. We do translate alt-home.
- if (event.IsAltDown() && (key != ui::VKEY_HOME) &&
- views::NativeTextfieldWin::IsNumPadDigit(key,
- (event.flags() & ui::EF_EXTENDED) != 0))
- return true;
-
- // Skip accelerators for key combinations omnibox wants to crack. This list
- // should be synced with OnKeyDownOnlyWritable() (but for tab which is dealt
- // with above in LocationBarView::SkipDefaultKeyEventProcessing).
- //
- // We cannot return true for all keys because we still need to handle some
- // accelerators (e.g., F5 for reload the page should work even when the
- // Omnibox gets focused).
- switch (key) {
- case ui::VKEY_ESCAPE: {
- ScopedFreeze freeze(this, GetTextObjectModel());
- return model()->OnEscapeKeyPressed();
- }
-
- case ui::VKEY_RETURN:
- return true;
-
- case ui::VKEY_UP:
- case ui::VKEY_DOWN:
- return !event.IsAltDown();
-
- case ui::VKEY_DELETE:
- case ui::VKEY_INSERT:
- return !event.IsAltDown() && event.IsShiftDown() &&
- !event.IsControlDown();
-
- case ui::VKEY_X:
- case ui::VKEY_V:
- return !event.IsAltDown() && event.IsControlDown();
-
- case ui::VKEY_BACK:
- return true;
-
- default:
- return false;
- }
-}
-
-void OmniboxViewWin::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 OmniboxViewWin::IsCommandIdChecked(int command_id) const {
- return false;
-}
-
-bool OmniboxViewWin::IsCommandIdEnabled(int command_id) const {
- switch (command_id) {
- case IDS_UNDO:
- return !!CanUndo();
- case IDC_CUT:
- return !!CanCut();
- case IDC_COPY:
- return !!CanCopy();
- case IDC_COPY_URL:
- return !!CanCopy() &&
- !model()->user_input_in_progress() &&
- toolbar_model()->WouldReplaceSearchURLWithSearchTerms();
- case IDC_PASTE:
- return !!CanPaste();
- case IDS_PASTE_AND_GO:
- return model()->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 OmniboxViewWin::GetAcceleratorForCommandId(
- int command_id,
- ui::Accelerator* accelerator) {
- return parent_view_->GetWidget()->GetAccelerator(command_id, accelerator);
-}
-
-bool OmniboxViewWin::IsItemForCommandIdDynamic(int command_id) const {
- // No need to change the default IDS_PASTE_AND_GO label unless this is a
- // search.
- return command_id == IDS_PASTE_AND_GO;
-}
-
-string16 OmniboxViewWin::GetLabelForCommandId(int command_id) const {
- DCHECK_EQ(IDS_PASTE_AND_GO, command_id);
- return l10n_util::GetStringUTF16(
- model()->IsPasteAndSearch(GetClipboardText()) ?
- IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO);
-}
-
-void OmniboxViewWin::ExecuteCommand(int command_id) {
- ScopedFreeze freeze(this, GetTextObjectModel());
- if (command_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(GetClipboardText());
- return;
- }
-
- OnBeforePossibleChange();
- switch (command_id) {
- case IDS_UNDO:
- Undo();
- break;
-
- case IDC_CUT:
- Cut();
- break;
-
- case IDC_COPY:
- Copy();
- break;
-
- case IDC_COPY_URL:
- CopyURL();
- break;
-
- case IDC_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 OmniboxViewWin::WordBreakProc(LPTSTR edit_text,
- int current_pos,
- int length,
- 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.
-
- // 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, length, WB_LEFTBREAK);
-
- if ((prev_delim == 0) &&
- !WordBreakProc(edit_text, 0, length, 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, length, 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, length, 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, length, 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, length, 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, length, WB_ISDELIMITER))
- return i;
- }
- return length;
- }
-
- NOTREACHED();
- return 0;
-}
-
-// static
-bool OmniboxViewWin::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 OmniboxViewWin::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 OmniboxViewWin::OnContextMenu(HWND window, const CPoint& point) {
- BuildContextMenu();
-
- views::MenuModelAdapter adapter(context_menu_contents_.get());
- context_menu_runner_.reset(new views::MenuRunner(adapter.CreateMenu()));
-
- gfx::Point location(point);
- if (point.x == -1 || point.y == -1) {
- POINT p;
- GetCaretPos(&p);
- MapWindowPoints(HWND_DESKTOP, &p, 1);
- location.SetPoint(p.x, p.y);
- }
-
- ignore_result(context_menu_runner_->RunMenuAt(native_view_host_->GetWidget(),
- NULL, gfx::Rect(location, gfx::Size()), views::MenuItemView::TOPLEFT,
- views::MenuRunner::HAS_MNEMONICS));
-}
-
-void OmniboxViewWin::OnCopy() {
- string16 text(GetSelectedText());
- if (text.empty())
- return;
-
- CHARRANGE sel;
- GURL url;
- bool write_url = false;
- GetSel(sel);
- // GetSel() doesn't preserve selection direction, so sel.cpMin will always be
- // the smaller value.
- model()->AdjustTextForCopy(sel.cpMin, IsSelectAll(), &text, &url, &write_url);
- if (write_url)
- DoCopyURL(url, text, model()->profile());
- else
- DoCopyText(text, model()->profile());
-}
-
-LRESULT OmniboxViewWin::OnCreate(const CREATESTRUCTW* /*create_struct*/) {
- if (base::win::IsTSFAwareRequired()) {
- // Enable TSF support of RichEdit.
- SetEditStyle(SES_USECTF, SES_USECTF);
- }
- if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
- BOOL touch_mode = RegisterTouchWindow(m_hWnd, TWF_WANTPALM);
- DCHECK(touch_mode);
- }
- SetMsgHandled(FALSE);
-
- // When TSF is enabled, OnTextUpdated() may be called without any previous
- // call that would have indicated the start of an editing session. In order
- // to guarantee we've always called OnBeforePossibleChange() before
- // OnAfterPossibleChange(), we therefore call that here. Note that multiple
- // (i.e. unmatched) calls to this function in a row are safe.
- if (base::win::IsTSFAwareRequired())
- OnBeforePossibleChange();
- return 0;
-}
-
-void OmniboxViewWin::OnCut() {
- OnCopy();
-
- // This replace selection will have no effect (even on the undo stack) if the
- // current selection is empty.
- ReplaceSel(L"", true);
-}
-
-LRESULT OmniboxViewWin::OnGetObject(UINT message,
- WPARAM wparam,
- LPARAM lparam) {
- // This is a request for the native accessibility object.
- if (lparam == OBJID_CLIENT) {
- return LresultFromObject(IID_IAccessible, wparam,
- native_view_host_->GetNativeViewAccessible());
- }
- return 0;
-}
-
-LRESULT OmniboxViewWin::OnImeComposition(UINT message,
- WPARAM wparam,
- LPARAM lparam) {
- if (ignore_ime_messages_) {
- // This message was sent while we're in the middle of meddling with the
- // underlying edit control. If we handle it below, OnAfterPossibleChange()
- // can get bogus text for the edit, and rerun autocomplete, destructively
- // modifying the result set that we're in the midst of using. For example,
- // if SetWindowTextAndCaretPos() was called due to the user clicking an
- // entry in the popup, we're in the middle of executing SetSelectedLine(),
- // and changing the results can cause checkfailures.
- return DefWindowProc(message, wparam, lparam);
- }
-
- ScopedFreeze freeze(this, GetTextObjectModel());
- OnBeforePossibleChange();
- LRESULT result = DefWindowProc(message, wparam, lparam);
- // Force an IME composition confirmation operation to trigger the text_changed
- // code in OnAfterPossibleChange(), even if identical contents are confirmed,
- // to make sure the model can update its internal states correctly.
- OnAfterPossibleChangeInternal((lparam & GCS_RESULTSTR) != 0);
- return result;
-}
-
-LRESULT OmniboxViewWin::OnImeEndComposition(UINT message, WPARAM wparam,
- LPARAM lparam) {
- // The edit control auto-clears the selection on WM_IME_ENDCOMPOSITION, which
- // means any inline autocompletion we were showing will no longer be
- // selected, and therefore no longer replaced by further user typing. To
- // avoid this we manually restore the original selection after the edit
- // handles the message.
- CHARRANGE range;
- GetSel(range);
- LRESULT result = DefWindowProc(message, wparam, lparam);
- SetSel(range);
- return result;
-}
-
-LRESULT OmniboxViewWin::OnImeNotify(UINT message,
- WPARAM wparam,
- LPARAM lparam) {
- // Close the popup when the IME composition window is open, so they don't
- // overlap.
- switch (wparam) {
- case IMN_OPENCANDIDATE:
- ime_candidate_window_open_ = true;
- CloseOmniboxPopup();
- break;
- case IMN_CLOSECANDIDATE:
- ime_candidate_window_open_ = false;
-
- // UpdatePopup assumes user input is in progress, so only call it if
- // that's the case. Otherwise, autocomplete may run on an empty user
- // text. For example, Baidu Japanese IME sends IMN_CLOSECANDIDATE when
- // composition mode is entered, but the user may not have input anything
- // yet.
- if (model()->user_input_in_progress())
- UpdatePopup();
-
- break;
- default:
- break;
- }
- return DefWindowProc(message, wparam, lparam);
-}
-
-LRESULT OmniboxViewWin::OnTouchEvent(UINT message,
- WPARAM wparam,
- LPARAM lparam) {
- // There is a bug in Windows 8 where in the generated mouse messages
- // after touch go to the window which previously had focus. This means that
- // if a user taps the omnibox to give it focus, we don't get the simulated
- // WM_LBUTTONDOWN, and thus don't properly select all the text. To ensure
- // that we get this message, we capture the mouse when the user is doing a
- // single-point tap on an unfocused model.
- if ((wparam == 1) && !model()->has_focus()) {
- TOUCHINPUT point = {0};
- if (GetTouchInputInfo(reinterpret_cast<HTOUCHINPUT>(lparam), 1,
- &point, sizeof(TOUCHINPUT))) {
- if (point.dwFlags & TOUCHEVENTF_DOWN)
- SetCapture();
- else if (point.dwFlags & TOUCHEVENTF_UP)
- ReleaseCapture();
- }
- }
- SetMsgHandled(false);
- return 0;
-}
-
-void OmniboxViewWin::OnKeyDown(TCHAR key,
- UINT repeat_count,
- UINT flags) {
- delete_at_end_pressed_ = false;
-
- if (OnKeyDownAllModes(key, repeat_count, flags))
- return;
-
- // Make sure that we handle system key events like Alt-F4.
- if (popup_window_mode_) {
- DefWindowProc(GetCurrentMessage()->message, key, MAKELPARAM(repeat_count,
- flags));
- return;
- }
-
- if (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 OmniboxViewWin::OnKeyUp(TCHAR key,
- UINT repeat_count,
- UINT flags) {
- if (key == VK_CONTROL)
- model()->OnControlKeyChanged(false);
-
- // On systems with RTL input languages, ctrl+shift toggles the reading order
- // (depending on which shift key is pressed). But by default the CRichEditCtrl
- // only changes the current reading order, and as soon as the user deletes all
- // the text, or we call SetWindowText(), it reverts to the "default" order.
- // To work around this, if the user hits ctrl+shift, we pass it to
- // DefWindowProc() while the edit is empty, which toggles the default reading
- // order; then we restore the user's input.
- if (!(flags & KF_ALTDOWN) &&
- (((key == VK_CONTROL) && (GetKeyState(VK_SHIFT) < 0)) ||
- ((key == VK_SHIFT) && (GetKeyState(VK_CONTROL) < 0)))) {
- ScopedFreeze freeze(this, GetTextObjectModel());
-
- string16 saved_text(GetText());
- CHARRANGE saved_sel;
- GetSelection(saved_sel);
-
- SetWindowText(L"");
-
- DefWindowProc(WM_KEYUP, key, MAKELPARAM(repeat_count, flags));
-
- SetWindowText(saved_text.c_str());
- SetSelectionRange(saved_sel);
- return;
- }
-
- SetMsgHandled(false);
-}
-
-void OmniboxViewWin::OnKillFocus(HWND focus_wnd) {
- if (m_hWnd == focus_wnd) {
- // Focus isn't actually leaving.
- SetMsgHandled(false);
- return;
- }
-
- // This must be invoked before ClosePopup.
- model()->OnWillKillFocus(focus_wnd);
-
- // Close the popup.
- CloseOmniboxPopup();
-
- // 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);
-
- // 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.
- //
- // Crazy hack: If we just do PlaceCaretAt(0), and the beginning of the text is
- // currently scrolled out of view, we can wind up with a blinking cursor in
- // the toolbar at the current X coordinate of the beginning of the text. By
- // first doing a reverse-select-all to scroll the beginning of the text into
- // view, we work around this CRichEditCtrl bug.
- SelectAll(true);
- PlaceCaretAt(0);
-
- if (tsf_event_router_)
- tsf_event_router_->SetManager(NULL);
-}
-
-void OmniboxViewWin::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));
-
- // Rich Edit 4.1 doesn't select the last word when the user double clicks
- // past the text. Do it manually.
- CHARRANGE selection;
- GetSelection(selection);
- // The default window proc for Rich Edit 4.1 seems to select the CHARRANGE
- // {text_length, text_length + 1} after a double click past the text.
- int length = GetTextLength();
- if (selection.cpMin == length && selection.cpMax == length + 1) {
- string16 text = GetText();
- int word_break = WordBreakProc(&text[0], length, length, WB_LEFT);
- selection.cpMin = word_break;
- selection.cpMax = length;
- SetSelectionRange(selection);
- }
-
- OnAfterPossibleChange();
-
- gaining_focus_.reset(); // See NOTE in OnMouseActivate().
-}
-
-void OmniboxViewWin::OnLButtonDown(UINT keys, const CPoint& point) {
- TrackMousePosition(kLeft, point);
- if (gaining_focus_.get()) {
- // 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_ &&
- views::NativeTextfieldWin::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 OmniboxViewWin::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));
-
- SelectAllIfNecessary(kLeft, point);
-
- tracking_click_[kLeft] = false;
-
- possible_drag_ = false;
-}
-
-void OmniboxViewWin::OnMButtonDblClk(UINT /*keys*/, const CPoint& /*point*/) {
- gaining_focus_.reset(); // See NOTE in OnMouseActivate().
-
- // By default, the edit responds to middle-clicks by capturing the mouse and
- // ignoring all subsequent events until it receives another click (of any of
- // the left, middle, or right buttons). This bizarre behavior is not only
- // useless but can cause the UI to appear unresponsive if a user accidentally
- // middle-clicks the edit (instead of a tab above it), so we purposefully eat
- // this message (instead of calling SetMsgHandled(false)) to avoid triggering
- // this.
-}
-
-void OmniboxViewWin::OnMButtonDown(UINT /*keys*/, const CPoint& /*point*/) {
- tracking_double_click_ = false;
-
- // See note in OnMButtonDblClk above.
-}
-
-void OmniboxViewWin::OnMButtonUp(UINT /*keys*/, const CPoint& /*point*/) {
- possible_drag_ = false;
-
- // See note in OnMButtonDblClk above.
-}
-
-LRESULT OmniboxViewWin::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 click. We have to do this here rather
- // than in OnXButtonDown() since in many scenarios OnSetFocus() will be
- // reached before OnXButtonDown(), 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() ||
- (model()->focus_state() == OMNIBOX_FOCUS_INVISIBLE)) &&
- ((mouse_message == WM_LBUTTONDOWN || mouse_message == WM_RBUTTONDOWN)) &&
- (result == MA_ACTIVATE)) {
- if (gaining_focus_) {
- // On Windows 8 in metro mode, we get two WM_MOUSEACTIVATE messages when
- // we click on the omnibox with the mouse.
- DCHECK(win8::IsSingleWindowMetroMode());
- return result;
- }
- gaining_focus_.reset(new ScopedFreeze(this, GetTextObjectModel()));
-
- // Restore caret visibility whenever the user clicks in the omnibox in a
- // way that would give it focus. We must handle this case separately here
- // because if the omnibox currently has invisible focus, the mouse event
- // won't trigger either SetFocus() or OmniboxEditModel::OnSetFocus().
- model()->SetCaretVisibility(true);
-
- // NOTE: Despite |mouse_message| being WM_XBUTTONDOWN here, we're not
- // guaranteed to call OnXButtonDown() later! Specifically, if this is the
- // second click of a double click, we'll reach here but later call
- // OnXButtonDblClk(). 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 OmniboxViewWin::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_[kLeft] && !IsDrag(click_point_[kLeft], point))
- return;
-
- tracking_click_[kLeft] = 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 OmniboxViewWin::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;
-
- // If textfield has focus, reaffirm its caret visibility (without focus, a new
- // caret could be created and confuse the user as to where the focus is). This
- // needs to be called regardless of the current visibility of the caret. This
- // is because the underlying edit control will automatically re-create the
- // caret when it receives certain events that trigger repaints, e.g. window
- // resize events. This also checks for the existence of selected text, in
- // which case there shouldn't be a recreated caret since this would create
- // both a highlight and a blinking caret.
- if (model()->has_focus()) {
- CHARRANGE sel;
- GetSel(sel);
- if (sel.cpMin == sel.cpMax)
- ApplyCaretVisibility();
- }
-}
-
-void OmniboxViewWin::OnPaste() {
- // Replace the selection if we have something to paste.
- const string16 text(GetClipboardText());
- if (!text.empty()) {
- // Record this paste, so we can do different behavior.
- model()->on_paste();
- // Force a Paste operation to trigger the text_changed code in
- // OnAfterPossibleChange(), even if identical contents are pasted into the
- // text box.
- text_before_change_.clear();
- ReplaceSel(text.c_str(), true);
- }
-}
-
-void OmniboxViewWin::OnRButtonDblClk(UINT /*keys*/, const CPoint& /*point*/) {
- gaining_focus_.reset(); // See NOTE in OnMouseActivate().
- SetMsgHandled(false);
-}
-
-void OmniboxViewWin::OnRButtonDown(UINT /*keys*/, const CPoint& point) {
- TrackMousePosition(kRight, point);
- tracking_double_click_ = false;
- possible_drag_ = false;
- gaining_focus_.reset();
- SetMsgHandled(false);
-}
-
-void OmniboxViewWin::OnRButtonUp(UINT /*keys*/, const CPoint& point) {
- SelectAllIfNecessary(kRight, point);
- tracking_click_[kRight] = false;
- SetMsgHandled(false);
-}
-
-void OmniboxViewWin::OnSetFocus(HWND focus_wnd) {
- views::FocusManager* focus_manager = parent_view_->GetFocusManager();
- if (focus_manager) {
- // Notify the FocusManager that the focused view is now the location bar
- // (our parent view).
- focus_manager->SetFocusedView(parent_view_);
- } else {
- NOTREACHED();
- }
-
- model()->OnSetFocus(GetKeyState(VK_CONTROL) < 0);
-
- // 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;
- }
-
- if (!tsf_event_router_) {
- SetMsgHandled(false);
- } else {
- DefWindowProc();
- // Document manager created by RichEdit can be obtained only after
- // WM_SETFOCUS event is handled.
- tsf_event_router_->SetManager(
- ui::TSFBridge::GetInstance()->GetThreadManager());
- SetMsgHandled(true);
- }
-}
-
-LRESULT OmniboxViewWin::OnSetText(const wchar_t* text) {
- // Ignore all IME messages while we process this WM_SETTEXT message.
- // When SetWindowText() is called while an IME is composing text, the IME
- // calls SendMessage() to send a WM_IME_COMPOSITION message. When we receive
- // this WM_IME_COMPOSITION message, we update the omnibox and may call
- // SetWindowText() again. To stop this recursive message-handler call, we
- // stop updating the omnibox while we process a WM_SETTEXT message.
- // We wouldn't need to do this update anyway, because either we're in the
- // middle of updating the omnibox already or the caller of SetWindowText()
- // is going to update the omnibox next.
- base::AutoReset<bool> auto_reset_ignore_ime_messages(
- &ignore_ime_messages_, true);
- return DefWindowProc(WM_SETTEXT, 0, reinterpret_cast<LPARAM>(text));
-}
-
-void OmniboxViewWin::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 OmniboxViewWin::OnWindowPosChanging(WINDOWPOS* window_pos) {
- if (force_hidden_)
- window_pos->flags &= ~SWP_SHOWWINDOW;
- SetMsgHandled(true);
-}
-
-BOOL OmniboxViewWin::OnMouseWheel(UINT flags, short delta, CPoint point) {
- // Forward the mouse-wheel message to the window under the mouse.
- if (!ui::RerouteMouseWheel(m_hWnd, MAKEWPARAM(flags, delta),
- MAKELPARAM(point.x, point.y)))
- SetMsgHandled(false);
- return 0;
-}
-
-void OmniboxViewWin::HandleKeystroke(UINT message,
- TCHAR key,
- UINT repeat_count,
- UINT flags) {
- ScopedFreeze freeze(this, GetTextObjectModel());
- OnBeforePossibleChange();
-
- if (key == ui::VKEY_HOME || key == ui::VKEY_END) {
- // DefWindowProc() might reset the keyboard layout when it receives a
- // keydown event for VKEY_HOME or VKEY_END. When the window was created
- // with WS_EX_LAYOUTRTL and the current keyboard layout is not a RTL one,
- // if the input text is pure LTR text, the layout changes to the first RTL
- // keyboard layout in keyboard layout queue; if the input text is
- // bidirectional text, the layout changes to the keyboard layout of the
- // first RTL character in input text. When the window was created without
- // WS_EX_LAYOUTRTL and the current keyboard layout is not a LTR one, if the
- // input text is pure RTL text, the layout changes to English; if the input
- // text is bidirectional text, the layout changes to the keyboard layout of
- // the first LTR character in input text. Such keyboard layout change
- // behavior is surprising and inconsistent with keyboard behavior
- // elsewhere, so reset the layout in this case.
- HKL layout = GetKeyboardLayout(0);
- DefWindowProc(message, key, MAKELPARAM(repeat_count, flags));
- ActivateKeyboardLayout(layout, KLF_REORDER);
- } else {
- DefWindowProc(message, key, MAKELPARAM(repeat_count, flags));
- }
-
- // CRichEditCtrl automatically turns on IMF_AUTOKEYBOARD when the user
- // inputs an RTL character, making it difficult for the user to control
- // what language is set as they type. Force this off to make the edit's
- // behavior more stable.
- const int lang_options = SendMessage(EM_GETLANGOPTIONS, 0, 0);
- if (lang_options & IMF_AUTOKEYBOARD)
- SendMessage(EM_SETLANGOPTIONS, 0, lang_options & ~IMF_AUTOKEYBOARD);
-
- OnAfterPossibleChange();
-}
-
-bool OmniboxViewWin::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.
-
- // If adding a new key that could possibly be an accelerator then you'll need
- // to update LocationBarView::SkipDefaultKeyEventProcessing() as well.
- int count = repeat_count;
- switch (key) {
- case VK_RIGHT:
- // TODO(sky): figure out RTL.
- if (base::i18n::IsRTL())
- return false;
- {
- CHARRANGE selection;
- GetSel(selection);
- return (selection.cpMin == selection.cpMax) &&
- (selection.cpMin == GetTextLength()) &&
- model()->CommitSuggestedText(true);
- }
-
- case VK_RETURN:
- model()->AcceptInput((flags & KF_ALTDOWN) ?
- NEW_FOREGROUND_TAB : CURRENT_TAB, false);
- return true;
-
- case VK_PRIOR:
- case VK_NEXT:
- count = model()->result().size();
- // FALL THROUGH
- case VK_UP:
- case VK_DOWN:
- // Ignore alt + numpad, but treat alt + (non-numpad) like (non-numpad).
- if ((flags & KF_ALTDOWN) && !(flags & KF_EXTENDED))
- return false;
-
- model()->OnUpOrDownKeyPressed(((key == VK_PRIOR) || (key == VK_UP)) ?
- -count : 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. Also note that we bring
- // up 'clear browsing data' on control-shift-delete.
- // Copy: Ctrl-Insert and Ctrl-c is treated as copy. Shift-Ctrl-c is not.
- // (This is handled in OnKeyDownAllModes().)
- // Paste: Shift-Insert and Ctrl-v are treated 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)
- return false;
- if (GetKeyState(VK_SHIFT) >= 0) {
- if (GetKeyState(VK_CONTROL) >= 0) {
- CHARRANGE selection;
- GetSel(selection);
- delete_at_end_pressed_ = ((selection.cpMin == selection.cpMax) &&
- (selection.cpMin == GetTextLength()));
- }
- 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 (model()->popup_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.
- model()->popup_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:
- // Ignore insert by itself, so we don't turn overtype mode on/off.
- if (!(flags & KF_ALTDOWN) && (GetKeyState(VK_SHIFT) >= 0) &&
- (GetKeyState(VK_CONTROL) >= 0))
- return true;
- // FALL THROUGH
- 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: {
- const bool shift_pressed = GetKeyState(VK_SHIFT) < 0;
- if (model()->is_keyword_hint() && !shift_pressed) {
- // Accept the keyword.
- ScopedFreeze freeze(this, GetTextObjectModel());
- model()->AcceptKeyword();
- } else if (shift_pressed &&
- model()->popup_model()->selected_line_state() ==
- OmniboxPopupModel::KEYWORD) {
- model()->ClearKeyword(GetText());
- } else {
- model()->OnUpOrDownKeyPressed(shift_pressed ? -count : count);
- }
- return true;
- }
-
- case 0xbb: // Ctrl-'='. Triggers subscripting (even in plain text mode).
- // We don't use VK_OEM_PLUS in case the macro isn't defined.
- // (e.g., we don't have this symbol in embeded environment).
- return true;
-
- default:
- return false;
- }
-}
-
-bool OmniboxViewWin::OnKeyDownAllModes(TCHAR key,
- UINT repeat_count,
- UINT flags) {
- // See KF_ALTDOWN comment atop OnKeyDownOnlyWritable().
-
- switch (key) {
- case VK_CONTROL:
- model()->OnControlKeyChanged(true);
- return false;
-
- case VK_INSERT:
- case 'C':
- // See more detailed comments in OnKeyDownOnlyWritable().
- if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0))
- return false;
- if (GetKeyState(VK_SHIFT) >= 0)
- Copy();
- return true;
-
- default:
- return false;
- }
-}
-
-void OmniboxViewWin::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;
- base::win::ScopedComPtr<ITextSelection> selection;
- const HRESULT hr = text_object_model->GetSelection(selection.Receive());
- DCHECK_EQ(S_OK, hr);
- long flags;
- selection->GetFlags(&flags);
- if (flags & tomSelStartActive)
- std::swap(sel.cpMin, sel.cpMax);
-}
-
-string16 OmniboxViewWin::GetSelectedText() const {
- CHARRANGE sel;
- GetSel(sel);
- string16 str;
- if (sel.cpMin != sel.cpMax)
- GetSelText(WriteInto(&str, sel.cpMax - sel.cpMin + 1));
- return str;
-}
-
-void OmniboxViewWin::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;
- base::win::ScopedComPtr<ITextSelection> selection;
- const HRESULT hr = text_object_model->GetSelection(selection.Receive());
- DCHECK_EQ(S_OK, hr);
- selection->SetFlags(tomSelStartActive);
-}
-
-void OmniboxViewWin::PlaceCaretAt(string16::size_type pos) {
- SetSelection(static_cast<LONG>(pos), static_cast<LONG>(pos));
-}
-
-bool OmniboxViewWin::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 OmniboxViewWin::ClipXCoordToVisibleText(LONG x,
- bool is_triple_click) const {
- // Clip the X coordinate to the left edge of the text. Careful:
- // PosFromChar(0) may return a negative X coordinate if the beginning of the
- // text has scrolled off the edit, so don't go past the clip rect's edge.
- PARAFORMAT2 pf2;
- GetParaFormat(pf2);
- // Calculation of the clipped coordinate is more complicated if the paragraph
- // layout is RTL layout, or if there is RTL characters inside the LTR layout
- // paragraph.
- const bool ltr_text_in_ltr_layout = !(pf2.wEffects & PFE_RTLPARA) &&
- !base::i18n::StringContainsStrongRTLChars(GetText());
- const int length = GetTextLength();
- RECT r;
- GetRect(&r);
- // The values returned by PosFromChar() seem to refer always
- // to the left edge of the character's bounding box.
- const LONG first_position_x = PosFromChar(0).x;
- LONG min_x = first_position_x;
- if (!ltr_text_in_ltr_layout) {
- for (int i = 1; i < length; ++i)
- min_x = std::min(min_x, PosFromChar(i).x);
- }
- const LONG left_bound = std::max(r.left, min_x);
- // PosFromChar(length) is a phantom character past the end of the text. It is
- // not necessarily a right bound; in RTL controls it may be a left bound. So
- // treat it as a right bound only if it is to the right of the first
- // character.
- LONG right_bound = r.right;
- LONG end_position_x = PosFromChar(length).x;
- if (end_position_x >= first_position_x) {
- right_bound = std::min(right_bound, end_position_x); // LTR case.
- }
- // For trailing characters that are 2 pixels wide or less (like "l" in some
- // fonts), we have a problem:
- // * Clicks on any pixel within the character will place the cursor before
- // the character.
- // * Clicks on the pixel just after the character will not allow triple-
- // click to work properly (true for any last character width).
- // So, we move to the last pixel of the character when this is a
- // triple-click, and moving to one past the last pixel in all other
- // scenarios. This way, all clicks that can move the cursor will place it at
- // the end of the text, but triple-click will still work.
- if (x < left_bound) {
- return (is_triple_click && ltr_text_in_ltr_layout) ? left_bound - 1 :
- left_bound;
- }
- if ((length == 0) || (x < right_bound))
- return x;
- return is_triple_click ? (right_bound - 1) : right_bound;
-}
-
-int OmniboxViewWin::GetOmniboxTextLength() const {
- return static_cast<int>(GetTextLength());
-}
-
-void OmniboxViewWin::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::Component scheme, host;
- AutocompleteInput::ParseForEmphasizeComponents(
- GetText(), model()->GetDesiredTLD(), &scheme, &host);
- const bool emphasize = model()->CurrentTextIsURL() && (host.len > 0);
-
- // Set the baseline emphasis.
- CHARFORMAT cf = {0};
- cf.dwMask = CFM_COLOR;
- // If we're going to emphasize parts of the text, then the baseline state
- // should be "de-emphasized". If not, then everything should be rendered in
- // the standard text color.
- cf.crTextColor = skia::SkColorToCOLORREF(parent_view_->GetColor(
- security_level_,
- emphasize ? LocationBarView::DEEMPHASIZED_TEXT : LocationBarView::TEXT));
- // NOTE: Don't use SetDefaultCharFormat() instead of the below; that sets the
- // format that will get applied to text added in the future, not to text
- // already in the edit.
- SelectAll(false);
- SetSelectionCharFormat(cf);
-
- if (emphasize) {
- // We've found a host name, give it more emphasis.
- cf.crTextColor = skia::SkColorToCOLORREF(parent_view_->GetColor(
- security_level_, LocationBarView::TEXT));
- SetSelection(host.begin, host.end());
- SetSelectionCharFormat(cf);
- }
-
- // Emphasize the scheme for security UI display purposes (if necessary).
- insecure_scheme_component_.reset();
- if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() &&
- scheme.is_nonempty() && (security_level_ != ToolbarModel::NONE)) {
- if (security_level_ == ToolbarModel::SECURITY_ERROR) {
- insecure_scheme_component_.begin = scheme.begin;
- insecure_scheme_component_.len = scheme.len;
- }
- cf.crTextColor = skia::SkColorToCOLORREF(parent_view_->GetColor(
- security_level_, LocationBarView::SECURITY_TEXT));
- SetSelection(scheme.begin, scheme.end());
- SetSelectionCharFormat(cf);
- }
-
- // Restore the selection.
- SetSelectionRange(saved_sel);
-}
-
-void OmniboxViewWin::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 OmniboxViewWin::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_.GetBaseline() - font_x_height_ -
- kAdditionalSpaceOutsideFont,
- PosFromChar(insecure_scheme_component_.end()).x,
- font_top + font_.GetBaseline() +
- 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.
- gfx::Canvas canvas(gfx::Size(scheme_rect.Width(), scheme_rect.Height()),
- ui::SCALE_FACTOR_100P, false);
- SkCanvas* sk_canvas = canvas.sk_canvas();
- sk_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.
- sk_canvas->save();
- if (selection_rect.isEmpty() ||
- sk_canvas->clipRect(selection_rect, SkRegion::kDifference_Op)) {
- paint.setColor(parent_view_->GetColor(security_level_,
- LocationBarView::SECURITY_TEXT));
- sk_canvas->drawLine(start_point.fX, start_point.fY,
- end_point.fX, end_point.fY, paint);
- }
- sk_canvas->restore();
-
- // Draw the selected portion of the stroke.
- if (!selection_rect.isEmpty() && sk_canvas->clipRect(selection_rect)) {
- paint.setColor(parent_view_->GetColor(security_level_,
- LocationBarView::SELECTED_TEXT));
- sk_canvas->drawLine(start_point.fX, start_point.fY,
- end_point.fX, end_point.fY, paint);
- }
-
- // Now copy what we drew to the target HDC.
- skia::DrawToNativeContext(sk_canvas, 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 OmniboxViewWin::DrawDropHighlight(HDC hdc,
- const CRect& client_rect,
- const CRect& paint_clip_rect) {
- DCHECK_NE(-1, drop_highlight_position_);
-
- 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_.GetHeight());
-
- // 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 OmniboxViewWin::TextChanged() {
- ScopedFreeze freeze(this, GetTextObjectModel());
- OmniboxView::TextChanged();
-}
-
-ITextDocument* OmniboxViewWin::GetTextObjectModel() const {
- if (!text_object_model_) {
- // This is lazily initialized, instead of being initialized in the
- // constructor, in order to avoid hurting startup performance.
- base::win::ScopedComPtr<IRichEditOle, NULL> ole_interface;
- ole_interface.Attach(GetOleInterface());
- if (ole_interface) {
- ole_interface.QueryInterface(
- __uuidof(ITextDocument),
- reinterpret_cast<void**>(&text_object_model_));
- }
- }
- return text_object_model_;
-}
-
-void OmniboxViewWin::StartDragIfNecessary(const CPoint& point) {
- if (initiated_drag_ || !IsDrag(click_point_[kLeft], point))
- return;
-
- ui::OSExchangeData data;
-
- 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(click_point_[kLeft].x, click_point_[kLeft].y));
- SetSelectionRange(sel);
- }
-
- const string16 start_text(GetText());
- string16 text_to_write(GetSelectedText());
- GURL url;
- bool write_url;
- const bool is_all_selected = IsSelectAllForRange(sel);
-
- // |sel| was set by GetSelection(), which preserves selection direction, so
- // sel.cpMin may not be the smaller value.
- model()->AdjustTextForCopy(std::min(sel.cpMin, sel.cpMax), is_all_selected,
- &text_to_write, &url, &write_url);
-
- if (write_url) {
- string16 title;
- gfx::Image favicon;
- if (is_all_selected)
- model()->GetDataForURLExport(&url, &title, &favicon);
- button_drag_utils::SetURLAndDragImage(url, title, favicon.AsImageSkia(),
- &data, native_view_host_->GetWidget());
- supported_modes |= DROPEFFECT_LINK;
- content::RecordAction(UserMetricsAction("Omnibox_DragURL"));
- } else {
- supported_modes |= DROPEFFECT_MOVE;
- content::RecordAction(UserMetricsAction("Omnibox_DragString"));
- }
-
- data.SetString(text_to_write);
-
- scoped_refptr<ui::DragSource> drag_source(new ui::DragSource);
- DWORD dropped_mode;
- base::AutoReset<bool> auto_reset_in_drag(&in_drag_, true);
- if (DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(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)));
- }
-
- initiated_drag_ = true;
- tracking_click_[kLeft] = false;
-}
-
-void OmniboxViewWin::OnPossibleDrag(const CPoint& point) {
- if (possible_drag_)
- return;
-
- click_point_[kLeft] = 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 OmniboxViewWin::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_.GetHeight() + font_y_adjustment_};
- InvalidateRect(&highlight_bounds, false);
- }
-}
-
-void OmniboxViewWin::BuildContextMenu() {
- if (context_menu_contents_.get())
- return;
-
- context_menu_contents_.reset(new ui::SimpleMenuModel(this));
- // Set up context menu.
- if (popup_window_mode_) {
- context_menu_contents_->AddItemWithStringId(IDC_COPY, IDS_COPY);
- } else {
- context_menu_contents_->AddItemWithStringId(IDS_UNDO, IDS_UNDO);
- context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR);
- context_menu_contents_->AddItemWithStringId(IDC_CUT, IDS_CUT);
- context_menu_contents_->AddItemWithStringId(IDC_COPY, IDS_COPY);
- if (chrome::search::IsQueryExtractionEnabled())
- context_menu_contents_->AddItemWithStringId(IDC_COPY_URL, IDS_COPY_URL);
- context_menu_contents_->AddItemWithStringId(IDC_PASTE, IDS_PASTE);
- // GetContextualLabel() will override this next label with the
- // IDS_PASTE_AND_SEARCH label as needed.
- context_menu_contents_->AddItemWithStringId(IDS_PASTE_AND_GO,
- IDS_PASTE_AND_GO);
- context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR);
- context_menu_contents_->AddItemWithStringId(IDS_SELECT_ALL, IDS_SELECT_ALL);
- context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR);
- context_menu_contents_->AddItemWithStringId(IDS_EDIT_SEARCH_ENGINES,
- IDS_EDIT_SEARCH_ENGINES);
- }
-}
-
-void OmniboxViewWin::SelectAllIfNecessary(MouseButton button,
- const CPoint& point) {
- // When the user has clicked and released to give us focus, select all.
- if (tracking_click_[button] &&
- !IsDrag(click_point_[button], 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;
- }
-}
-
-void OmniboxViewWin::TrackMousePosition(MouseButton button,
- 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_[button] = true;
- click_point_[button] = point;
- }
-}
-
-int OmniboxViewWin::GetHorizontalMargin() const {
- RECT rect;
- GetRect(&rect);
- RECT client_rect;
- GetClientRect(&client_rect);
- return (rect.left - client_rect.left) + (client_rect.right - rect.right);
-}
-
-int OmniboxViewWin::WidthNeededToDisplay(const string16& text) const {
- // Use font_.GetStringWidth() instead of
- // PosFromChar(location_entry_->GetTextLength()) because PosFromChar() is
- // apparently buggy. In both LTR UI and RTL UI with left-to-right layout,
- // PosFromChar(i) might return 0 when i is greater than 1.
- return font_.GetStringWidth(text) + GetHorizontalMargin();
-}
+// Copyright 2012 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/ui/views/omnibox/omnibox_view_win.h"
+
+#include <algorithm>
+#include <locale>
+#include <string>
+
+#include <richedit.h>
+#include <textserv.h>
+
+#include "base/auto_reset.h"
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/i18n/rtl.h"
+#include "base/lazy_instance.h"
+#include "base/memory/ref_counted.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "base/win/iat_patch_function.h"
+#include "base/win/metro.h"
+#include "base/win/scoped_hdc.h"
+#include "base/win/scoped_select_object.h"
+#include "base/win/windows_version.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/autocomplete/autocomplete_input.h"
+#include "chrome/browser/autocomplete/autocomplete_match.h"
+#include "chrome/browser/autocomplete/keyword_provider.h"
+#include "chrome/browser/bookmarks/bookmark_node_data.h"
+#include "chrome/browser/command_updater.h"
+#include "chrome/browser/instant/search.h"
+#include "chrome/browser/net/url_fixer_upper.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/omnibox/omnibox_edit_controller.h"
+#include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
+#include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
+#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
+#include "chrome/browser/ui/views/missing_system_file_dialog_win.h"
+#include "chrome/browser/ui/views/omnibox/omnibox_view_views.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/browser/web_contents.h"
+#include "googleurl/src/url_util.h"
+#include "grit/generated_resources.h"
+#include "net/base/escape.h"
+#include "skia/ext/skia_utils_win.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/drag_source.h"
+#include "ui/base/dragdrop/drop_target.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/base/dragdrop/os_exchange_data_provider_win.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/ime/win/tsf_bridge.h"
+#include "ui/base/ime/win/tsf_event_router.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/l10n_util_win.h"
+#include "ui/base/win/mouse_wheel_util.h"
+#include "ui/base/win/touch_input.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/button_drag_utils.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+#include "ui/views/controls/menu/menu_model_adapter.h"
+#include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/controls/textfield/native_textfield_win.h"
+#include "ui/views/widget/widget.h"
+#include "win8/util/win8_util.h"
+
+#pragma comment(lib, "oleacc.lib") // Needed for accessibility support.
+
+using content::UserMetricsAction;
+using content::WebContents;
+
+namespace {
+
+const char kAutocompleteEditStateKey[] = "AutocompleteEditState";
+
+// msftedit.dll is RichEdit ver 4.1.
+// This version is available from WinXP SP1 and has TSF support.
+const wchar_t* kRichEditDLLName = L"msftedit.dll";
+
+// 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;
+}
+
+// A helper method for determining a valid drag operation given the allowed
+// operation. We prefer copy over link.
+int CopyOrLinkDragOperation(int drag_operation) {
+ if (drag_operation & ui::DragDropTypes::DRAG_COPY)
+ return ui::DragDropTypes::DRAG_COPY;
+ if (drag_operation & ui::DragDropTypes::DRAG_LINK)
+ return ui::DragDropTypes::DRAG_LINK;
+ return ui::DragDropTypes::DRAG_NONE;
+}
+
+// The AutocompleteEditState struct contains enough information about the
+// OmniboxEditModel and OmniboxViewWin 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 : public base::SupportsUserData::Data {
+ AutocompleteEditState(const OmniboxEditModel::State& model_state,
+ const OmniboxViewWin::State& view_state)
+ : model_state(model_state),
+ view_state(view_state) {
+ }
+ virtual ~AutocompleteEditState() {}
+
+ const OmniboxEditModel::State model_state;
+ const OmniboxViewWin::State view_state;
+};
+
+// Returns true if the current point is far enough from the origin that it
+// would be considered a drag.
+bool IsDrag(const POINT& origin, const POINT& current) {
+ return views::View::ExceededDragThreshold(
+ gfx::Point(current) - gfx::Point(origin));
+}
+
+// Copies |selected_text| as text to the primary clipboard.
+void DoCopyText(const string16& selected_text, Profile* profile) {
+ ui::ScopedClipboardWriter scw(ui::Clipboard::GetForCurrentThread(),
+ ui::Clipboard::BUFFER_STANDARD,
+ content::BrowserContext::
+ GetMarkerForOffTheRecordContext(profile));
+ scw.WriteText(selected_text);
+}
+
+// Writes |url| and |text| to the clipboard as a well-formed URL.
+void DoCopyURL(const GURL& url, const string16& text, Profile* profile) {
+ BookmarkNodeData data;
+ data.ReadFromTuple(url, text);
+ data.WriteToClipboard(profile);
+}
+
+} // namespace
+
+// EditDropTarget is the IDropTarget implementation installed on
+// OmniboxViewWin. 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 OmniboxViewWin::EditDropTarget : public ui::DropTarget {
+ public:
+ explicit EditDropTarget(OmniboxViewWin* 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.
+ OmniboxViewWin* 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_COPY_AND_ASSIGN(EditDropTarget);
+};
+
+OmniboxViewWin::EditDropTarget::EditDropTarget(OmniboxViewWin* edit)
+ : ui::DropTarget(edit->m_hWnd),
+ edit_(edit),
+ drag_has_url_(false),
+ drag_has_string_(false) {
+}
+
+DWORD OmniboxViewWin::EditDropTarget::OnDragEnter(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect) {
+ ui::OSExchangeData os_data(new ui::OSExchangeDataProviderWin(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 OmniboxViewWin::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 OmniboxViewWin::EditDropTarget::OnDragLeave(IDataObject* data_object) {
+ ResetDropHighlights();
+}
+
+DWORD OmniboxViewWin::EditDropTarget::OnDrop(IDataObject* data_object,
+ DWORD key_state,
+ POINT cursor_position,
+ DWORD effect) {
+ effect = OnDragOver(data_object, key_state, cursor_position, effect);
+
+ ui::OSExchangeData os_data(new ui::OSExchangeDataProviderWin(data_object));
+ gfx::Point point(cursor_position.x, cursor_position.y);
+ ui::DropTargetEvent event(
+ os_data, point, point,
+ ui::DragDropTypes::DropEffectToDragOperation(effect));
+
+ int drag_operation = edit_->OnPerformDropImpl(event, edit_->in_drag());
+
+ if (!drag_has_url_)
+ ResetDropHighlights();
+
+ return ui::DragDropTypes::DragOperationToDropEffect(drag_operation);
+}
+
+void OmniboxViewWin::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 OmniboxViewWin::EditDropTarget::ResetDropHighlights() {
+ if (drag_has_string_)
+ edit_->SetDropHighlightPosition(-1);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Helper classes
+
+OmniboxViewWin::ScopedFreeze::ScopedFreeze(OmniboxViewWin* 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);
+ }
+}
+
+OmniboxViewWin::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 in addition to 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_->InvalidateRect(NULL, false);
+ edit_->UpdateWindow();
+ }
+ }
+}
+
+OmniboxViewWin::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);
+}
+
+OmniboxViewWin::ScopedSuspendUndo::~ScopedSuspendUndo() {
+ // Resume Undo processing.
+ if (text_object_model_)
+ text_object_model_->Undo(tomResume, NULL);
+}
+
+// A subclass of NativeViewHost that provides accessibility info for the
+// underlying Omnibox view.
+class OmniboxViewWrapper : public views::NativeViewHost {
+ public:
+ explicit OmniboxViewWrapper(OmniboxViewWin* omnibox_view_win)
+ : omnibox_view_win_(omnibox_view_win) {}
+
+ gfx::NativeViewAccessible GetNativeViewAccessible() {
+ // This forces it to use NativeViewAccessibilityWin rather than
+ // any accessibility provided natively by the HWND.
+ return View::GetNativeViewAccessible();
+ }
+
+ // views::View
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) {
+ views::NativeViewHost::GetAccessibleState(state);
+ state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_LOCATION);
+ state->role = ui::AccessibilityTypes::ROLE_TEXT;
+ state->value = omnibox_view_win_->GetText();
+ state->state = ui::AccessibilityTypes::STATE_EDITABLE;
+ size_t sel_start;
+ size_t sel_end;
+ omnibox_view_win_->GetSelectionBounds(&sel_start, &sel_end);
+ state->selection_start = sel_start;
+ state->selection_end = sel_end;
+ }
+
+ private:
+ OmniboxViewWin* omnibox_view_win_;
+
+ DISALLOW_COPY_AND_ASSIGN(OmniboxViewWrapper);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// OmniboxViewWin
+
+namespace {
+
+// 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;
+
+// Intercepted method for BeginPaint(). Must use __stdcall convention.
+HDC WINAPI BeginPaintIntercept(HWND hWnd, LPPAINTSTRUCT lpPaint) {
+ if (!edit_hwnd || (hWnd != edit_hwnd))
+ return ::BeginPaint(hWnd, lpPaint);
+
+ *lpPaint = paint_struct;
+ return paint_struct.hdc;
+}
+
+// Intercepted method for EndPaint(). Must use __stdcall convention.
+BOOL WINAPI EndPaintIntercept(HWND hWnd, const PAINTSTRUCT* lpPaint) {
+ return (edit_hwnd && (hWnd == edit_hwnd)) || ::EndPaint(hWnd, lpPaint);
+}
+
+class PaintPatcher {
+ public:
+ PaintPatcher();
+ ~PaintPatcher();
+
+ void RefPatch();
+ void DerefPatch();
+
+ private:
+ size_t refcount_;
+ base::win::IATPatchFunction begin_paint_;
+ base::win::IATPatchFunction end_paint_;
+
+ DISALLOW_COPY_AND_ASSIGN(PaintPatcher);
+};
+
+PaintPatcher::PaintPatcher() : refcount_(0) {
+}
+
+PaintPatcher::~PaintPatcher() {
+ DCHECK_EQ(0U, refcount_);
+}
+
+void PaintPatcher::RefPatch() {
+ if (refcount_ == 0) {
+ DCHECK(!begin_paint_.is_patched());
+ DCHECK(!end_paint_.is_patched());
+ begin_paint_.Patch(kRichEditDLLName, "user32.dll", "BeginPaint",
+ &BeginPaintIntercept);
+ end_paint_.Patch(kRichEditDLLName, "user32.dll", "EndPaint",
+ &EndPaintIntercept);
+ }
+ ++refcount_;
+}
+
+void PaintPatcher::DerefPatch() {
+ DCHECK(begin_paint_.is_patched());
+ DCHECK(end_paint_.is_patched());
+ --refcount_;
+ if (refcount_ == 0) {
+ begin_paint_.Unpatch();
+ end_paint_.Unpatch();
+ }
+}
+
+base::LazyInstance<PaintPatcher> g_paint_patcher = LAZY_INSTANCE_INITIALIZER;
+
+// twips are a unit of type measurement, and RichEdit controls use them
+// to set offsets.
+const int kTwipsPerInch = 1440;
+
+} // namespace
+
+HMODULE OmniboxViewWin::loaded_library_module_ = NULL;
+
+OmniboxViewWin::OmniboxViewWin(OmniboxEditController* controller,
+ ToolbarModel* toolbar_model,
+ LocationBarView* parent_view,
+ CommandUpdater* command_updater,
+ bool popup_window_mode,
+ views::View* location_bar)
+ : OmniboxView(parent_view->profile(), controller, toolbar_model,
+ command_updater),
+ popup_view_(OmniboxPopupContentsView::Create(
+ parent_view->font(), this, model(), location_bar)),
+ parent_view_(parent_view),
+ popup_window_mode_(popup_window_mode),
+ force_hidden_(false),
+ tracking_click_(),
+ tracking_double_click_(false),
+ double_click_time_(0),
+ can_discard_mousemove_(false),
+ ignore_ime_messages_(false),
+ delete_at_end_pressed_(false),
+ font_(parent_view->font()),
+ possible_drag_(false),
+ in_drag_(false),
+ initiated_drag_(false),
+ drop_highlight_position_(-1),
+ ime_candidate_window_open_(false),
+ background_color_(skia::SkColorToCOLORREF(parent_view->GetColor(
+ ToolbarModel::NONE, LocationBarView::BACKGROUND))),
+ security_level_(ToolbarModel::NONE),
+ text_object_model_(NULL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ tsf_event_router_(base::win::IsTSFAwareRequired() ?
+ new ui::TSFEventRouter(this) : NULL)) {
+ if (!loaded_library_module_)
+ loaded_library_module_ = LoadLibrary(kRichEditDLLName);
+
+ if (!loaded_library_module_) {
+ // RichEdit DLL is not available. This is a rare error.
+ MissingSystemFileDialog::ShowDialog(
+ GetAncestor(location_bar->GetWidget()->GetNativeView(), GA_ROOT),
+ parent_view_->profile());
+ }
+
+ saved_selection_for_focus_change_.cpMin = -1;
+
+ g_paint_patcher.Pointer()->RefPatch();
+
+ Create(location_bar->GetWidget()->GetNativeView(), 0, 0, 0,
+ l10n_util::GetExtendedStyles());
+ SetReadOnly(popup_window_mode_);
+ SetFont(font_.GetNativeFont());
+
+ // Disable auto font changing. Otherwise, characters come from
+ // auto-completion and characters come from keyboard may be rendered with
+ // different fonts. See http://crbug.com/168480 for details.
+ const LRESULT lang_option = SendMessage(m_hWnd, EM_GETLANGOPTIONS, 0, 0);
+ SendMessage(m_hWnd, EM_SETLANGOPTIONS, 0, lang_option & ~IMF_AUTOFONT);
+
+ // 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.
+ base::win::ScopedGetDC screen_dc(NULL);
+ base::win::ScopedSelectObject font_in_dc(screen_dc, font_.GetNativeFont());
+ TEXTMETRIC tm = {0};
+ GetTextMetrics(screen_dc, &tm);
+ int cap_height = font_.GetBaseline() - tm.tmInternalLeading;
+ // 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.
+ const float kXHeightRatio = 0.7f;
+ font_x_height_ = static_cast<int>(
+ (static_cast<float>(cap_height) * kXHeightRatio) + 0.5);
+
+ // We set font_y_adjustment_ so that the ascender of the font gets
+ // centered on the available height of the view.
+ font_y_adjustment_ =
+ (parent_view->GetInternalHeight(true) - cap_height) / 2 -
+ tm.tmInternalLeading;
+
+ // 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(screen_dc, LOGPIXELSY);
+
+ // Set the default character style -- adjust to our desired baseline.
+ CHARFORMAT cf = {0};
+ cf.dwMask = CFM_OFFSET;
+ cf.yOffset = -font_y_adjustment_ * kTwipsPerPixel;
+ SetDefaultCharFormat(cf);
+
+ SetBackgroundColor(background_color_);
+
+ if (!popup_window_mode_) {
+ // Non-read-only edit controls have a drop target. Revoke it so that we can
+ // install our own. Revoking automatically deletes the existing one.
+ HRESULT hr = RevokeDragDrop(m_hWnd);
+ DCHECK_EQ(S_OK, hr);
+
+ // Register our drop target. The scoped_refptr here will delete the drop
+ // target if it fails to register itself correctly on |m_hWnd|. Otherwise,
+ // the edit control will invoke RevokeDragDrop when it's being destroyed, so
+ // we don't have to do so.
+ scoped_refptr<EditDropTarget> drop_target(new EditDropTarget(this));
+ }
+}
+
+OmniboxViewWin::~OmniboxViewWin() {
+ // Explicitly release the text object model now that we're done with it, and
+ // before we free the library. If the library gets unloaded before this
+ // released, it becomes garbage. Note that since text_object_model_ is lazy
+ // initialized, it may still be null.
+ if (text_object_model_)
+ text_object_model_->Release();
+
+ // 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();
+}
+
+views::View* OmniboxViewWin::parent_view() const {
+ return parent_view_;
+}
+
+void OmniboxViewWin::SaveStateToTab(WebContents* tab) {
+ DCHECK(tab);
+
+ const OmniboxEditModel::State model_state(model()->GetStateForTabSwitch());
+
+ CHARRANGE selection;
+ GetSelection(selection);
+ tab->SetUserData(
+ kAutocompleteEditStateKey,
+ new AutocompleteEditState(
+ model_state,
+ State(selection, saved_selection_for_focus_change_)));
+}
+
+void OmniboxViewWin::Update(const WebContents* tab_for_state_restoring) {
+ const bool visibly_changed_permanent_text =
+ model()->UpdatePermanentText(toolbar_model()->GetText(true));
+
+ const ToolbarModel::SecurityLevel security_level =
+ toolbar_model()->GetSecurityLevel();
+ const bool changed_security_level = (security_level != security_level_);
+
+ // Bail early when no visible state will actually change (prevents an
+ // unnecessary ScopedFreeze, and thus UpdateWindow()).
+ if (!changed_security_level && !visibly_changed_permanent_text &&
+ !tab_for_state_restoring)
+ return;
+
+ // Update our local state as desired. We set security_level_ here so it will
+ // already be correct before we get to any RevertAll()s below and use it.
+ security_level_ = security_level;
+
+ // When we're switching to a new tab, restore its state, if any.
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ 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 = static_cast<AutocompleteEditState*>(
+ tab_for_state_restoring->GetUserData(&kAutocompleteEditStateKey));
+ 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 OmniboxViewWin::OpenMatch(const AutocompleteMatch& match,
+ WindowOpenDisposition disposition,
+ const GURL& alternate_nav_url,
+ size_t selected_line) {
+ // When we navigate, we first revert to the unedited state, then if necessary
+ // synchronously change the permanent text to the new URL. If we don't freeze
+ // here, the user could potentially see a flicker of the current URL before
+ // the new one reappears, which would look glitchy.
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OmniboxView::OpenMatch(match, disposition, alternate_nav_url, selected_line);
+}
+
+string16 OmniboxViewWin::GetText() const {
+ const int len = GetTextLength() + 1;
+ string16 str;
+ if (len > 1)
+ GetWindowText(WriteInto(&str, len), len);
+ return str;
+}
+
+void OmniboxViewWin::SetUserText(const string16& text,
+ const string16& display_text,
+ bool update_popup) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ saved_selection_for_focus_change_.cpMin = -1;
+ OmniboxView::SetUserText(text, display_text, update_popup);
+}
+
+void OmniboxViewWin::SetWindowTextAndCaretPos(const string16& text,
+ size_t caret_pos,
+ bool update_popup,
+ bool notify_text_changed) {
+ SetWindowText(text.c_str());
+ PlaceCaretAt(caret_pos);
+
+ if (update_popup)
+ UpdatePopup();
+
+ if (notify_text_changed)
+ TextChanged();
+}
+
+void OmniboxViewWin::SetForcedQuery() {
+ const string16 current_text(GetText());
+ const size_t start = current_text.find_first_not_of(kWhitespaceWide);
+ if (start == string16::npos || (current_text[start] != '?'))
+ OmniboxView::SetUserText(L"?");
+ else
+ SetSelection(current_text.length(), start + 1);
+}
+
+bool OmniboxViewWin::IsSelectAll() const {
+ CHARRANGE selection;
+ GetSel(selection);
+ return IsSelectAllForRange(selection);
+}
+
+bool OmniboxViewWin::DeleteAtEndPressed() {
+ return delete_at_end_pressed_;
+}
+
+void OmniboxViewWin::GetSelectionBounds(string16::size_type* start,
+ string16::size_type* end) const {
+ CHARRANGE selection;
+ GetSel(selection);
+ *start = static_cast<size_t>(selection.cpMin);
+ *end = static_cast<size_t>(selection.cpMax);
+}
+
+void OmniboxViewWin::SelectAll(bool reversed) {
+ if (reversed)
+ SetSelection(GetTextLength(), 0);
+ else
+ SetSelection(0, GetTextLength());
+}
+
+void OmniboxViewWin::RevertAll() {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ saved_selection_for_focus_change_.cpMin = -1;
+ OmniboxView::RevertAll();
+}
+
+void OmniboxViewWin::UpdatePopup() {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ model()->SetInputInProgress(true);
+
+ // Don't allow the popup to open while the candidate window is open, so
+ // they don't overlap.
+ if (ime_candidate_window_open_)
+ return;
+
+ 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;
+ }
+
+ // 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 != sel.cpMin,
+ (sel.cpMax < GetTextLength()) || IsImeComposing());
+}
+
+void OmniboxViewWin::SetFocus() {
+ ::SetFocus(m_hWnd);
+ // Restore caret visibility if focus is explicitly requested. This is
+ // necessary because if we already have invisible focus, the ::SetFocus()
+ // call above will short-circuit, preventing us from reaching
+ // OmniboxEditModel::OnSetFocus(), which handles restoring visibility when the
+ // omnibox regains focus after losing focus.
+ model()->SetCaretVisibility(true);
+}
+
+void OmniboxViewWin::ApplyCaretVisibility() {
+ // We hide the caret just before destroying it, since destroying a caret that
+ // is in the "solid" phase of its blinking will leave a solid vertical bar.
+ // We even hide and destroy the caret if we're going to create it again below.
+ // If the caret was already visible on entry to this function, the
+ // CreateCaret() call (which first destroys the old caret) might leave a solid
+ // vertical bar for the same reason as above. Unconditionally hiding prevents
+ // this. The caret could be visible on entry to this function if the
+ // underlying edit control had re-created it automatically (see comments in
+ // OnPaint()).
+ HideCaret();
+ // We use DestroyCaret()/CreateCaret() instead of simply HideCaret()/
+ // ShowCaret() because HideCaret() is not sticky across paint events, e.g. a
+ // window resize will effectively restore caret visibility, regardless of
+ // whether HideCaret() was called before. While we do catch and handle these
+ // paint events (see OnPaint()), it doesn't seem to be enough to simply call
+ // HideCaret() while handling them because of the unpredictability of this
+ // Windows API. According to the documentation, it should be a cumulative call
+ // e.g. 5 hide calls should be balanced by 5 show calls. We have not found
+ // this to be true, which may be explained by the fact that this API is called
+ // internally in Windows, as well.
+ ::DestroyCaret();
+ if (model()->is_caret_visible()) {
+ ::CreateCaret(m_hWnd, (HBITMAP) NULL, 1, font_.GetHeight());
+ // According to the Windows API documentation, a newly created caret needs
+ // ShowCaret to be visible.
+ ShowCaret();
+ }
+}
+
+void OmniboxViewWin::SetDropHighlightPosition(int position) {
+ if (drop_highlight_position_ != position) {
+ RepaintDropHighlight(drop_highlight_position_);
+ drop_highlight_position_ = position;
+ RepaintDropHighlight(drop_highlight_position_);
+ }
+}
+
+void OmniboxViewWin::MoveSelectedText(int new_position) {
+ const string16 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 OmniboxViewWin::InsertText(int position, const string16& text) {
+ DCHECK((position >= 0) && (position <= GetTextLength()));
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ SetSelection(position, position);
+ ReplaceSel(text.c_str());
+ OnAfterPossibleChange();
+}
+
+void OmniboxViewWin::OnTemporaryTextMaybeChanged(const string16& display_text,
+ bool save_original_selection,
+ bool notify_text_changed) {
+ 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(), false,
+ notify_text_changed);
+}
+
+bool OmniboxViewWin::OnInlineAutocompleteTextMaybeChanged(
+ const string16& 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());
+ 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 OmniboxViewWin::OnRevertTemporaryText() {
+ SetSelectionRange(original_selection_);
+}
+
+void OmniboxViewWin::OnBeforePossibleChange() {
+ // Record our state.
+ text_before_change_ = GetText();
+ GetSelection(sel_before_change_);
+}
+
+bool OmniboxViewWin::OnAfterPossibleChange() {
+ return OnAfterPossibleChangeInternal(false);
+}
+
+bool OmniboxViewWin::OnAfterPossibleChangeInternal(bool force_text_changed) {
+ // 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 != new_sel.cpMax) ||
+ (sel_before_change_.cpMin != sel_before_change_.cpMax)) &&
+ ((new_sel.cpMin != sel_before_change_.cpMin) ||
+ (new_sel.cpMax != sel_before_change_.cpMax));
+
+ // See if the text or selection have changed since OnBeforePossibleChange().
+ const string16 new_text(GetText());
+ const bool text_differs = (new_text != text_before_change_) ||
+ force_text_changed;
+
+ // 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(
+ text_before_change_, new_text, new_sel.cpMin, new_sel.cpMax,
+ selection_differs, text_differs, just_deleted_text, !IsImeComposing());
+
+ if (selection_differs)
+ controller()->OnSelectionBoundsChanged();
+
+ if (something_changed && text_differs)
+ TextChanged();
+
+ if (text_differs) {
+ // Note that a TEXT_CHANGED event implies that the cursor/selection
+ // probably changed too, so we don't need to send both.
+ native_view_host_->GetWidget()->NotifyAccessibilityEvent(
+ native_view_host_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true);
+ } else if (selection_differs) {
+ // Notify assistive technology that the cursor or selection changed.
+ native_view_host_->GetWidget()->NotifyAccessibilityEvent(
+ native_view_host_,
+ ui::AccessibilityTypes::EVENT_SELECTION_CHANGED,
+ true);
+ } else if (delete_at_end_pressed_) {
+ model()->OnChanged();
+ }
+
+ return something_changed;
+}
+
+void OmniboxViewWin::OnCandidateWindowCountChanged(size_t window_count) {
+ ime_candidate_window_open_ = (window_count != 0);
+ if (ime_candidate_window_open_) {
+ CloseOmniboxPopup();
+ } else if (model()->user_input_in_progress()) {
+ // UpdatePopup assumes user input is in progress, so only call it if
+ // that's the case. Otherwise, autocomplete may run on an empty user
+ // text.
+ UpdatePopup();
+ }
+}
+
+void OmniboxViewWin::OnTextUpdated(const ui::Range& /*composition_range*/) {
+ if (ignore_ime_messages_)
+ return;
+ OnAfterPossibleChangeInternal(true);
+ // Call OnBeforePossibleChange function here to get correct diff in next IME
+ // update. The Text Services Framework does not provide any notification
+ // before entering edit session, therefore we don't have good place to call
+ // OnBeforePossibleChange.
+ OnBeforePossibleChange();
+}
+
+gfx::NativeView OmniboxViewWin::GetNativeView() const {
+ return m_hWnd;
+}
+
+// static
+gfx::NativeView OmniboxViewWin::GetRelativeWindowForNativeView(
+ gfx::NativeView edit_native_view) {
+ // When an IME is attached to the rich-edit control, retrieve its window
+ // handle, and the popup window of OmniboxPopupView will be shown under the
+ // IME windows.
+ // Otherwise, the popup window will be shown under top-most windows.
+ // TODO(hbono): http://b/1111369 if we exclude this popup window from the
+ // display area of IME windows, this workaround becomes unnecessary.
+ HWND ime_window = ImmGetDefaultIMEWnd(edit_native_view);
+ return ime_window ? ime_window : HWND_NOTOPMOST;
+}
+
+gfx::NativeView OmniboxViewWin::GetRelativeWindowForPopup() const {
+ return GetRelativeWindowForNativeView(GetNativeView());
+}
+
+void OmniboxViewWin::SetInstantSuggestion(const string16& suggestion) {
+ parent_view_->SetInstantSuggestion(suggestion);
+}
+
+int OmniboxViewWin::TextWidth() const {
+ return WidthNeededToDisplay(GetText());
+}
+
+string16 OmniboxViewWin::GetInstantSuggestion() const {
+ return parent_view_->GetInstantSuggestion();
+}
+
+bool OmniboxViewWin::IsImeComposing() const {
+ if (tsf_event_router_)
+ return tsf_event_router_->IsImeComposing();
+ bool ime_composing = false;
+ HIMC context = ImmGetContext(m_hWnd);
+ if (context) {
+ ime_composing = !!ImmGetCompositionString(context, GCS_COMPSTR, NULL, 0);
+ ImmReleaseContext(m_hWnd, context);
+ }
+ return ime_composing;
+}
+
+int OmniboxViewWin::GetMaxEditWidth(int entry_width) const {
+ RECT formatting_rect;
+ GetRect(&formatting_rect);
+ RECT edit_bounds;
+ GetClientRect(&edit_bounds);
+ return entry_width - formatting_rect.left -
+ (edit_bounds.right - formatting_rect.right);
+}
+
+views::View* OmniboxViewWin::AddToView(views::View* parent) {
+ native_view_host_ = new OmniboxViewWrapper(this);
+ parent->AddChildView(native_view_host_);
+ native_view_host_->set_focus_view(parent);
+ native_view_host_->Attach(GetNativeView());
+ return native_view_host_;
+}
+
+int OmniboxViewWin::OnPerformDrop(const ui::DropTargetEvent& event) {
+ return OnPerformDropImpl(event, false);
+}
+
+gfx::Font OmniboxViewWin::GetFont() {
+ return font_;
+}
+
+int OmniboxViewWin::WidthOfTextAfterCursor() {
+ CHARRANGE selection;
+ GetSelection(selection);
+ // See comments in LocationBarView::Layout as to why this uses -1.
+ const int start = std::max(0, static_cast<int>(selection.cpMax - 1));
+ return WidthNeededToDisplay(GetText().substr(start));
+}
+
+int OmniboxViewWin::OnPerformDropImpl(const ui::DropTargetEvent& event,
+ bool in_drag) {
+ const ui::OSExchangeData& data = event.data();
+
+ if (data.HasURL()) {
+ GURL url;
+ string16 title;
+ if (data.GetURLAndTitle(&url, &title)) {
+ string16 text(StripJavascriptSchemas(UTF8ToUTF16(url.spec())));
+ OmniboxView::SetUserText(text);
+ model()->AcceptInput(CURRENT_TAB, true);
+ return CopyOrLinkDragOperation(event.source_operations());
+ }
+ } else if (data.HasString()) {
+ int string_drop_position = drop_highlight_position();
+ string16 text;
+ if ((string_drop_position != -1 || !in_drag) && data.GetString(&text)) {
+ DCHECK(string_drop_position == -1 ||
+ ((string_drop_position >= 0) &&
+ (string_drop_position <= GetTextLength())));
+ if (in_drag) {
+ if (event.source_operations()== ui::DragDropTypes::DRAG_MOVE)
+ MoveSelectedText(string_drop_position);
+ else
+ InsertText(string_drop_position, text);
+ } else {
+ string16 collapsed_text(CollapseWhitespace(text, true));
+ if (model()->CanPasteAndGo(collapsed_text))
+ model()->PasteAndGo(collapsed_text);
+ }
+ return CopyOrLinkDragOperation(event.source_operations());
+ }
+ }
+
+ return ui::DragDropTypes::DRAG_NONE;
+}
+
+void OmniboxViewWin::CopyURL() {
+ DoCopyURL(toolbar_model()->GetURL(),
+ toolbar_model()->GetText(false),
+ model()->profile());
+}
+
+bool OmniboxViewWin::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) {
+ ui::KeyboardCode key = event.key_code();
+ // We don't process ALT + numpad digit as accelerators, they are used for
+ // entering special characters. We do translate alt-home.
+ if (event.IsAltDown() && (key != ui::VKEY_HOME) &&
+ views::NativeTextfieldWin::IsNumPadDigit(key,
+ (event.flags() & ui::EF_EXTENDED) != 0))
+ return true;
+
+ // Skip accelerators for key combinations omnibox wants to crack. This list
+ // should be synced with OnKeyDownOnlyWritable() (but for tab which is dealt
+ // with above in LocationBarView::SkipDefaultKeyEventProcessing).
+ //
+ // We cannot return true for all keys because we still need to handle some
+ // accelerators (e.g., F5 for reload the page should work even when the
+ // Omnibox gets focused).
+ switch (key) {
+ case ui::VKEY_ESCAPE: {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ return model()->OnEscapeKeyPressed();
+ }
+
+ case ui::VKEY_RETURN:
+ return true;
+
+ case ui::VKEY_UP:
+ case ui::VKEY_DOWN:
+ return !event.IsAltDown();
+
+ case ui::VKEY_DELETE:
+ case ui::VKEY_INSERT:
+ return !event.IsAltDown() && event.IsShiftDown() &&
+ !event.IsControlDown();
+
+ case ui::VKEY_X:
+ case ui::VKEY_V:
+ return !event.IsAltDown() && event.IsControlDown();
+
+ case ui::VKEY_BACK:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+void OmniboxViewWin::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 OmniboxViewWin::IsCommandIdChecked(int command_id) const {
+ return false;
+}
+
+bool OmniboxViewWin::IsCommandIdEnabled(int command_id) const {
+ switch (command_id) {
+ case IDS_UNDO:
+ return !!CanUndo();
+ case IDC_CUT:
+ return !!CanCut();
+ case IDC_COPY:
+ return !!CanCopy();
+ case IDC_COPY_URL:
+ return !!CanCopy() &&
+ !model()->user_input_in_progress() &&
+ toolbar_model()->WouldReplaceSearchURLWithSearchTerms();
+ case IDC_PASTE:
+ return !!CanPaste();
+ case IDS_PASTE_AND_GO:
+ return model()->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 OmniboxViewWin::GetAcceleratorForCommandId(
+ int command_id,
+ ui::Accelerator* accelerator) {
+ return parent_view_->GetWidget()->GetAccelerator(command_id, accelerator);
+}
+
+bool OmniboxViewWin::IsItemForCommandIdDynamic(int command_id) const {
+ // No need to change the default IDS_PASTE_AND_GO label unless this is a
+ // search.
+ return command_id == IDS_PASTE_AND_GO;
+}
+
+string16 OmniboxViewWin::GetLabelForCommandId(int command_id) const {
+ DCHECK_EQ(IDS_PASTE_AND_GO, command_id);
+ return l10n_util::GetStringUTF16(
+ model()->IsPasteAndSearch(GetClipboardText()) ?
+ IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO);
+}
+
+void OmniboxViewWin::ExecuteCommand(int command_id) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ if (command_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(GetClipboardText());
+ return;
+ }
+
+ OnBeforePossibleChange();
+ switch (command_id) {
+ case IDS_UNDO:
+ Undo();
+ break;
+
+ case IDC_CUT:
+ Cut();
+ break;
+
+ case IDC_COPY:
+ Copy();
+ break;
+
+ case IDC_COPY_URL:
+ CopyURL();
+ break;
+
+ case IDC_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 OmniboxViewWin::WordBreakProc(LPTSTR edit_text,
+ int current_pos,
+ int length,
+ 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.
+
+ // 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, length, WB_LEFTBREAK);
+
+ if ((prev_delim == 0) &&
+ !WordBreakProc(edit_text, 0, length, 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, length, 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, length, 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, length, 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, length, 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, length, WB_ISDELIMITER))
+ return i;
+ }
+ return length;
+ }
+
+ NOTREACHED();
+ return 0;
+}
+
+// static
+bool OmniboxViewWin::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 OmniboxViewWin::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 OmniboxViewWin::OnContextMenu(HWND window, const CPoint& point) {
+ BuildContextMenu();
+
+ views::MenuModelAdapter adapter(context_menu_contents_.get());
+ context_menu_runner_.reset(new views::MenuRunner(adapter.CreateMenu()));
+
+ gfx::Point location(point);
+ if (point.x == -1 || point.y == -1) {
+ POINT p;
+ GetCaretPos(&p);
+ MapWindowPoints(HWND_DESKTOP, &p, 1);
+ location.SetPoint(p.x, p.y);
+ }
+
+ ignore_result(context_menu_runner_->RunMenuAt(native_view_host_->GetWidget(),
+ NULL, gfx::Rect(location, gfx::Size()), views::MenuItemView::TOPLEFT,
+ views::MenuRunner::HAS_MNEMONICS));
+}
+
+void OmniboxViewWin::OnCopy() {
+ string16 text(GetSelectedText());
+ if (text.empty())
+ return;
+
+ CHARRANGE sel;
+ GURL url;
+ bool write_url = false;
+ GetSel(sel);
+ // GetSel() doesn't preserve selection direction, so sel.cpMin will always be
+ // the smaller value.
+ model()->AdjustTextForCopy(sel.cpMin, IsSelectAll(), &text, &url, &write_url);
+ if (write_url)
+ DoCopyURL(url, text, model()->profile());
+ else
+ DoCopyText(text, model()->profile());
+}
+
+LRESULT OmniboxViewWin::OnCreate(const CREATESTRUCTW* /*create_struct*/) {
+ if (base::win::IsTSFAwareRequired()) {
+ // Enable TSF support of RichEdit.
+ SetEditStyle(SES_USECTF, SES_USECTF);
+ }
+ if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
+ BOOL touch_mode = RegisterTouchWindow(m_hWnd, TWF_WANTPALM);
+ DCHECK(touch_mode);
+ }
+ SetMsgHandled(FALSE);
+
+ // When TSF is enabled, OnTextUpdated() may be called without any previous
+ // call that would have indicated the start of an editing session. In order
+ // to guarantee we've always called OnBeforePossibleChange() before
+ // OnAfterPossibleChange(), we therefore call that here. Note that multiple
+ // (i.e. unmatched) calls to this function in a row are safe.
+ if (base::win::IsTSFAwareRequired())
+ OnBeforePossibleChange();
+ return 0;
+}
+
+void OmniboxViewWin::OnCut() {
+ OnCopy();
+
+ // This replace selection will have no effect (even on the undo stack) if the
+ // current selection is empty.
+ ReplaceSel(L"", true);
+}
+
+LRESULT OmniboxViewWin::OnGetObject(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ // This is a request for the native accessibility object.
+ if (lparam == OBJID_CLIENT) {
+ return LresultFromObject(IID_IAccessible, wparam,
+ native_view_host_->GetNativeViewAccessible());
+ }
+ return 0;
+}
+
+LRESULT OmniboxViewWin::OnImeComposition(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ if (ignore_ime_messages_) {
+ // This message was sent while we're in the middle of meddling with the
+ // underlying edit control. If we handle it below, OnAfterPossibleChange()
+ // can get bogus text for the edit, and rerun autocomplete, destructively
+ // modifying the result set that we're in the midst of using. For example,
+ // if SetWindowTextAndCaretPos() was called due to the user clicking an
+ // entry in the popup, we're in the middle of executing SetSelectedLine(),
+ // and changing the results can cause checkfailures.
+ return DefWindowProc(message, wparam, lparam);
+ }
+
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+ LRESULT result = DefWindowProc(message, wparam, lparam);
+ // Force an IME composition confirmation operation to trigger the text_changed
+ // code in OnAfterPossibleChange(), even if identical contents are confirmed,
+ // to make sure the model can update its internal states correctly.
+ OnAfterPossibleChangeInternal((lparam & GCS_RESULTSTR) != 0);
+ return result;
+}
+
+LRESULT OmniboxViewWin::OnImeEndComposition(UINT message, WPARAM wparam,
+ LPARAM lparam) {
+ // The edit control auto-clears the selection on WM_IME_ENDCOMPOSITION, which
+ // means any inline autocompletion we were showing will no longer be
+ // selected, and therefore no longer replaced by further user typing. To
+ // avoid this we manually restore the original selection after the edit
+ // handles the message.
+ CHARRANGE range;
+ GetSel(range);
+ LRESULT result = DefWindowProc(message, wparam, lparam);
+ SetSel(range);
+ return result;
+}
+
+LRESULT OmniboxViewWin::OnImeNotify(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ // Close the popup when the IME composition window is open, so they don't
+ // overlap.
+ switch (wparam) {
+ case IMN_OPENCANDIDATE:
+ ime_candidate_window_open_ = true;
+ CloseOmniboxPopup();
+ break;
+ case IMN_CLOSECANDIDATE:
+ ime_candidate_window_open_ = false;
+
+ // UpdatePopup assumes user input is in progress, so only call it if
+ // that's the case. Otherwise, autocomplete may run on an empty user
+ // text. For example, Baidu Japanese IME sends IMN_CLOSECANDIDATE when
+ // composition mode is entered, but the user may not have input anything
+ // yet.
+ if (model()->user_input_in_progress())
+ UpdatePopup();
+
+ break;
+ default:
+ break;
+ }
+ return DefWindowProc(message, wparam, lparam);
+}
+
+LRESULT OmniboxViewWin::OnTouchEvent(UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ // There is a bug in Windows 8 where in the generated mouse messages
+ // after touch go to the window which previously had focus. This means that
+ // if a user taps the omnibox to give it focus, we don't get the simulated
+ // WM_LBUTTONDOWN, and thus don't properly select all the text. To ensure
+ // that we get this message, we capture the mouse when the user is doing a
+ // single-point tap on an unfocused model.
+ if ((wparam == 1) && !model()->has_focus()) {
+ TOUCHINPUT point = {0};
+ if (GetTouchInputInfoWrapper(reinterpret_cast<HTOUCHINPUT>(lparam), 1,
+ &point, sizeof(TOUCHINPUT))) {
+ if (point.dwFlags & TOUCHEVENTF_DOWN)
+ SetCapture();
+ else if (point.dwFlags & TOUCHEVENTF_UP)
+ ReleaseCapture();
+ }
+ }
+ SetMsgHandled(false);
+ return 0;
+}
+
+void OmniboxViewWin::OnKeyDown(TCHAR key,
+ UINT repeat_count,
+ UINT flags) {
+ delete_at_end_pressed_ = false;
+
+ if (OnKeyDownAllModes(key, repeat_count, flags))
+ return;
+
+ // Make sure that we handle system key events like Alt-F4.
+ if (popup_window_mode_) {
+ DefWindowProc(GetCurrentMessage()->message, key, MAKELPARAM(repeat_count,
+ flags));
+ return;
+ }
+
+ if (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 OmniboxViewWin::OnKeyUp(TCHAR key,
+ UINT repeat_count,
+ UINT flags) {
+ if (key == VK_CONTROL)
+ model()->OnControlKeyChanged(false);
+
+ // On systems with RTL input languages, ctrl+shift toggles the reading order
+ // (depending on which shift key is pressed). But by default the CRichEditCtrl
+ // only changes the current reading order, and as soon as the user deletes all
+ // the text, or we call SetWindowText(), it reverts to the "default" order.
+ // To work around this, if the user hits ctrl+shift, we pass it to
+ // DefWindowProc() while the edit is empty, which toggles the default reading
+ // order; then we restore the user's input.
+ if (!(flags & KF_ALTDOWN) &&
+ (((key == VK_CONTROL) && (GetKeyState(VK_SHIFT) < 0)) ||
+ ((key == VK_SHIFT) && (GetKeyState(VK_CONTROL) < 0)))) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+
+ string16 saved_text(GetText());
+ CHARRANGE saved_sel;
+ GetSelection(saved_sel);
+
+ SetWindowText(L"");
+
+ DefWindowProc(WM_KEYUP, key, MAKELPARAM(repeat_count, flags));
+
+ SetWindowText(saved_text.c_str());
+ SetSelectionRange(saved_sel);
+ return;
+ }
+
+ SetMsgHandled(false);
+}
+
+void OmniboxViewWin::OnKillFocus(HWND focus_wnd) {
+ if (m_hWnd == focus_wnd) {
+ // Focus isn't actually leaving.
+ SetMsgHandled(false);
+ return;
+ }
+
+ // This must be invoked before ClosePopup.
+ model()->OnWillKillFocus(focus_wnd);
+
+ // Close the popup.
+ CloseOmniboxPopup();
+
+ // 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);
+
+ // 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.
+ //
+ // Crazy hack: If we just do PlaceCaretAt(0), and the beginning of the text is
+ // currently scrolled out of view, we can wind up with a blinking cursor in
+ // the toolbar at the current X coordinate of the beginning of the text. By
+ // first doing a reverse-select-all to scroll the beginning of the text into
+ // view, we work around this CRichEditCtrl bug.
+ SelectAll(true);
+ PlaceCaretAt(0);
+
+ if (tsf_event_router_)
+ tsf_event_router_->SetManager(NULL);
+}
+
+void OmniboxViewWin::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));
+
+ // Rich Edit 4.1 doesn't select the last word when the user double clicks
+ // past the text. Do it manually.
+ CHARRANGE selection;
+ GetSelection(selection);
+ // The default window proc for Rich Edit 4.1 seems to select the CHARRANGE
+ // {text_length, text_length + 1} after a double click past the text.
+ int length = GetTextLength();
+ if (selection.cpMin == length && selection.cpMax == length + 1) {
+ string16 text = GetText();
+ int word_break = WordBreakProc(&text[0], length, length, WB_LEFT);
+ selection.cpMin = word_break;
+ selection.cpMax = length;
+ SetSelectionRange(selection);
+ }
+
+ OnAfterPossibleChange();
+
+ gaining_focus_.reset(); // See NOTE in OnMouseActivate().
+}
+
+void OmniboxViewWin::OnLButtonDown(UINT keys, const CPoint& point) {
+ TrackMousePosition(kLeft, point);
+ if (gaining_focus_.get()) {
+ // 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_ &&
+ views::NativeTextfieldWin::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 OmniboxViewWin::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));
+
+ SelectAllIfNecessary(kLeft, point);
+
+ tracking_click_[kLeft] = false;
+
+ possible_drag_ = false;
+}
+
+void OmniboxViewWin::OnMButtonDblClk(UINT /*keys*/, const CPoint& /*point*/) {
+ gaining_focus_.reset(); // See NOTE in OnMouseActivate().
+
+ // By default, the edit responds to middle-clicks by capturing the mouse and
+ // ignoring all subsequent events until it receives another click (of any of
+ // the left, middle, or right buttons). This bizarre behavior is not only
+ // useless but can cause the UI to appear unresponsive if a user accidentally
+ // middle-clicks the edit (instead of a tab above it), so we purposefully eat
+ // this message (instead of calling SetMsgHandled(false)) to avoid triggering
+ // this.
+}
+
+void OmniboxViewWin::OnMButtonDown(UINT /*keys*/, const CPoint& /*point*/) {
+ tracking_double_click_ = false;
+
+ // See note in OnMButtonDblClk above.
+}
+
+void OmniboxViewWin::OnMButtonUp(UINT /*keys*/, const CPoint& /*point*/) {
+ possible_drag_ = false;
+
+ // See note in OnMButtonDblClk above.
+}
+
+LRESULT OmniboxViewWin::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 click. We have to do this here rather
+ // than in OnXButtonDown() since in many scenarios OnSetFocus() will be
+ // reached before OnXButtonDown(), 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() ||
+ (model()->focus_state() == OMNIBOX_FOCUS_INVISIBLE)) &&
+ ((mouse_message == WM_LBUTTONDOWN || mouse_message == WM_RBUTTONDOWN)) &&
+ (result == MA_ACTIVATE)) {
+ if (gaining_focus_) {
+ // On Windows 8 in metro mode, we get two WM_MOUSEACTIVATE messages when
+ // we click on the omnibox with the mouse.
+ DCHECK(win8::IsSingleWindowMetroMode());
+ return result;
+ }
+ gaining_focus_.reset(new ScopedFreeze(this, GetTextObjectModel()));
+
+ // Restore caret visibility whenever the user clicks in the omnibox in a
+ // way that would give it focus. We must handle this case separately here
+ // because if the omnibox currently has invisible focus, the mouse event
+ // won't trigger either SetFocus() or OmniboxEditModel::OnSetFocus().
+ model()->SetCaretVisibility(true);
+
+ // NOTE: Despite |mouse_message| being WM_XBUTTONDOWN here, we're not
+ // guaranteed to call OnXButtonDown() later! Specifically, if this is the
+ // second click of a double click, we'll reach here but later call
+ // OnXButtonDblClk(). 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 OmniboxViewWin::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_[kLeft] && !IsDrag(click_point_[kLeft], point))
+ return;
+
+ tracking_click_[kLeft] = 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 OmniboxViewWin::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;
+
+ // If textfield has focus, reaffirm its caret visibility (without focus, a new
+ // caret could be created and confuse the user as to where the focus is). This
+ // needs to be called regardless of the current visibility of the caret. This
+ // is because the underlying edit control will automatically re-create the
+ // caret when it receives certain events that trigger repaints, e.g. window
+ // resize events. This also checks for the existence of selected text, in
+ // which case there shouldn't be a recreated caret since this would create
+ // both a highlight and a blinking caret.
+ if (model()->has_focus()) {
+ CHARRANGE sel;
+ GetSel(sel);
+ if (sel.cpMin == sel.cpMax)
+ ApplyCaretVisibility();
+ }
+}
+
+void OmniboxViewWin::OnPaste() {
+ // Replace the selection if we have something to paste.
+ const string16 text(GetClipboardText());
+ if (!text.empty()) {
+ // Record this paste, so we can do different behavior.
+ model()->on_paste();
+ // Force a Paste operation to trigger the text_changed code in
+ // OnAfterPossibleChange(), even if identical contents are pasted into the
+ // text box.
+ text_before_change_.clear();
+ ReplaceSel(text.c_str(), true);
+ }
+}
+
+void OmniboxViewWin::OnRButtonDblClk(UINT /*keys*/, const CPoint& /*point*/) {
+ gaining_focus_.reset(); // See NOTE in OnMouseActivate().
+ SetMsgHandled(false);
+}
+
+void OmniboxViewWin::OnRButtonDown(UINT /*keys*/, const CPoint& point) {
+ TrackMousePosition(kRight, point);
+ tracking_double_click_ = false;
+ possible_drag_ = false;
+ gaining_focus_.reset();
+ SetMsgHandled(false);
+}
+
+void OmniboxViewWin::OnRButtonUp(UINT /*keys*/, const CPoint& point) {
+ SelectAllIfNecessary(kRight, point);
+ tracking_click_[kRight] = false;
+ SetMsgHandled(false);
+}
+
+void OmniboxViewWin::OnSetFocus(HWND focus_wnd) {
+ views::FocusManager* focus_manager = parent_view_->GetFocusManager();
+ if (focus_manager) {
+ // Notify the FocusManager that the focused view is now the location bar
+ // (our parent view).
+ focus_manager->SetFocusedView(parent_view_);
+ } else {
+ NOTREACHED();
+ }
+
+ model()->OnSetFocus(GetKeyState(VK_CONTROL) < 0);
+
+ // 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;
+ }
+
+ if (!tsf_event_router_) {
+ SetMsgHandled(false);
+ } else {
+ DefWindowProc();
+ // Document manager created by RichEdit can be obtained only after
+ // WM_SETFOCUS event is handled.
+ tsf_event_router_->SetManager(
+ ui::TSFBridge::GetInstance()->GetThreadManager());
+ SetMsgHandled(true);
+ }
+}
+
+LRESULT OmniboxViewWin::OnSetText(const wchar_t* text) {
+ // Ignore all IME messages while we process this WM_SETTEXT message.
+ // When SetWindowText() is called while an IME is composing text, the IME
+ // calls SendMessage() to send a WM_IME_COMPOSITION message. When we receive
+ // this WM_IME_COMPOSITION message, we update the omnibox and may call
+ // SetWindowText() again. To stop this recursive message-handler call, we
+ // stop updating the omnibox while we process a WM_SETTEXT message.
+ // We wouldn't need to do this update anyway, because either we're in the
+ // middle of updating the omnibox already or the caller of SetWindowText()
+ // is going to update the omnibox next.
+ base::AutoReset<bool> auto_reset_ignore_ime_messages(
+ &ignore_ime_messages_, true);
+ return DefWindowProc(WM_SETTEXT, 0, reinterpret_cast<LPARAM>(text));
+}
+
+void OmniboxViewWin::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 OmniboxViewWin::OnWindowPosChanging(WINDOWPOS* window_pos) {
+ if (force_hidden_)
+ window_pos->flags &= ~SWP_SHOWWINDOW;
+ SetMsgHandled(true);
+}
+
+BOOL OmniboxViewWin::OnMouseWheel(UINT flags, short delta, CPoint point) {
+ // Forward the mouse-wheel message to the window under the mouse.
+ if (!ui::RerouteMouseWheel(m_hWnd, MAKEWPARAM(flags, delta),
+ MAKELPARAM(point.x, point.y)))
+ SetMsgHandled(false);
+ return 0;
+}
+
+void OmniboxViewWin::HandleKeystroke(UINT message,
+ TCHAR key,
+ UINT repeat_count,
+ UINT flags) {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OnBeforePossibleChange();
+
+ if (key == ui::VKEY_HOME || key == ui::VKEY_END) {
+ // DefWindowProc() might reset the keyboard layout when it receives a
+ // keydown event for VKEY_HOME or VKEY_END. When the window was created
+ // with WS_EX_LAYOUTRTL and the current keyboard layout is not a RTL one,
+ // if the input text is pure LTR text, the layout changes to the first RTL
+ // keyboard layout in keyboard layout queue; if the input text is
+ // bidirectional text, the layout changes to the keyboard layout of the
+ // first RTL character in input text. When the window was created without
+ // WS_EX_LAYOUTRTL and the current keyboard layout is not a LTR one, if the
+ // input text is pure RTL text, the layout changes to English; if the input
+ // text is bidirectional text, the layout changes to the keyboard layout of
+ // the first LTR character in input text. Such keyboard layout change
+ // behavior is surprising and inconsistent with keyboard behavior
+ // elsewhere, so reset the layout in this case.
+ HKL layout = GetKeyboardLayout(0);
+ DefWindowProc(message, key, MAKELPARAM(repeat_count, flags));
+ ActivateKeyboardLayout(layout, KLF_REORDER);
+ } else {
+ DefWindowProc(message, key, MAKELPARAM(repeat_count, flags));
+ }
+
+ // CRichEditCtrl automatically turns on IMF_AUTOKEYBOARD when the user
+ // inputs an RTL character, making it difficult for the user to control
+ // what language is set as they type. Force this off to make the edit's
+ // behavior more stable.
+ const int lang_options = SendMessage(EM_GETLANGOPTIONS, 0, 0);
+ if (lang_options & IMF_AUTOKEYBOARD)
+ SendMessage(EM_SETLANGOPTIONS, 0, lang_options & ~IMF_AUTOKEYBOARD);
+
+ OnAfterPossibleChange();
+}
+
+bool OmniboxViewWin::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.
+
+ // If adding a new key that could possibly be an accelerator then you'll need
+ // to update LocationBarView::SkipDefaultKeyEventProcessing() as well.
+ int count = repeat_count;
+ switch (key) {
+ case VK_RIGHT:
+ // TODO(sky): figure out RTL.
+ if (base::i18n::IsRTL())
+ return false;
+ {
+ CHARRANGE selection;
+ GetSel(selection);
+ return (selection.cpMin == selection.cpMax) &&
+ (selection.cpMin == GetTextLength()) &&
+ model()->CommitSuggestedText(true);
+ }
+
+ case VK_RETURN:
+ model()->AcceptInput((flags & KF_ALTDOWN) ?
+ NEW_FOREGROUND_TAB : CURRENT_TAB, false);
+ return true;
+
+ case VK_PRIOR:
+ case VK_NEXT:
+ count = model()->result().size();
+ // FALL THROUGH
+ case VK_UP:
+ case VK_DOWN:
+ // Ignore alt + numpad, but treat alt + (non-numpad) like (non-numpad).
+ if ((flags & KF_ALTDOWN) && !(flags & KF_EXTENDED))
+ return false;
+
+ model()->OnUpOrDownKeyPressed(((key == VK_PRIOR) || (key == VK_UP)) ?
+ -count : 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. Also note that we bring
+ // up 'clear browsing data' on control-shift-delete.
+ // Copy: Ctrl-Insert and Ctrl-c is treated as copy. Shift-Ctrl-c is not.
+ // (This is handled in OnKeyDownAllModes().)
+ // Paste: Shift-Insert and Ctrl-v are treated 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)
+ return false;
+ if (GetKeyState(VK_SHIFT) >= 0) {
+ if (GetKeyState(VK_CONTROL) >= 0) {
+ CHARRANGE selection;
+ GetSel(selection);
+ delete_at_end_pressed_ = ((selection.cpMin == selection.cpMax) &&
+ (selection.cpMin == GetTextLength()));
+ }
+ 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 (model()->popup_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.
+ model()->popup_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:
+ // Ignore insert by itself, so we don't turn overtype mode on/off.
+ if (!(flags & KF_ALTDOWN) && (GetKeyState(VK_SHIFT) >= 0) &&
+ (GetKeyState(VK_CONTROL) >= 0))
+ return true;
+ // FALL THROUGH
+ 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: {
+ const bool shift_pressed = GetKeyState(VK_SHIFT) < 0;
+ if (model()->is_keyword_hint() && !shift_pressed) {
+ // Accept the keyword.
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ model()->AcceptKeyword();
+ } else if (shift_pressed &&
+ model()->popup_model()->selected_line_state() ==
+ OmniboxPopupModel::KEYWORD) {
+ model()->ClearKeyword(GetText());
+ } else {
+ model()->OnUpOrDownKeyPressed(shift_pressed ? -count : count);
+ }
+ return true;
+ }
+
+ case 0xbb: // Ctrl-'='. Triggers subscripting (even in plain text mode).
+ // We don't use VK_OEM_PLUS in case the macro isn't defined.
+ // (e.g., we don't have this symbol in embeded environment).
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+bool OmniboxViewWin::OnKeyDownAllModes(TCHAR key,
+ UINT repeat_count,
+ UINT flags) {
+ // See KF_ALTDOWN comment atop OnKeyDownOnlyWritable().
+
+ switch (key) {
+ case VK_CONTROL:
+ model()->OnControlKeyChanged(true);
+ return false;
+
+ case VK_INSERT:
+ case 'C':
+ // See more detailed comments in OnKeyDownOnlyWritable().
+ if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0))
+ return false;
+ if (GetKeyState(VK_SHIFT) >= 0)
+ Copy();
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+void OmniboxViewWin::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;
+ base::win::ScopedComPtr<ITextSelection> selection;
+ const HRESULT hr = text_object_model->GetSelection(selection.Receive());
+ DCHECK_EQ(S_OK, hr);
+ long flags;
+ selection->GetFlags(&flags);
+ if (flags & tomSelStartActive)
+ std::swap(sel.cpMin, sel.cpMax);
+}
+
+string16 OmniboxViewWin::GetSelectedText() const {
+ CHARRANGE sel;
+ GetSel(sel);
+ string16 str;
+ if (sel.cpMin != sel.cpMax)
+ GetSelText(WriteInto(&str, sel.cpMax - sel.cpMin + 1));
+ return str;
+}
+
+void OmniboxViewWin::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;
+ base::win::ScopedComPtr<ITextSelection> selection;
+ const HRESULT hr = text_object_model->GetSelection(selection.Receive());
+ DCHECK_EQ(S_OK, hr);
+ selection->SetFlags(tomSelStartActive);
+}
+
+void OmniboxViewWin::PlaceCaretAt(string16::size_type pos) {
+ SetSelection(static_cast<LONG>(pos), static_cast<LONG>(pos));
+}
+
+bool OmniboxViewWin::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 OmniboxViewWin::ClipXCoordToVisibleText(LONG x,
+ bool is_triple_click) const {
+ // Clip the X coordinate to the left edge of the text. Careful:
+ // PosFromChar(0) may return a negative X coordinate if the beginning of the
+ // text has scrolled off the edit, so don't go past the clip rect's edge.
+ PARAFORMAT2 pf2;
+ GetParaFormat(pf2);
+ // Calculation of the clipped coordinate is more complicated if the paragraph
+ // layout is RTL layout, or if there is RTL characters inside the LTR layout
+ // paragraph.
+ const bool ltr_text_in_ltr_layout = !(pf2.wEffects & PFE_RTLPARA) &&
+ !base::i18n::StringContainsStrongRTLChars(GetText());
+ const int length = GetTextLength();
+ RECT r;
+ GetRect(&r);
+ // The values returned by PosFromChar() seem to refer always
+ // to the left edge of the character's bounding box.
+ const LONG first_position_x = PosFromChar(0).x;
+ LONG min_x = first_position_x;
+ if (!ltr_text_in_ltr_layout) {
+ for (int i = 1; i < length; ++i)
+ min_x = std::min(min_x, PosFromChar(i).x);
+ }
+ const LONG left_bound = std::max(r.left, min_x);
+ // PosFromChar(length) is a phantom character past the end of the text. It is
+ // not necessarily a right bound; in RTL controls it may be a left bound. So
+ // treat it as a right bound only if it is to the right of the first
+ // character.
+ LONG right_bound = r.right;
+ LONG end_position_x = PosFromChar(length).x;
+ if (end_position_x >= first_position_x) {
+ right_bound = std::min(right_bound, end_position_x); // LTR case.
+ }
+ // For trailing characters that are 2 pixels wide or less (like "l" in some
+ // fonts), we have a problem:
+ // * Clicks on any pixel within the character will place the cursor before
+ // the character.
+ // * Clicks on the pixel just after the character will not allow triple-
+ // click to work properly (true for any last character width).
+ // So, we move to the last pixel of the character when this is a
+ // triple-click, and moving to one past the last pixel in all other
+ // scenarios. This way, all clicks that can move the cursor will place it at
+ // the end of the text, but triple-click will still work.
+ if (x < left_bound) {
+ return (is_triple_click && ltr_text_in_ltr_layout) ? left_bound - 1 :
+ left_bound;
+ }
+ if ((length == 0) || (x < right_bound))
+ return x;
+ return is_triple_click ? (right_bound - 1) : right_bound;
+}
+
+int OmniboxViewWin::GetOmniboxTextLength() const {
+ return static_cast<int>(GetTextLength());
+}
+
+void OmniboxViewWin::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::Component scheme, host;
+ AutocompleteInput::ParseForEmphasizeComponents(
+ GetText(), model()->GetDesiredTLD(), &scheme, &host);
+ const bool emphasize = model()->CurrentTextIsURL() && (host.len > 0);
+
+ // Set the baseline emphasis.
+ CHARFORMAT cf = {0};
+ cf.dwMask = CFM_COLOR;
+ // If we're going to emphasize parts of the text, then the baseline state
+ // should be "de-emphasized". If not, then everything should be rendered in
+ // the standard text color.
+ cf.crTextColor = skia::SkColorToCOLORREF(parent_view_->GetColor(
+ security_level_,
+ emphasize ? LocationBarView::DEEMPHASIZED_TEXT : LocationBarView::TEXT));
+ // NOTE: Don't use SetDefaultCharFormat() instead of the below; that sets the
+ // format that will get applied to text added in the future, not to text
+ // already in the edit.
+ SelectAll(false);
+ SetSelectionCharFormat(cf);
+
+ if (emphasize) {
+ // We've found a host name, give it more emphasis.
+ cf.crTextColor = skia::SkColorToCOLORREF(parent_view_->GetColor(
+ security_level_, LocationBarView::TEXT));
+ SetSelection(host.begin, host.end());
+ SetSelectionCharFormat(cf);
+ }
+
+ // Emphasize the scheme for security UI display purposes (if necessary).
+ insecure_scheme_component_.reset();
+ if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() &&
+ scheme.is_nonempty() && (security_level_ != ToolbarModel::NONE)) {
+ if (security_level_ == ToolbarModel::SECURITY_ERROR) {
+ insecure_scheme_component_.begin = scheme.begin;
+ insecure_scheme_component_.len = scheme.len;
+ }
+ cf.crTextColor = skia::SkColorToCOLORREF(parent_view_->GetColor(
+ security_level_, LocationBarView::SECURITY_TEXT));
+ SetSelection(scheme.begin, scheme.end());
+ SetSelectionCharFormat(cf);
+ }
+
+ // Restore the selection.
+ SetSelectionRange(saved_sel);
+}
+
+void OmniboxViewWin::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 OmniboxViewWin::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_.GetBaseline() - font_x_height_ -
+ kAdditionalSpaceOutsideFont,
+ PosFromChar(insecure_scheme_component_.end()).x,
+ font_top + font_.GetBaseline() +
+ 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.
+ gfx::Canvas canvas(gfx::Size(scheme_rect.Width(), scheme_rect.Height()),
+ ui::SCALE_FACTOR_100P, false);
+ SkCanvas* sk_canvas = canvas.sk_canvas();
+ sk_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.
+ sk_canvas->save();
+ if (selection_rect.isEmpty() ||
+ sk_canvas->clipRect(selection_rect, SkRegion::kDifference_Op)) {
+ paint.setColor(parent_view_->GetColor(security_level_,
+ LocationBarView::SECURITY_TEXT));
+ sk_canvas->drawLine(start_point.fX, start_point.fY,
+ end_point.fX, end_point.fY, paint);
+ }
+ sk_canvas->restore();
+
+ // Draw the selected portion of the stroke.
+ if (!selection_rect.isEmpty() && sk_canvas->clipRect(selection_rect)) {
+ paint.setColor(parent_view_->GetColor(security_level_,
+ LocationBarView::SELECTED_TEXT));
+ sk_canvas->drawLine(start_point.fX, start_point.fY,
+ end_point.fX, end_point.fY, paint);
+ }
+
+ // Now copy what we drew to the target HDC.
+ skia::DrawToNativeContext(sk_canvas, 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 OmniboxViewWin::DrawDropHighlight(HDC hdc,
+ const CRect& client_rect,
+ const CRect& paint_clip_rect) {
+ DCHECK_NE(-1, drop_highlight_position_);
+
+ 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_.GetHeight());
+
+ // 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 OmniboxViewWin::TextChanged() {
+ ScopedFreeze freeze(this, GetTextObjectModel());
+ OmniboxView::TextChanged();
+}
+
+ITextDocument* OmniboxViewWin::GetTextObjectModel() const {
+ if (!text_object_model_) {
+ // This is lazily initialized, instead of being initialized in the
+ // constructor, in order to avoid hurting startup performance.
+ base::win::ScopedComPtr<IRichEditOle, NULL> ole_interface;
+ ole_interface.Attach(GetOleInterface());
+ if (ole_interface) {
+ ole_interface.QueryInterface(
+ __uuidof(ITextDocument),
+ reinterpret_cast<void**>(&text_object_model_));
+ }
+ }
+ return text_object_model_;
+}
+
+void OmniboxViewWin::StartDragIfNecessary(const CPoint& point) {
+ if (initiated_drag_ || !IsDrag(click_point_[kLeft], point))
+ return;
+
+ ui::OSExchangeData data;
+
+ 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(click_point_[kLeft].x, click_point_[kLeft].y));
+ SetSelectionRange(sel);
+ }
+
+ const string16 start_text(GetText());
+ string16 text_to_write(GetSelectedText());
+ GURL url;
+ bool write_url;
+ const bool is_all_selected = IsSelectAllForRange(sel);
+
+ // |sel| was set by GetSelection(), which preserves selection direction, so
+ // sel.cpMin may not be the smaller value.
+ model()->AdjustTextForCopy(std::min(sel.cpMin, sel.cpMax), is_all_selected,
+ &text_to_write, &url, &write_url);
+
+ if (write_url) {
+ string16 title;
+ gfx::Image favicon;
+ if (is_all_selected)
+ model()->GetDataForURLExport(&url, &title, &favicon);
+ button_drag_utils::SetURLAndDragImage(url, title, favicon.AsImageSkia(),
+ &data, native_view_host_->GetWidget());
+ supported_modes |= DROPEFFECT_LINK;
+ content::RecordAction(UserMetricsAction("Omnibox_DragURL"));
+ } else {
+ supported_modes |= DROPEFFECT_MOVE;
+ content::RecordAction(UserMetricsAction("Omnibox_DragString"));
+ }
+
+ data.SetString(text_to_write);
+
+ scoped_refptr<ui::DragSource> drag_source(new ui::DragSource);
+ DWORD dropped_mode;
+ base::AutoReset<bool> auto_reset_in_drag(&in_drag_, true);
+ if (DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(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)));
+ }
+
+ initiated_drag_ = true;
+ tracking_click_[kLeft] = false;
+}
+
+void OmniboxViewWin::OnPossibleDrag(const CPoint& point) {
+ if (possible_drag_)
+ return;
+
+ click_point_[kLeft] = 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 OmniboxViewWin::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_.GetHeight() + font_y_adjustment_};
+ InvalidateRect(&highlight_bounds, false);
+ }
+}
+
+void OmniboxViewWin::BuildContextMenu() {
+ if (context_menu_contents_.get())
+ return;
+
+ context_menu_contents_.reset(new ui::SimpleMenuModel(this));
+ // Set up context menu.
+ if (popup_window_mode_) {
+ context_menu_contents_->AddItemWithStringId(IDC_COPY, IDS_COPY);
+ } else {
+ context_menu_contents_->AddItemWithStringId(IDS_UNDO, IDS_UNDO);
+ context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR);
+ context_menu_contents_->AddItemWithStringId(IDC_CUT, IDS_CUT);
+ context_menu_contents_->AddItemWithStringId(IDC_COPY, IDS_COPY);
+ if (chrome::search::IsQueryExtractionEnabled())
+ context_menu_contents_->AddItemWithStringId(IDC_COPY_URL, IDS_COPY_URL);
+ context_menu_contents_->AddItemWithStringId(IDC_PASTE, IDS_PASTE);
+ // GetContextualLabel() will override this next label with the
+ // IDS_PASTE_AND_SEARCH label as needed.
+ context_menu_contents_->AddItemWithStringId(IDS_PASTE_AND_GO,
+ IDS_PASTE_AND_GO);
+ context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR);
+ context_menu_contents_->AddItemWithStringId(IDS_SELECT_ALL, IDS_SELECT_ALL);
+ context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR);
+ context_menu_contents_->AddItemWithStringId(IDS_EDIT_SEARCH_ENGINES,
+ IDS_EDIT_SEARCH_ENGINES);
+ }
+}
+
+void OmniboxViewWin::SelectAllIfNecessary(MouseButton button,
+ const CPoint& point) {
+ // When the user has clicked and released to give us focus, select all.
+ if (tracking_click_[button] &&
+ !IsDrag(click_point_[button], 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;
+ }
+}
+
+void OmniboxViewWin::TrackMousePosition(MouseButton button,
+ 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_[button] = true;
+ click_point_[button] = point;
+ }
+}
+
+int OmniboxViewWin::GetHorizontalMargin() const {
+ RECT rect;
+ GetRect(&rect);
+ RECT client_rect;
+ GetClientRect(&client_rect);
+ return (rect.left - client_rect.left) + (client_rect.right - rect.right);
+}
+
+int OmniboxViewWin::WidthNeededToDisplay(const string16& text) const {
+ // Use font_.GetStringWidth() instead of
+ // PosFromChar(location_entry_->GetTextLength()) because PosFromChar() is
+ // apparently buggy. In both LTR UI and RTL UI with left-to-right layout,
+ // PosFromChar(i) might return 0 when i is greater than 1.
+ return font_.GetStringWidth(text) + GetHorizontalMargin();
+}
« no previous file with comments | « no previous file | content/browser/renderer_host/render_widget_host_view_win.cc » ('j') | ui/base/win/touch_input.h » ('J')

Powered by Google App Engine
This is Rietveld 408576698