| 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();
|
| +}
|
|
|