omnibox_view_win.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
1// Copyright 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/ui/views/omnibox/omnibox_view_win.h" 6 7#include <algorithm> 8#include <locale> 9#include <string> 10 11#include <richedit.h> 12#include <textserv.h> 13 14#include "base/auto_reset.h" 15#include "base/basictypes.h" 16#include "base/bind.h" 17#include "base/i18n/rtl.h" 18#include "base/lazy_instance.h" 19#include "base/memory/ref_counted.h" 20#include "base/strings/string_util.h" 21#include "base/strings/utf_string_conversions.h" 22#include "base/win/iat_patch_function.h" 23#include "base/win/metro.h" 24#include "base/win/scoped_hdc.h" 25#include "base/win/scoped_select_object.h" 26#include "base/win/windows_version.h" 27#include "chrome/app/chrome_command_ids.h" 28#include "chrome/browser/autocomplete/autocomplete_input.h" 29#include "chrome/browser/autocomplete/autocomplete_match.h" 30#include "chrome/browser/autocomplete/keyword_provider.h" 31#include "chrome/browser/bookmarks/bookmark_node_data.h" 32#include "chrome/browser/command_updater.h" 33#include "chrome/browser/net/url_fixer_upper.h" 34#include "chrome/browser/profiles/profile.h" 35#include "chrome/browser/search/search.h" 36#include "chrome/browser/ui/browser.h" 37#include "chrome/browser/ui/omnibox/omnibox_edit_controller.h" 38#include "chrome/browser/ui/omnibox/omnibox_edit_model.h" 39#include "chrome/browser/ui/omnibox/omnibox_popup_model.h" 40#include "chrome/browser/ui/views/location_bar/location_bar_view.h" 41#include "chrome/browser/ui/views/omnibox/omnibox_view_views.h" 42#include "chrome/common/chrome_notification_types.h" 43#include "content/public/browser/user_metrics.h" 44#include "content/public/browser/web_contents.h" 45#include "extensions/common/constants.h" 46#include "googleurl/src/url_util.h" 47#include "grit/generated_resources.h" 48#include "net/base/escape.h" 49#include "skia/ext/skia_utils_win.h" 50#include "ui/base/accessibility/accessible_view_state.h" 51#include "ui/base/clipboard/clipboard.h" 52#include "ui/base/clipboard/scoped_clipboard_writer.h" 53#include "ui/base/dragdrop/drag_drop_types.h" 54#include "ui/base/dragdrop/drag_source_win.h" 55#include "ui/base/dragdrop/drop_target_win.h" 56#include "ui/base/dragdrop/os_exchange_data.h" 57#include "ui/base/dragdrop/os_exchange_data_provider_win.h" 58#include "ui/base/events/event.h" 59#include "ui/base/events/event_constants.h" 60#include "ui/base/ime/win/tsf_bridge.h" 61#include "ui/base/ime/win/tsf_event_router.h" 62#include "ui/base/keycodes/keyboard_codes.h" 63#include "ui/base/l10n/l10n_util.h" 64#include "ui/base/l10n/l10n_util_win.h" 65#include "ui/base/win/hwnd_util.h" 66#include "ui/base/win/mouse_wheel_util.h" 67#include "ui/base/win/touch_input.h" 68#include "ui/gfx/canvas.h" 69#include "ui/gfx/image/image.h" 70#include "ui/views/button_drag_utils.h" 71#include "ui/views/controls/menu/menu_item_view.h" 72#include "ui/views/controls/menu/menu_runner.h" 73#include "ui/views/controls/textfield/native_textfield_win.h" 74#include "ui/views/widget/widget.h" 75#include "win8/util/win8_util.h" 76 77#pragma comment(lib, "oleacc.lib") // Needed for accessibility support. 78 79using content::UserMetricsAction; 80using content::WebContents; 81 82namespace { 83 84const char kAutocompleteEditStateKey[] = "AutocompleteEditState"; 85 86// msftedit.dll is RichEdit ver 4.1. 87// This version is available from WinXP SP1 and has TSF support. 88const wchar_t* kRichEditDLLName = L"msftedit.dll"; 89 90// A helper method for determining a valid DROPEFFECT given the allowed 91// DROPEFFECTS. We prefer copy over link. 92DWORD CopyOrLinkDropEffect(DWORD effect) { 93 if (effect & DROPEFFECT_COPY) 94 return DROPEFFECT_COPY; 95 if (effect & DROPEFFECT_LINK) 96 return DROPEFFECT_LINK; 97 return DROPEFFECT_NONE; 98} 99 100// A helper method for determining a valid drag operation given the allowed 101// operation. We prefer copy over link. 102int CopyOrLinkDragOperation(int drag_operation) { 103 if (drag_operation & ui::DragDropTypes::DRAG_COPY) 104 return ui::DragDropTypes::DRAG_COPY; 105 if (drag_operation & ui::DragDropTypes::DRAG_LINK) 106 return ui::DragDropTypes::DRAG_LINK; 107 return ui::DragDropTypes::DRAG_NONE; 108} 109 110// The AutocompleteEditState struct contains enough information about the 111// OmniboxEditModel and OmniboxViewWin to save/restore a user's 112// typing, caret position, etc. across tab changes. We explicitly don't 113// preserve things like whether the popup was open as this might be weird. 114struct AutocompleteEditState : public base::SupportsUserData::Data { 115 AutocompleteEditState(const OmniboxEditModel::State& model_state, 116 const OmniboxViewWin::State& view_state) 117 : model_state(model_state), 118 view_state(view_state) { 119 } 120 virtual ~AutocompleteEditState() {} 121 122 const OmniboxEditModel::State model_state; 123 const OmniboxViewWin::State view_state; 124}; 125 126// Returns true if the current point is far enough from the origin that it 127// would be considered a drag. 128bool IsDrag(const POINT& origin, const POINT& current) { 129 return views::View::ExceededDragThreshold( 130 gfx::Point(current) - gfx::Point(origin)); 131} 132 133// Copies |selected_text| as text to the primary clipboard. 134void DoCopyText(const string16& selected_text) { 135 ui::ScopedClipboardWriter scw(ui::Clipboard::GetForCurrentThread(), 136 ui::Clipboard::BUFFER_STANDARD); 137 scw.WriteText(selected_text); 138} 139 140// Writes |url| and |text| to the clipboard as a well-formed URL. 141void DoCopyURL(const GURL& url, const string16& text) { 142 BookmarkNodeData data; 143 data.ReadFromTuple(url, text); 144 data.WriteToClipboard(); 145} 146 147} // namespace 148 149// EditDropTarget is the IDropTarget implementation installed on 150// OmniboxViewWin. EditDropTarget prefers URL over plain text. A drop 151// of a URL replaces all the text of the edit and navigates immediately to the 152// URL. A drop of plain text from the same edit either copies or moves the 153// selected text, and a drop of plain text from a source other than the edit 154// does a paste and go. 155class OmniboxViewWin::EditDropTarget : public ui::DropTargetWin { 156 public: 157 explicit EditDropTarget(OmniboxViewWin* edit); 158 159 protected: 160 virtual DWORD OnDragEnter(IDataObject* data_object, 161 DWORD key_state, 162 POINT cursor_position, 163 DWORD effect); 164 virtual DWORD OnDragOver(IDataObject* data_object, 165 DWORD key_state, 166 POINT cursor_position, 167 DWORD effect); 168 virtual void OnDragLeave(IDataObject* data_object); 169 virtual DWORD OnDrop(IDataObject* data_object, 170 DWORD key_state, 171 POINT cursor_position, 172 DWORD effect); 173 174 private: 175 // If dragging a string, the drop highlight position of the edit is reset 176 // based on the mouse position. 177 void UpdateDropHighlightPosition(const POINT& cursor_screen_position); 178 179 // Resets the visual drop indicates we install on the edit. 180 void ResetDropHighlights(); 181 182 // The edit we're the drop target for. 183 OmniboxViewWin* edit_; 184 185 // If true, the drag session contains a URL. 186 bool drag_has_url_; 187 188 // If true, the drag session contains a string. If drag_has_url_ is true, 189 // this is false regardless of whether the clipboard has a string. 190 bool drag_has_string_; 191 192 DISALLOW_COPY_AND_ASSIGN(EditDropTarget); 193}; 194 195OmniboxViewWin::EditDropTarget::EditDropTarget(OmniboxViewWin* edit) 196 : ui::DropTargetWin(edit->m_hWnd), 197 edit_(edit), 198 drag_has_url_(false), 199 drag_has_string_(false) { 200} 201 202DWORD OmniboxViewWin::EditDropTarget::OnDragEnter(IDataObject* data_object, 203 DWORD key_state, 204 POINT cursor_position, 205 DWORD effect) { 206 ui::OSExchangeData os_data(new ui::OSExchangeDataProviderWin(data_object)); 207 drag_has_url_ = os_data.HasURL(); 208 drag_has_string_ = !drag_has_url_ && os_data.HasString(); 209 if (drag_has_url_) { 210 if (edit_->in_drag()) { 211 // The edit we're associated with originated the drag. No point in 212 // allowing the user to drop back on us. 213 drag_has_url_ = false; 214 } 215 // NOTE: it would be nice to visually show all the text is going to 216 // be replaced by selecting all, but this caused painting problems. In 217 // particular the flashing caret would appear outside the edit! For now 218 // we stick with no visual indicator other than that shown own the mouse 219 // cursor. 220 } 221 return OnDragOver(data_object, key_state, cursor_position, effect); 222} 223 224DWORD OmniboxViewWin::EditDropTarget::OnDragOver(IDataObject* data_object, 225 DWORD key_state, 226 POINT cursor_position, 227 DWORD effect) { 228 if (drag_has_url_) 229 return CopyOrLinkDropEffect(effect); 230 231 if (drag_has_string_) { 232 UpdateDropHighlightPosition(cursor_position); 233 if (edit_->drop_highlight_position() == -1 && edit_->in_drag()) 234 return DROPEFFECT_NONE; 235 if (edit_->in_drag()) { 236 // The edit we're associated with originated the drag. Do the normal drag 237 // behavior. 238 DCHECK((effect & DROPEFFECT_COPY) && (effect & DROPEFFECT_MOVE)); 239 return (key_state & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE; 240 } 241 // Our edit didn't originate the drag, only allow link or copy. 242 return CopyOrLinkDropEffect(effect); 243 } 244 245 return DROPEFFECT_NONE; 246} 247 248void OmniboxViewWin::EditDropTarget::OnDragLeave(IDataObject* data_object) { 249 ResetDropHighlights(); 250} 251 252DWORD OmniboxViewWin::EditDropTarget::OnDrop(IDataObject* data_object, 253 DWORD key_state, 254 POINT cursor_position, 255 DWORD effect) { 256 effect = OnDragOver(data_object, key_state, cursor_position, effect); 257 258 ui::OSExchangeData os_data(new ui::OSExchangeDataProviderWin(data_object)); 259 gfx::Point point(cursor_position.x, cursor_position.y); 260 ui::DropTargetEvent event( 261 os_data, point, point, 262 ui::DragDropTypes::DropEffectToDragOperation(effect)); 263 264 int drag_operation = edit_->OnPerformDropImpl(event, edit_->in_drag()); 265 266 if (!drag_has_url_) 267 ResetDropHighlights(); 268 269 return ui::DragDropTypes::DragOperationToDropEffect(drag_operation); 270} 271 272void OmniboxViewWin::EditDropTarget::UpdateDropHighlightPosition( 273 const POINT& cursor_screen_position) { 274 if (drag_has_string_) { 275 POINT client_position = cursor_screen_position; 276 ::ScreenToClient(edit_->m_hWnd, &client_position); 277 int drop_position = edit_->CharFromPos(client_position); 278 if (edit_->in_drag()) { 279 // Our edit originated the drag, don't allow a drop if over the selected 280 // region. 281 LONG sel_start, sel_end; 282 edit_->GetSel(sel_start, sel_end); 283 if ((sel_start != sel_end) && (drop_position >= sel_start) && 284 (drop_position <= sel_end)) 285 drop_position = -1; 286 } else { 287 // A drop from a source other than the edit replaces all the text, so 288 // we don't show the drop location. See comment in OnDragEnter as to why 289 // we don't try and select all here. 290 drop_position = -1; 291 } 292 edit_->SetDropHighlightPosition(drop_position); 293 } 294} 295 296void OmniboxViewWin::EditDropTarget::ResetDropHighlights() { 297 if (drag_has_string_) 298 edit_->SetDropHighlightPosition(-1); 299} 300 301/////////////////////////////////////////////////////////////////////////////// 302// Helper classes 303 304OmniboxViewWin::ScopedFreeze::ScopedFreeze(OmniboxViewWin* edit, 305 ITextDocument* text_object_model) 306 : edit_(edit), 307 text_object_model_(text_object_model) { 308 // Freeze the screen. 309 if (text_object_model_) { 310 long count; 311 text_object_model_->Freeze(&count); 312 } 313} 314 315OmniboxViewWin::ScopedFreeze::~ScopedFreeze() { 316 // Unfreeze the screen. 317 // NOTE: If this destructor is reached while the edit is being destroyed (for 318 // example, because we double-clicked the edit of a popup and caused it to 319 // transform to an unconstrained window), it will no longer have an HWND, and 320 // text_object_model_ may point to a destroyed object, so do nothing here. 321 if (edit_->IsWindow() && text_object_model_) { 322 long count; 323 text_object_model_->Unfreeze(&count); 324 if (count == 0) { 325 // We need to UpdateWindow() here in addition to InvalidateRect() because, 326 // as far as I can tell, the edit likes to synchronously erase its 327 // background when unfreezing, thus requiring us to synchronously redraw 328 // if we don't want flicker. 329 edit_->InvalidateRect(NULL, false); 330 edit_->UpdateWindow(); 331 } 332 } 333} 334 335OmniboxViewWin::ScopedSuspendUndo::ScopedSuspendUndo( 336 ITextDocument* text_object_model) 337 : text_object_model_(text_object_model) { 338 // Suspend Undo processing. 339 if (text_object_model_) 340 text_object_model_->Undo(tomSuspend, NULL); 341} 342 343OmniboxViewWin::ScopedSuspendUndo::~ScopedSuspendUndo() { 344 // Resume Undo processing. 345 if (text_object_model_) 346 text_object_model_->Undo(tomResume, NULL); 347} 348 349// A subclass of NativeViewHost that provides accessibility info for the 350// underlying Omnibox view. 351class OmniboxViewWrapper : public views::NativeViewHost { 352 public: 353 explicit OmniboxViewWrapper(OmniboxViewWin* omnibox_view_win) 354 : omnibox_view_win_(omnibox_view_win) {} 355 356 gfx::NativeViewAccessible GetNativeViewAccessible() { 357 // This forces it to use NativeViewAccessibilityWin rather than 358 // any accessibility provided natively by the HWND. 359 return View::GetNativeViewAccessible(); 360 } 361 362 // views::View 363 virtual void GetAccessibleState(ui::AccessibleViewState* state) { 364 views::NativeViewHost::GetAccessibleState(state); 365 state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_LOCATION); 366 state->role = ui::AccessibilityTypes::ROLE_TEXT; 367 state->value = omnibox_view_win_->GetText(); 368 state->state = ui::AccessibilityTypes::STATE_EDITABLE; 369 size_t sel_start; 370 size_t sel_end; 371 omnibox_view_win_->GetSelectionBounds(&sel_start, &sel_end); 372 state->selection_start = sel_start; 373 state->selection_end = sel_end; 374 } 375 376 private: 377 OmniboxViewWin* omnibox_view_win_; 378 379 DISALLOW_COPY_AND_ASSIGN(OmniboxViewWrapper); 380}; 381 382/////////////////////////////////////////////////////////////////////////////// 383// OmniboxViewWin 384 385namespace { 386 387// These are used to hook the CRichEditCtrl's calls to BeginPaint() and 388// EndPaint() and provide a memory DC instead. See OnPaint(). 389HWND edit_hwnd = NULL; 390PAINTSTRUCT paint_struct; 391 392// Intercepted method for BeginPaint(). Must use __stdcall convention. 393HDC WINAPI BeginPaintIntercept(HWND hWnd, LPPAINTSTRUCT lpPaint) { 394 if (!edit_hwnd || (hWnd != edit_hwnd)) 395 return ::BeginPaint(hWnd, lpPaint); 396 397 *lpPaint = paint_struct; 398 return paint_struct.hdc; 399} 400 401// Intercepted method for EndPaint(). Must use __stdcall convention. 402BOOL WINAPI EndPaintIntercept(HWND hWnd, const PAINTSTRUCT* lpPaint) { 403 return (edit_hwnd && (hWnd == edit_hwnd)) || ::EndPaint(hWnd, lpPaint); 404} 405 406class PaintPatcher { 407 public: 408 PaintPatcher(); 409 ~PaintPatcher(); 410 411 void RefPatch(); 412 void DerefPatch(); 413 414 private: 415 size_t refcount_; 416 base::win::IATPatchFunction begin_paint_; 417 base::win::IATPatchFunction end_paint_; 418 419 DISALLOW_COPY_AND_ASSIGN(PaintPatcher); 420}; 421 422PaintPatcher::PaintPatcher() : refcount_(0) { 423} 424 425PaintPatcher::~PaintPatcher() { 426 DCHECK_EQ(0U, refcount_); 427} 428 429void PaintPatcher::RefPatch() { 430 if (refcount_ == 0) { 431 DCHECK(!begin_paint_.is_patched()); 432 DCHECK(!end_paint_.is_patched()); 433 begin_paint_.Patch(kRichEditDLLName, "user32.dll", "BeginPaint", 434 &BeginPaintIntercept); 435 end_paint_.Patch(kRichEditDLLName, "user32.dll", "EndPaint", 436 &EndPaintIntercept); 437 } 438 ++refcount_; 439} 440 441void PaintPatcher::DerefPatch() { 442 DCHECK(begin_paint_.is_patched()); 443 DCHECK(end_paint_.is_patched()); 444 --refcount_; 445 if (refcount_ == 0) { 446 begin_paint_.Unpatch(); 447 end_paint_.Unpatch(); 448 } 449} 450 451base::LazyInstance<PaintPatcher> g_paint_patcher = LAZY_INSTANCE_INITIALIZER; 452 453// twips are a unit of type measurement, and RichEdit controls use them 454// to set offsets. 455const int kTwipsPerInch = 1440; 456 457} // namespace 458 459HMODULE OmniboxViewWin::loaded_library_module_ = NULL; 460 461OmniboxViewWin::OmniboxViewWin(OmniboxEditController* controller, 462 ToolbarModel* toolbar_model, 463 LocationBarView* location_bar, 464 CommandUpdater* command_updater, 465 bool popup_window_mode, 466 const gfx::Font& font, 467 int font_y_offset) 468 : OmniboxView(location_bar->profile(), controller, toolbar_model, 469 command_updater), 470 popup_view_( 471 OmniboxPopupContentsView::Create(font, this, model(), location_bar)), 472 location_bar_(location_bar), 473 popup_window_mode_(popup_window_mode), 474 force_hidden_(false), 475 tracking_click_(), 476 tracking_double_click_(false), 477 double_click_time_(0), 478 can_discard_mousemove_(false), 479 ignore_ime_messages_(false), 480 delete_at_end_pressed_(false), 481 font_(font), 482 font_y_adjustment_(font_y_offset), 483 possible_drag_(false), 484 in_drag_(false), 485 initiated_drag_(false), 486 drop_highlight_position_(-1), 487 ime_candidate_window_open_(false), 488 background_color_(skia::SkColorToCOLORREF(location_bar->GetColor( 489 ToolbarModel::NONE, LocationBarView::BACKGROUND))), 490 security_level_(ToolbarModel::NONE), 491 text_object_model_(NULL), 492 tsf_event_router_(base::win::IsTSFAwareRequired() ? 493 new ui::TSFEventRouter(this) : NULL) { 494 if (!loaded_library_module_) 495 loaded_library_module_ = LoadLibrary(kRichEditDLLName); 496 // RichEdit should be available; rare exceptions should use the Views omnibox. 497 DCHECK(loaded_library_module_); 498 499 saved_selection_for_focus_change_.cpMin = -1; 500 501 g_paint_patcher.Pointer()->RefPatch(); 502 503 Create(location_bar->GetWidget()->GetNativeView(), 0, 0, 0, 504 l10n_util::GetExtendedStyles()); 505 SetReadOnly(popup_window_mode_); 506 gfx::NativeFont native_font(font_.GetNativeFont()); 507 SetFont(native_font); 508 509 // IMF_DUALFONT (on by default) is supposed to use one font for ASCII text 510 // and a different one for Asian text. In some cases, ASCII characters may 511 // be mis-marked as Asian, e.g. because input from the keyboard may be 512 // auto-stamped with the keyboard language. As a result adjacent characters 513 // can render in different fonts, which looks bizarre. To fix this we 514 // disable dual-font mode, which forces the control to use a single font for 515 // everything. 516 // Note that we should not disable the very similar IMF_AUTOFONT flag, which 517 // allows the control to hunt for fonts that can display all the current 518 // characters; doing this results in "missing glyph" boxes when the user 519 // enters characters not available in the currently-chosen font. 520 const LRESULT lang_options = SendMessage(m_hWnd, EM_GETLANGOPTIONS, 0, 0); 521 SendMessage(m_hWnd, EM_SETLANGOPTIONS, 0, lang_options & ~IMF_DUALFONT); 522 523 // NOTE: Do not use SetWordBreakProcEx() here, that is no longer supported as 524 // of Rich Edit 2.0 onward. 525 SendMessage(m_hWnd, EM_SETWORDBREAKPROC, 0, 526 reinterpret_cast<LPARAM>(&WordBreakProc)); 527 528 // Get the metrics for the font. 529 base::win::ScopedGetDC screen_dc(NULL); 530 base::win::ScopedSelectObject font_in_dc(screen_dc, native_font); 531 TEXTMETRIC tm = {0}; 532 GetTextMetrics(screen_dc, &tm); 533 int cap_height = font_.GetBaseline() - tm.tmInternalLeading; 534 // The ratio of a font's x-height to its cap height. Sadly, Windows 535 // doesn't provide a true value for a font's x-height in its text 536 // metrics, so we approximate. 537 const float kXHeightRatio = 0.7f; 538 font_x_height_ = static_cast<int>( 539 (static_cast<float>(cap_height) * kXHeightRatio) + 0.5); 540 541 // Get the number of twips per pixel, which we need below to offset our text 542 // by the desired number of pixels. 543 const long kTwipsPerPixel = 544 kTwipsPerInch / GetDeviceCaps(screen_dc, LOGPIXELSY); 545 546 // Set the default character style -- adjust to our desired baseline. 547 CHARFORMAT cf = {0}; 548 cf.dwMask = CFM_OFFSET; 549 cf.yOffset = -font_y_adjustment_ * kTwipsPerPixel; 550 SetDefaultCharFormat(cf); 551 552 SetBackgroundColor(background_color_); 553 554 if (!popup_window_mode_) { 555 // Non-read-only edit controls have a drop target. Revoke it so that we can 556 // install our own. Revoking automatically deletes the existing one. 557 HRESULT hr = RevokeDragDrop(m_hWnd); 558 DCHECK_EQ(S_OK, hr); 559 560 // Register our drop target. The scoped_refptr here will delete the drop 561 // target if it fails to register itself correctly on |m_hWnd|. Otherwise, 562 // the edit control will invoke RevokeDragDrop when it's being destroyed, so 563 // we don't have to do so. 564 scoped_refptr<EditDropTarget> drop_target(new EditDropTarget(this)); 565 } 566} 567 568OmniboxViewWin::~OmniboxViewWin() { 569 // Explicitly release the text object model now that we're done with it, and 570 // before we free the library. If the library gets unloaded before this 571 // released, it becomes garbage. Note that since text_object_model_ is lazy 572 // initialized, it may still be null. 573 if (text_object_model_) 574 text_object_model_->Release(); 575 576 // We balance our reference count and unpatch when the last instance has 577 // been destroyed. This prevents us from relying on the AtExit or static 578 // destructor sequence to do our unpatching, which is generally fragile. 579 g_paint_patcher.Pointer()->DerefPatch(); 580} 581 582views::View* OmniboxViewWin::parent_view() const { 583 return location_bar_; 584} 585 586void OmniboxViewWin::SaveStateToTab(WebContents* tab) { 587 DCHECK(tab); 588 589 const OmniboxEditModel::State model_state(model()->GetStateForTabSwitch()); 590 591 CHARRANGE selection; 592 GetSelection(selection); 593 tab->SetUserData( 594 kAutocompleteEditStateKey, 595 new AutocompleteEditState( 596 model_state, 597 State(selection, saved_selection_for_focus_change_))); 598} 599 600void OmniboxViewWin::Update(const WebContents* tab_for_state_restoring) { 601 const bool visibly_changed_permanent_text = 602 model()->UpdatePermanentText(toolbar_model()->GetText(true)); 603 604 const ToolbarModel::SecurityLevel security_level = 605 toolbar_model()->GetSecurityLevel(); 606 const bool changed_security_level = (security_level != security_level_); 607 608 // Bail early when no visible state will actually change (prevents an 609 // unnecessary ScopedFreeze, and thus UpdateWindow()). 610 if (!changed_security_level && !visibly_changed_permanent_text && 611 !tab_for_state_restoring) 612 return; 613 614 // Update our local state as desired. We set security_level_ here so it will 615 // already be correct before we get to any RevertAll()s below and use it. 616 security_level_ = security_level; 617 618 // When we're switching to a new tab, restore its state, if any. 619 ScopedFreeze freeze(this, GetTextObjectModel()); 620 if (tab_for_state_restoring) { 621 // Make sure we reset our own state first. The new tab may not have any 622 // saved state, or it may not have had input in progress, in which case we 623 // won't overwrite all our local state. 624 RevertAll(); 625 626 const AutocompleteEditState* state = static_cast<AutocompleteEditState*>( 627 tab_for_state_restoring->GetUserData(&kAutocompleteEditStateKey)); 628 if (state) { 629 model()->RestoreState(state->model_state); 630 631 // Restore user's selection. We do this after restoring the user_text 632 // above so we're selecting in the correct string. 633 SetSelectionRange(state->view_state.selection); 634 saved_selection_for_focus_change_ = 635 state->view_state.saved_selection_for_focus_change; 636 } 637 } else if (visibly_changed_permanent_text) { 638 // Not switching tabs, just updating the permanent text. (In the case where 639 // we _were_ switching tabs, the RevertAll() above already drew the new 640 // permanent text.) 641 642 // Tweak: if the edit was previously nonempty and had all the text selected, 643 // select all the new text. This makes one particular case better: the 644 // user clicks in the box to change it right before the permanent URL is 645 // changed. Since the new URL is still fully selected, the user's typing 646 // will replace the edit contents as they'd intended. 647 // 648 // NOTE: The selection can be longer than the text length if the edit is in 649 // in rich text mode and the user has selected the "phantom newline" at the 650 // end, so use ">=" instead of "==" to see if all the text is selected. In 651 // theory we prevent this case from ever occurring, but this is still safe. 652 CHARRANGE sel; 653 GetSelection(sel); 654 const bool was_reversed = (sel.cpMin > sel.cpMax); 655 const bool was_sel_all = (sel.cpMin != sel.cpMax) && 656 IsSelectAllForRange(sel); 657 658 RevertAll(); 659 660 if (was_sel_all) 661 SelectAll(was_reversed); 662 } else if (changed_security_level) { 663 // Only the security style changed, nothing else. Redraw our text using it. 664 EmphasizeURLComponents(); 665 } 666} 667 668void OmniboxViewWin::OpenMatch(const AutocompleteMatch& match, 669 WindowOpenDisposition disposition, 670 const GURL& alternate_nav_url, 671 size_t selected_line) { 672 // When we navigate, we first revert to the unedited state, then if necessary 673 // synchronously change the permanent text to the new URL. If we don't freeze 674 // here, the user could potentially see a flicker of the current URL before 675 // the new one reappears, which would look glitchy. 676 ScopedFreeze freeze(this, GetTextObjectModel()); 677 OmniboxView::OpenMatch(match, disposition, alternate_nav_url, selected_line); 678} 679 680string16 OmniboxViewWin::GetText() const { 681 const int len = GetTextLength() + 1; 682 string16 str; 683 if (len > 1) 684 GetWindowText(WriteInto(&str, len), len); 685 return str; 686} 687 688void OmniboxViewWin::SetUserText(const string16& text, 689 const string16& display_text, 690 bool update_popup) { 691 ScopedFreeze freeze(this, GetTextObjectModel()); 692 saved_selection_for_focus_change_.cpMin = -1; 693 OmniboxView::SetUserText(text, display_text, update_popup); 694} 695 696void OmniboxViewWin::SetWindowTextAndCaretPos(const string16& text, 697 size_t caret_pos, 698 bool update_popup, 699 bool notify_text_changed) { 700 SetWindowText(text.c_str()); 701 PlaceCaretAt(caret_pos); 702 703 if (update_popup) 704 UpdatePopup(); 705 706 if (notify_text_changed) 707 TextChanged(); 708} 709 710void OmniboxViewWin::SetForcedQuery() { 711 const string16 current_text(GetText()); 712 const size_t start = current_text.find_first_not_of(kWhitespaceWide); 713 if (start == string16::npos || (current_text[start] != '?')) 714 OmniboxView::SetUserText(L"?"); 715 else 716 SetSelection(current_text.length(), start + 1); 717} 718 719bool OmniboxViewWin::IsSelectAll() const { 720 CHARRANGE selection; 721 GetSel(selection); 722 return IsSelectAllForRange(selection); 723} 724 725bool OmniboxViewWin::DeleteAtEndPressed() { 726 return delete_at_end_pressed_; 727} 728 729void OmniboxViewWin::GetSelectionBounds(string16::size_type* start, 730 string16::size_type* end) const { 731 CHARRANGE selection; 732 GetSel(selection); 733 *start = static_cast<size_t>(selection.cpMin); 734 *end = static_cast<size_t>(selection.cpMax); 735} 736 737void OmniboxViewWin::SelectAll(bool reversed) { 738 if (reversed) 739 SetSelection(GetTextLength(), 0); 740 else 741 SetSelection(0, GetTextLength()); 742} 743 744void OmniboxViewWin::RevertAll() { 745 ScopedFreeze freeze(this, GetTextObjectModel()); 746 saved_selection_for_focus_change_.cpMin = -1; 747 OmniboxView::RevertAll(); 748} 749 750void OmniboxViewWin::UpdatePopup() { 751 ScopedFreeze freeze(this, GetTextObjectModel()); 752 model()->SetInputInProgress(true); 753 754 // Don't allow the popup to open while the candidate window is open, so 755 // they don't overlap. 756 if (ime_candidate_window_open_) 757 return; 758 759 if (!model()->has_focus()) { 760 // When we're in the midst of losing focus, don't rerun autocomplete. This 761 // can happen when losing focus causes the IME to cancel/finalize a 762 // composition. We still want to note that user input is in progress, we 763 // just don't want to do anything else. 764 // 765 // Note that in this case the ScopedFreeze above was unnecessary; however, 766 // we're inside the callstack of OnKillFocus(), which has already frozen the 767 // edit, so this will never result in an unnecessary UpdateWindow() call. 768 return; 769 } 770 771 // Don't inline autocomplete when: 772 // * The user is deleting text 773 // * The caret/selection isn't at the end of the text 774 // * The user has just pasted in something that replaced all the text 775 // * The user is trying to compose something in an IME 776 CHARRANGE sel; 777 GetSel(sel); 778 model()->StartAutocomplete(sel.cpMax != sel.cpMin, 779 (sel.cpMax < GetTextLength()) || IsImeComposing()); 780} 781 782void OmniboxViewWin::SetFocus() { 783 ::SetFocus(m_hWnd); 784 // Restore caret visibility if focus is explicitly requested. This is 785 // necessary because if we already have invisible focus, the ::SetFocus() 786 // call above will short-circuit, preventing us from reaching 787 // OmniboxEditModel::OnSetFocus(), which handles restoring visibility when the 788 // omnibox regains focus after losing focus. 789 model()->SetCaretVisibility(true); 790} 791 792void OmniboxViewWin::ApplyCaretVisibility() { 793 // We hide the caret just before destroying it, since destroying a caret that 794 // is in the "solid" phase of its blinking will leave a solid vertical bar. 795 // We even hide and destroy the caret if we're going to create it again below. 796 // If the caret was already visible on entry to this function, the 797 // CreateCaret() call (which first destroys the old caret) might leave a solid 798 // vertical bar for the same reason as above. Unconditionally hiding prevents 799 // this. The caret could be visible on entry to this function if the 800 // underlying edit control had re-created it automatically (see comments in 801 // OnPaint()). 802 HideCaret(); 803 // We use DestroyCaret()/CreateCaret() instead of simply HideCaret()/ 804 // ShowCaret() because HideCaret() is not sticky across paint events, e.g. a 805 // window resize will effectively restore caret visibility, regardless of 806 // whether HideCaret() was called before. While we do catch and handle these 807 // paint events (see OnPaint()), it doesn't seem to be enough to simply call 808 // HideCaret() while handling them because of the unpredictability of this 809 // Windows API. According to the documentation, it should be a cumulative call 810 // e.g. 5 hide calls should be balanced by 5 show calls. We have not found 811 // this to be true, which may be explained by the fact that this API is called 812 // internally in Windows, as well. 813 ::DestroyCaret(); 814 if (model()->is_caret_visible()) { 815 ::CreateCaret(m_hWnd, (HBITMAP) NULL, 1, font_.GetHeight()); 816 // According to the Windows API documentation, a newly created caret needs 817 // ShowCaret to be visible. 818 ShowCaret(); 819 } 820} 821 822void OmniboxViewWin::SetDropHighlightPosition(int position) { 823 if (drop_highlight_position_ != position) { 824 RepaintDropHighlight(drop_highlight_position_); 825 drop_highlight_position_ = position; 826 RepaintDropHighlight(drop_highlight_position_); 827 } 828} 829 830void OmniboxViewWin::MoveSelectedText(int new_position) { 831 const string16 selected_text(GetSelectedText()); 832 CHARRANGE sel; 833 GetSel(sel); 834 DCHECK((sel.cpMax != sel.cpMin) && (new_position >= 0) && 835 (new_position <= GetTextLength())); 836 837 ScopedFreeze freeze(this, GetTextObjectModel()); 838 OnBeforePossibleChange(); 839 840 // Nuke the selected text. 841 ReplaceSel(L"", TRUE); 842 843 // And insert it into the new location. 844 if (new_position >= sel.cpMin) 845 new_position -= (sel.cpMax - sel.cpMin); 846 PlaceCaretAt(new_position); 847 ReplaceSel(selected_text.c_str(), TRUE); 848 849 OnAfterPossibleChange(); 850} 851 852void OmniboxViewWin::InsertText(int position, const string16& text) { 853 DCHECK((position >= 0) && (position <= GetTextLength())); 854 ScopedFreeze freeze(this, GetTextObjectModel()); 855 OnBeforePossibleChange(); 856 SetSelection(position, position); 857 ReplaceSel(text.c_str()); 858 OnAfterPossibleChange(); 859} 860 861void OmniboxViewWin::OnTemporaryTextMaybeChanged(const string16& display_text, 862 bool save_original_selection, 863 bool notify_text_changed) { 864 if (save_original_selection) 865 GetSelection(original_selection_); 866 867 // Set new text and cursor position. Sometimes this does extra work (e.g. 868 // when the new text and the old text are identical), but it's only called 869 // when the user manually changes the selected line in the popup, so that's 870 // not really a problem. Also, even when the text hasn't changed we'd want to 871 // update the caret, because if the user had the cursor in the middle of the 872 // text and then arrowed to another entry with the same text, we'd still want 873 // to move the caret. 874 ScopedFreeze freeze(this, GetTextObjectModel()); 875 SetWindowTextAndCaretPos(display_text, display_text.length(), false, 876 notify_text_changed); 877} 878 879bool OmniboxViewWin::OnInlineAutocompleteTextMaybeChanged( 880 const string16& display_text, 881 size_t user_text_length) { 882 // Update the text and selection. Because this can be called repeatedly while 883 // typing, we've careful not to freeze the edit unless we really need to. 884 // Also, unlike in the temporary text case above, here we don't want to update 885 // the caret/selection unless we have to, since this might make the user's 886 // caret position change without warning during typing. 887 if (display_text == GetText()) 888 return false; 889 890 ScopedFreeze freeze(this, GetTextObjectModel()); 891 SetWindowText(display_text.c_str()); 892 // Set a reversed selection to keep the caret in the same position, which 893 // avoids scrolling the user's text. 894 SetSelection(static_cast<LONG>(display_text.length()), 895 static_cast<LONG>(user_text_length)); 896 TextChanged(); 897 return true; 898} 899 900void OmniboxViewWin::OnRevertTemporaryText() { 901 SetSelectionRange(original_selection_); 902 // We got here because the user hit the Escape key. We explicitly don't call 903 // TextChanged(), since calling it breaks Instant-Extended, and isn't needed 904 // otherwise (in regular non-Instant or Instant-but-not-Extended modes). 905 // 906 // Why it breaks Instant-Extended: Instant handles the Escape key separately 907 // (cf: OmniboxEditModel::RevertTemporaryText). Calling TextChanged() makes 908 // the page think the user additionally typed some text, causing it to update 909 // its suggestions dropdown with new suggestions, which is wrong. 910 // 911 // Why it isn't needed: OmniboxPopupModel::ResetToDefaultMatch() has already 912 // been called by now; it would've called TextChanged() if it was warranted. 913} 914 915void OmniboxViewWin::OnBeforePossibleChange() { 916 // Record our state. 917 text_before_change_ = GetText(); 918 GetSelection(sel_before_change_); 919} 920 921bool OmniboxViewWin::OnAfterPossibleChange() { 922 return OnAfterPossibleChangeInternal(false); 923} 924 925bool OmniboxViewWin::OnAfterPossibleChangeInternal(bool force_text_changed) { 926 // Prevent the user from selecting the "phantom newline" at the end of the 927 // edit. If they try, we just silently move the end of the selection back to 928 // the end of the real text. 929 CHARRANGE new_sel; 930 GetSelection(new_sel); 931 const int length = GetTextLength(); 932 if ((new_sel.cpMin > length) || (new_sel.cpMax > length)) { 933 if (new_sel.cpMin > length) 934 new_sel.cpMin = length; 935 if (new_sel.cpMax > length) 936 new_sel.cpMax = length; 937 SetSelectionRange(new_sel); 938 } 939 const bool selection_differs = 940 ((new_sel.cpMin != new_sel.cpMax) || 941 (sel_before_change_.cpMin != sel_before_change_.cpMax)) && 942 ((new_sel.cpMin != sel_before_change_.cpMin) || 943 (new_sel.cpMax != sel_before_change_.cpMax)); 944 945 // See if the text or selection have changed since OnBeforePossibleChange(). 946 const string16 new_text(GetText()); 947 const bool text_differs = (new_text != text_before_change_) || 948 force_text_changed; 949 950 // When the user has deleted text, we don't allow inline autocomplete. Make 951 // sure to not flag cases like selecting part of the text and then pasting 952 // (or typing) the prefix of that selection. (We detect these by making 953 // sure the caret, which should be after any insertion, hasn't moved 954 // forward of the old selection start.) 955 const bool just_deleted_text = 956 (text_before_change_.length() > new_text.length()) && 957 (new_sel.cpMin <= std::min(sel_before_change_.cpMin, 958 sel_before_change_.cpMax)); 959 960 const bool something_changed = model()->OnAfterPossibleChange( 961 text_before_change_, new_text, new_sel.cpMin, new_sel.cpMax, 962 selection_differs, text_differs, just_deleted_text, !IsImeComposing()); 963 964 if (selection_differs) 965 controller()->OnSelectionBoundsChanged(); 966 967 if (something_changed && text_differs) 968 TextChanged(); 969 970 if (text_differs) { 971 // Note that a TEXT_CHANGED event implies that the cursor/selection 972 // probably changed too, so we don't need to send both. 973 native_view_host_->NotifyAccessibilityEvent( 974 ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true); 975 } else if (selection_differs) { 976 // Notify assistive technology that the cursor or selection changed. 977 native_view_host_->NotifyAccessibilityEvent( 978 ui::AccessibilityTypes::EVENT_SELECTION_CHANGED, true); 979 } else if (delete_at_end_pressed_) { 980 model()->OnChanged(); 981 } 982 983 return something_changed; 984} 985 986void OmniboxViewWin::OnCandidateWindowCountChanged(size_t window_count) { 987 ime_candidate_window_open_ = (window_count != 0); 988 if (ime_candidate_window_open_) { 989 CloseOmniboxPopup(); 990 } else if (model()->user_input_in_progress()) { 991 // UpdatePopup assumes user input is in progress, so only call it if 992 // that's the case. Otherwise, autocomplete may run on an empty user 993 // text. 994 UpdatePopup(); 995 } 996} 997 998void OmniboxViewWin::OnTextUpdated(const ui::Range& /*composition_range*/) { 999 if (ignore_ime_messages_) 1000 return; 1001 OnAfterPossibleChangeInternal(true); 1002 // Call OnBeforePossibleChange function here to get correct diff in next IME 1003 // update. The Text Services Framework does not provide any notification 1004 // before entering edit session, therefore we don't have good place to call 1005 // OnBeforePossibleChange. 1006 OnBeforePossibleChange(); 1007} 1008 1009gfx::NativeView OmniboxViewWin::GetNativeView() const { 1010 return m_hWnd; 1011} 1012 1013// static 1014gfx::NativeView OmniboxViewWin::GetRelativeWindowForNativeView( 1015 gfx::NativeView edit_native_view) { 1016 // When an IME is attached to the rich-edit control, retrieve its window 1017 // handle, and the popup window of OmniboxPopupView will be shown under the 1018 // IME windows. 1019 // Otherwise, the popup window will be shown under top-most windows. 1020 // TODO(hbono): http://b/1111369 if we exclude this popup window from the 1021 // display area of IME windows, this workaround becomes unnecessary. 1022 HWND ime_window = ImmGetDefaultIMEWnd(edit_native_view); 1023 return ime_window ? ime_window : HWND_NOTOPMOST; 1024} 1025 1026gfx::NativeView OmniboxViewWin::GetRelativeWindowForPopup() const { 1027 return GetRelativeWindowForNativeView(GetNativeView()); 1028} 1029 1030void OmniboxViewWin::SetInstantSuggestion(const string16& suggestion) { 1031 location_bar_->SetInstantSuggestion(suggestion); 1032} 1033 1034int OmniboxViewWin::TextWidth() const { 1035 return WidthNeededToDisplay(GetText()); 1036} 1037 1038string16 OmniboxViewWin::GetInstantSuggestion() const { 1039 return location_bar_->GetInstantSuggestion(); 1040} 1041 1042bool OmniboxViewWin::IsImeComposing() const { 1043 if (tsf_event_router_) 1044 return tsf_event_router_->IsImeComposing(); 1045 bool ime_composing = false; 1046 HIMC context = ImmGetContext(m_hWnd); 1047 if (context) { 1048 ime_composing = !!ImmGetCompositionString(context, GCS_COMPSTR, NULL, 0); 1049 ImmReleaseContext(m_hWnd, context); 1050 } 1051 return ime_composing; 1052} 1053 1054int OmniboxViewWin::GetMaxEditWidth(int entry_width) const { 1055 RECT formatting_rect; 1056 GetRect(&formatting_rect); 1057 RECT edit_bounds; 1058 GetClientRect(&edit_bounds); 1059 return entry_width - formatting_rect.left - 1060 (edit_bounds.right - formatting_rect.right); 1061} 1062 1063views::View* OmniboxViewWin::AddToView(views::View* parent) { 1064 native_view_host_ = new OmniboxViewWrapper(this); 1065 parent->AddChildView(native_view_host_); 1066 native_view_host_->set_focus_view(parent); 1067 native_view_host_->Attach(GetNativeView()); 1068 return native_view_host_; 1069} 1070 1071int OmniboxViewWin::OnPerformDrop(const ui::DropTargetEvent& event) { 1072 return OnPerformDropImpl(event, false); 1073} 1074 1075int OmniboxViewWin::OnPerformDropImpl(const ui::DropTargetEvent& event, 1076 bool in_drag) { 1077 const ui::OSExchangeData& data = event.data(); 1078 1079 if (data.HasURL()) { 1080 GURL url; 1081 string16 title; 1082 if (data.GetURLAndTitle(&url, &title)) { 1083 string16 text(StripJavascriptSchemas(UTF8ToUTF16(url.spec()))); 1084 OmniboxView::SetUserText(text); 1085 model()->AcceptInput(CURRENT_TAB, true); 1086 return CopyOrLinkDragOperation(event.source_operations()); 1087 } 1088 } else if (data.HasString()) { 1089 int string_drop_position = drop_highlight_position(); 1090 string16 text; 1091 if ((string_drop_position != -1 || !in_drag) && data.GetString(&text)) { 1092 DCHECK(string_drop_position == -1 || 1093 ((string_drop_position >= 0) && 1094 (string_drop_position <= GetTextLength()))); 1095 if (in_drag) { 1096 if (event.source_operations()== ui::DragDropTypes::DRAG_MOVE) 1097 MoveSelectedText(string_drop_position); 1098 else 1099 InsertText(string_drop_position, text); 1100 } else { 1101 string16 collapsed_text(CollapseWhitespace(text, true)); 1102 if (model()->CanPasteAndGo(collapsed_text)) 1103 model()->PasteAndGo(collapsed_text); 1104 } 1105 return CopyOrLinkDragOperation(event.source_operations()); 1106 } 1107 } 1108 1109 return ui::DragDropTypes::DRAG_NONE; 1110} 1111 1112void OmniboxViewWin::CopyURL() { 1113 DoCopyURL(toolbar_model()->GetURL(), toolbar_model()->GetText(false)); 1114} 1115 1116bool OmniboxViewWin::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) { 1117 // Skip processing of [Alt]+<num-pad digit> Unicode alt key codes. 1118 if (event.IsUnicodeKeyCode()) 1119 return true; 1120 1121 // Skip accelerators for key combinations omnibox wants to crack. This list 1122 // should be synced with OnKeyDownOnlyWritable() (but for tab which is dealt 1123 // with above in LocationBarView::SkipDefaultKeyEventProcessing). 1124 // 1125 // We cannot return true for all keys because we still need to handle some 1126 // accelerators (e.g., F5 for reload the page should work even when the 1127 // Omnibox gets focused). 1128 switch (event.key_code()) { 1129 case ui::VKEY_ESCAPE: { 1130 ScopedFreeze freeze(this, GetTextObjectModel()); 1131 return model()->OnEscapeKeyPressed(); 1132 } 1133 1134 case ui::VKEY_RETURN: 1135 return true; 1136 1137 case ui::VKEY_UP: 1138 case ui::VKEY_DOWN: 1139 return !event.IsAltDown(); 1140 1141 case ui::VKEY_DELETE: 1142 case ui::VKEY_INSERT: 1143 return !event.IsAltDown() && event.IsShiftDown() && 1144 !event.IsControlDown(); 1145 1146 case ui::VKEY_X: 1147 case ui::VKEY_V: 1148 return !event.IsAltDown() && event.IsControlDown(); 1149 1150 case ui::VKEY_BACK: 1151 return true; 1152 1153 default: 1154 return false; 1155 } 1156} 1157 1158void OmniboxViewWin::HandleExternalMsg(UINT msg, 1159 UINT flags, 1160 const CPoint& screen_point) { 1161 if (msg == WM_CAPTURECHANGED) { 1162 SendMessage(msg, 0, NULL); 1163 return; 1164 } 1165 1166 CPoint client_point(screen_point); 1167 ::MapWindowPoints(NULL, m_hWnd, &client_point, 1); 1168 SendMessage(msg, flags, MAKELPARAM(client_point.x, client_point.y)); 1169} 1170 1171bool OmniboxViewWin::IsCommandIdChecked(int command_id) const { 1172 return false; 1173} 1174 1175bool OmniboxViewWin::IsCommandIdEnabled(int command_id) const { 1176 switch (command_id) { 1177 case IDS_UNDO: 1178 return !!CanUndo(); 1179 case IDC_CUT: 1180 return !!CanCut(); 1181 case IDC_COPY: 1182 return !!CanCopy(); 1183 case IDC_COPY_URL: 1184 return !!CanCopy() && 1185 !model()->user_input_in_progress() && 1186 toolbar_model()->WouldReplaceSearchURLWithSearchTerms(); 1187 case IDC_PASTE: 1188 return !!CanPaste(); 1189 case IDS_PASTE_AND_GO: 1190 return model()->CanPasteAndGo(GetClipboardText()); 1191 case IDS_SELECT_ALL: 1192 return !!CanSelectAll(); 1193 case IDC_EDIT_SEARCH_ENGINES: 1194 return command_updater()->IsCommandEnabled(command_id); 1195 default: 1196 NOTREACHED(); 1197 return false; 1198 } 1199} 1200 1201bool OmniboxViewWin::GetAcceleratorForCommandId( 1202 int command_id, 1203 ui::Accelerator* accelerator) { 1204 return location_bar_->GetWidget()->GetAccelerator(command_id, accelerator); 1205} 1206 1207bool OmniboxViewWin::IsItemForCommandIdDynamic(int command_id) const { 1208 // No need to change the default IDS_PASTE_AND_GO label unless this is a 1209 // search. 1210 return command_id == IDS_PASTE_AND_GO; 1211} 1212 1213string16 OmniboxViewWin::GetLabelForCommandId(int command_id) const { 1214 DCHECK_EQ(IDS_PASTE_AND_GO, command_id); 1215 return l10n_util::GetStringUTF16( 1216 model()->IsPasteAndSearch(GetClipboardText()) ? 1217 IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO); 1218} 1219 1220void OmniboxViewWin::ExecuteCommand(int command_id, int event_flags) { 1221 ScopedFreeze freeze(this, GetTextObjectModel()); 1222 // These commands don't invoke the popup via OnBefore/AfterPossibleChange(). 1223 if (command_id == IDS_PASTE_AND_GO) { 1224 model()->PasteAndGo(GetClipboardText()); 1225 return; 1226 } else if (command_id == IDC_EDIT_SEARCH_ENGINES) { 1227 command_updater()->ExecuteCommand(command_id); 1228 return; 1229 } else if (command_id == IDC_COPY) { 1230 Copy(); 1231 return; 1232 } else if (command_id == IDC_COPY_URL) { 1233 CopyURL(); 1234 return; 1235 } 1236 1237 OnBeforePossibleChange(); 1238 switch (command_id) { 1239 case IDS_UNDO: 1240 Undo(); 1241 break; 1242 1243 case IDC_CUT: 1244 Cut(); 1245 break; 1246 1247 case IDC_PASTE: 1248 Paste(); 1249 break; 1250 1251 case IDS_SELECT_ALL: 1252 SelectAll(false); 1253 break; 1254 1255 default: 1256 NOTREACHED(); 1257 break; 1258 } 1259 OnAfterPossibleChange(); 1260} 1261 1262// static 1263int CALLBACK OmniboxViewWin::WordBreakProc(LPTSTR edit_text, 1264 int current_pos, 1265 int length, 1266 int action) { 1267 // TODO(pkasting): http://b/1111308 We should let other people, like ICU and 1268 // GURL, do the work for us here instead of writing all this ourselves. 1269 1270 // With no clear guidance from the MSDN docs on how to handle "not found" in 1271 // the "find the nearest xxx..." cases below, I cap the return values at 1272 // [0, length]. Since one of these (0) is also a valid position, the return 1273 // values are thus ambiguous :( 1274 switch (action) { 1275 // Find nearest character before current position that begins a word. 1276 case WB_LEFT: 1277 case WB_MOVEWORDLEFT: { 1278 if (current_pos < 2) { 1279 // Either current_pos == 0, so we have a "not found" case and return 0, 1280 // or current_pos == 1, and the only character before this position is 1281 // at 0. 1282 return 0; 1283 } 1284 1285 // Look for a delimiter before the previous character; the previous word 1286 // starts immediately after. (If we looked for a delimiter before the 1287 // current character, we could stop on the immediate prior character, 1288 // which would mean we'd return current_pos -- which isn't "before the 1289 // current position".) 1290 const int prev_delim = 1291 WordBreakProc(edit_text, current_pos - 1, length, WB_LEFTBREAK); 1292 1293 if ((prev_delim == 0) && 1294 !WordBreakProc(edit_text, 0, length, WB_ISDELIMITER)) { 1295 // Got back 0, but position 0 isn't a delimiter. This was a "not 1296 // found" 0, so return one of our own. 1297 return 0; 1298 } 1299 1300 return prev_delim + 1; 1301 } 1302 1303 // Find nearest character after current position that begins a word. 1304 case WB_RIGHT: 1305 case WB_MOVEWORDRIGHT: { 1306 if (WordBreakProc(edit_text, current_pos, length, WB_ISDELIMITER)) { 1307 // The current character is a delimiter, so the next character starts 1308 // a new word. Done. 1309 return current_pos + 1; 1310 } 1311 1312 // Look for a delimiter after the current character; the next word starts 1313 // immediately after. 1314 const int next_delim = 1315 WordBreakProc(edit_text, current_pos, length, WB_RIGHTBREAK); 1316 if (next_delim == length) { 1317 // Didn't find a delimiter. Return length to signal "not found". 1318 return length; 1319 } 1320 1321 return next_delim + 1; 1322 } 1323 1324 // Determine if the current character delimits words. 1325 case WB_ISDELIMITER: 1326 return !!(WordBreakProc(edit_text, current_pos, length, WB_CLASSIFY) & 1327 WBF_BREAKLINE); 1328 1329 // Return the classification of the current character. 1330 case WB_CLASSIFY: 1331 if (IsWhitespace(edit_text[current_pos])) { 1332 // Whitespace normally breaks words, but the MSDN docs say that we must 1333 // not break on the CRs in a "CR, LF" or a "CR, CR, LF" sequence. Just 1334 // check for an arbitrarily long sequence of CRs followed by LF and 1335 // report "not a delimiter" for the current CR in that case. 1336 while ((current_pos < (length - 1)) && 1337 (edit_text[current_pos] == 0x13)) { 1338 if (edit_text[++current_pos] == 0x10) 1339 return WBF_ISWHITE; 1340 } 1341 return WBF_BREAKLINE | WBF_ISWHITE; 1342 } 1343 1344 // Punctuation normally breaks words, but the first two characters in 1345 // "://" (end of scheme) should not be breaks, so that "http://" will be 1346 // treated as one word. 1347 if (ispunct(edit_text[current_pos], std::locale()) && 1348 !SchemeEnd(edit_text, current_pos, length) && 1349 !SchemeEnd(edit_text, current_pos - 1, length)) 1350 return WBF_BREAKLINE; 1351 1352 // Normal character, no flags. 1353 return 0; 1354 1355 // Finds nearest delimiter before current position. 1356 case WB_LEFTBREAK: 1357 for (int i = current_pos - 1; i >= 0; --i) { 1358 if (WordBreakProc(edit_text, i, length, WB_ISDELIMITER)) 1359 return i; 1360 } 1361 return 0; 1362 1363 // Finds nearest delimiter after current position. 1364 case WB_RIGHTBREAK: 1365 for (int i = current_pos + 1; i < length; ++i) { 1366 if (WordBreakProc(edit_text, i, length, WB_ISDELIMITER)) 1367 return i; 1368 } 1369 return length; 1370 } 1371 1372 NOTREACHED(); 1373 return 0; 1374} 1375 1376// static 1377bool OmniboxViewWin::SchemeEnd(LPTSTR edit_text, 1378 int current_pos, 1379 int length) { 1380 return (current_pos >= 0) && 1381 ((length - current_pos) > 2) && 1382 (edit_text[current_pos] == ':') && 1383 (edit_text[current_pos + 1] == '/') && 1384 (edit_text[current_pos + 2] == '/'); 1385} 1386 1387void OmniboxViewWin::OnChar(TCHAR ch, UINT repeat_count, UINT flags) { 1388 // Don't let alt-enter beep. Not sure this is necessary, as the standard 1389 // alt-enter will hit DiscardWMSysChar() and get thrown away, and 1390 // ctrl-alt-enter doesn't seem to reach here for some reason? At least not on 1391 // my system... still, this is harmless and maybe necessary in other locales. 1392 if (ch == VK_RETURN && (flags & KF_ALTDOWN)) 1393 return; 1394 1395 // Escape is processed in OnKeyDown. Don't let any WM_CHAR messages propagate 1396 // as we don't want the RichEdit to do anything funky. 1397 if (ch == VK_ESCAPE && !(flags & KF_ALTDOWN)) 1398 return; 1399 1400 if (ch == VK_TAB) { 1401 // Don't add tabs to the input. 1402 return; 1403 } 1404 1405 HandleKeystroke(GetCurrentMessage()->message, ch, repeat_count, flags); 1406} 1407 1408void OmniboxViewWin::OnContextMenu(HWND window, const CPoint& point) { 1409 BuildContextMenu(); 1410 1411 context_menu_runner_.reset( 1412 new views::MenuRunner(context_menu_contents_.get())); 1413 1414 gfx::Point location(point); 1415 if (point.x == -1 || point.y == -1) { 1416 POINT p; 1417 GetCaretPos(&p); 1418 MapWindowPoints(HWND_DESKTOP, &p, 1); 1419 location.SetPoint(p.x, p.y); 1420 } 1421 1422 ignore_result(context_menu_runner_->RunMenuAt(native_view_host_->GetWidget(), 1423 NULL, gfx::Rect(location, gfx::Size()), views::MenuItemView::TOPLEFT, 1424 ui::MENU_SOURCE_MOUSE, views::MenuRunner::HAS_MNEMONICS)); 1425} 1426 1427void OmniboxViewWin::OnCopy() { 1428 string16 text(GetSelectedText()); 1429 if (text.empty()) 1430 return; 1431 1432 CHARRANGE sel; 1433 GURL url; 1434 bool write_url = false; 1435 GetSel(sel); 1436 // GetSel() doesn't preserve selection direction, so sel.cpMin will always be 1437 // the smaller value. 1438 model()->AdjustTextForCopy(sel.cpMin, IsSelectAll(), &text, &url, &write_url); 1439 if (write_url) 1440 DoCopyURL(url, text); 1441 else 1442 DoCopyText(text); 1443} 1444 1445LRESULT OmniboxViewWin::OnCreate(const CREATESTRUCTW* /*create_struct*/) { 1446 if (base::win::IsTSFAwareRequired()) { 1447 // Enable TSF support of RichEdit. 1448 SetEditStyle(SES_USECTF, SES_USECTF); 1449 } 1450 if (base::win::GetVersion() >= base::win::VERSION_WIN8) { 1451 BOOL touch_mode = RegisterTouchWindow(m_hWnd, TWF_WANTPALM); 1452 DCHECK(touch_mode); 1453 } 1454 SetMsgHandled(FALSE); 1455 1456 // When TSF is enabled, OnTextUpdated() may be called without any previous 1457 // call that would have indicated the start of an editing session. In order 1458 // to guarantee we've always called OnBeforePossibleChange() before 1459 // OnAfterPossibleChange(), we therefore call that here. Note that multiple 1460 // (i.e. unmatched) calls to this function in a row are safe. 1461 if (base::win::IsTSFAwareRequired()) 1462 OnBeforePossibleChange(); 1463 return 0; 1464} 1465 1466void OmniboxViewWin::OnCut() { 1467 OnCopy(); 1468 1469 // This replace selection will have no effect (even on the undo stack) if the 1470 // current selection is empty. 1471 ReplaceSel(L"", true); 1472} 1473 1474LRESULT OmniboxViewWin::OnGetObject(UINT message, 1475 WPARAM wparam, 1476 LPARAM lparam) { 1477 // This is a request for the native accessibility object. 1478 if (lparam == OBJID_CLIENT) { 1479 return LresultFromObject(IID_IAccessible, wparam, 1480 native_view_host_->GetNativeViewAccessible()); 1481 } 1482 return 0; 1483} 1484 1485LRESULT OmniboxViewWin::OnImeComposition(UINT message, 1486 WPARAM wparam, 1487 LPARAM lparam) { 1488 if (ignore_ime_messages_) { 1489 // This message was sent while we're in the middle of meddling with the 1490 // underlying edit control. If we handle it below, OnAfterPossibleChange() 1491 // can get bogus text for the edit, and rerun autocomplete, destructively 1492 // modifying the result set that we're in the midst of using. For example, 1493 // if SetWindowTextAndCaretPos() was called due to the user clicking an 1494 // entry in the popup, we're in the middle of executing SetSelectedLine(), 1495 // and changing the results can cause checkfailures. 1496 return DefWindowProc(message, wparam, lparam); 1497 } 1498 1499 ScopedFreeze freeze(this, GetTextObjectModel()); 1500 OnBeforePossibleChange(); 1501 LRESULT result = DefWindowProc(message, wparam, lparam); 1502 // Force an IME composition confirmation operation to trigger the text_changed 1503 // code in OnAfterPossibleChange(), even if identical contents are confirmed, 1504 // to make sure the model can update its internal states correctly. 1505 OnAfterPossibleChangeInternal((lparam & GCS_RESULTSTR) != 0); 1506 return result; 1507} 1508 1509LRESULT OmniboxViewWin::OnImeEndComposition(UINT message, WPARAM wparam, 1510 LPARAM lparam) { 1511 // The edit control auto-clears the selection on WM_IME_ENDCOMPOSITION, which 1512 // means any inline autocompletion we were showing will no longer be 1513 // selected, and therefore no longer replaced by further user typing. To 1514 // avoid this we manually restore the original selection after the edit 1515 // handles the message. 1516 CHARRANGE range; 1517 GetSel(range); 1518 LRESULT result = DefWindowProc(message, wparam, lparam); 1519 SetSel(range); 1520 return result; 1521} 1522 1523LRESULT OmniboxViewWin::OnImeNotify(UINT message, 1524 WPARAM wparam, 1525 LPARAM lparam) { 1526 // Close the popup when the IME composition window is open, so they don't 1527 // overlap. 1528 switch (wparam) { 1529 case IMN_OPENCANDIDATE: 1530 ime_candidate_window_open_ = true; 1531 CloseOmniboxPopup(); 1532 break; 1533 case IMN_CLOSECANDIDATE: 1534 ime_candidate_window_open_ = false; 1535 1536 // UpdatePopup assumes user input is in progress, so only call it if 1537 // that's the case. Otherwise, autocomplete may run on an empty user 1538 // text. For example, Baidu Japanese IME sends IMN_CLOSECANDIDATE when 1539 // composition mode is entered, but the user may not have input anything 1540 // yet. 1541 if (model()->user_input_in_progress()) 1542 UpdatePopup(); 1543 1544 break; 1545 default: 1546 break; 1547 } 1548 return DefWindowProc(message, wparam, lparam); 1549} 1550 1551LRESULT OmniboxViewWin::OnTouchEvent(UINT message, 1552 WPARAM wparam, 1553 LPARAM lparam) { 1554 // There is a bug in Windows 8 where in the generated mouse messages 1555 // after touch go to the window which previously had focus. This means that 1556 // if a user taps the omnibox to give it focus, we don't get the simulated 1557 // WM_LBUTTONDOWN, and thus don't properly select all the text. To ensure 1558 // that we get this message, we capture the mouse when the user is doing a 1559 // single-point tap on an unfocused model. 1560 if ((wparam == 1) && !model()->has_focus()) { 1561 TOUCHINPUT point = {0}; 1562 if (ui::GetTouchInputInfoWrapper(reinterpret_cast<HTOUCHINPUT>(lparam), 1, 1563 &point, sizeof(TOUCHINPUT))) { 1564 if (point.dwFlags & TOUCHEVENTF_DOWN) 1565 SetCapture(); 1566 else if (point.dwFlags & TOUCHEVENTF_UP) 1567 ReleaseCapture(); 1568 } 1569 } 1570 SetMsgHandled(false); 1571 return 0; 1572} 1573 1574void OmniboxViewWin::OnKeyDown(TCHAR key, 1575 UINT repeat_count, 1576 UINT flags) { 1577 delete_at_end_pressed_ = false; 1578 1579 if (OnKeyDownAllModes(key, repeat_count, flags)) 1580 return; 1581 1582 // Make sure that we handle system key events like Alt-F4. 1583 if (popup_window_mode_) { 1584 DefWindowProc(GetCurrentMessage()->message, key, MAKELPARAM(repeat_count, 1585 flags)); 1586 return; 1587 } 1588 1589 if (OnKeyDownOnlyWritable(key, repeat_count, flags)) 1590 return; 1591 1592 // CRichEditCtrl changes its text on WM_KEYDOWN instead of WM_CHAR for many 1593 // different keys (backspace, ctrl-v, ...), so we call this in both cases. 1594 HandleKeystroke(GetCurrentMessage()->message, key, repeat_count, flags); 1595} 1596 1597void OmniboxViewWin::OnKeyUp(TCHAR key, 1598 UINT repeat_count, 1599 UINT flags) { 1600 if (key == VK_CONTROL) 1601 model()->OnControlKeyChanged(false); 1602 1603 // On systems with RTL input languages, ctrl+shift toggles the reading order 1604 // (depending on which shift key is pressed). But by default the CRichEditCtrl 1605 // only changes the current reading order, and as soon as the user deletes all 1606 // the text, or we call SetWindowText(), it reverts to the "default" order. 1607 // To work around this, if the user hits ctrl+shift, we pass it to 1608 // DefWindowProc() while the edit is empty, which toggles the default reading 1609 // order; then we restore the user's input. 1610 if (!(flags & KF_ALTDOWN) && 1611 (((key == VK_CONTROL) && (GetKeyState(VK_SHIFT) < 0)) || 1612 ((key == VK_SHIFT) && (GetKeyState(VK_CONTROL) < 0)))) { 1613 ScopedFreeze freeze(this, GetTextObjectModel()); 1614 1615 string16 saved_text(GetText()); 1616 CHARRANGE saved_sel; 1617 GetSelection(saved_sel); 1618 1619 SetWindowText(L""); 1620 1621 DefWindowProc(WM_KEYUP, key, MAKELPARAM(repeat_count, flags)); 1622 1623 SetWindowText(saved_text.c_str()); 1624 SetSelectionRange(saved_sel); 1625 return; 1626 } 1627 1628 SetMsgHandled(false); 1629} 1630 1631void OmniboxViewWin::OnKillFocus(HWND focus_wnd) { 1632 if (m_hWnd == focus_wnd) { 1633 // Focus isn't actually leaving. 1634 SetMsgHandled(false); 1635 return; 1636 } 1637 1638 // This must be invoked before ClosePopup. 1639 model()->OnWillKillFocus(focus_wnd); 1640 1641 // Close the popup. 1642 CloseOmniboxPopup(); 1643 1644 // Save the user's existing selection to restore it later. 1645 GetSelection(saved_selection_for_focus_change_); 1646 1647 // Tell the model to reset itself. 1648 model()->OnKillFocus(); 1649 1650 // Let the CRichEditCtrl do its default handling. This will complete any 1651 // in-progress IME composition. We must do this after setting has_focus_ to 1652 // false so that UpdatePopup() will know not to rerun autocomplete. 1653 ScopedFreeze freeze(this, GetTextObjectModel()); 1654 DefWindowProc(WM_KILLFOCUS, reinterpret_cast<WPARAM>(focus_wnd), 0); 1655 1656 // Cancel any user selection and scroll the text back to the beginning of the 1657 // URL. We have to do this after calling DefWindowProc() because otherwise 1658 // an in-progress IME composition will be completed at the new caret position, 1659 // resulting in the string jumping unexpectedly to the front of the edit. 1660 // 1661 // Crazy hack: If we just do PlaceCaretAt(0), and the beginning of the text is 1662 // currently scrolled out of view, we can wind up with a blinking cursor in 1663 // the toolbar at the current X coordinate of the beginning of the text. By 1664 // first doing a reverse-select-all to scroll the beginning of the text into 1665 // view, we work around this CRichEditCtrl bug. 1666 SelectAll(true); 1667 PlaceCaretAt(0); 1668 1669 if (tsf_event_router_) 1670 tsf_event_router_->SetManager(NULL); 1671} 1672 1673void OmniboxViewWin::OnLButtonDblClk(UINT keys, const CPoint& point) { 1674 // Save the double click info for later triple-click detection. 1675 tracking_double_click_ = true; 1676 double_click_point_ = point; 1677 double_click_time_ = GetCurrentMessage()->time; 1678 possible_drag_ = false; 1679 1680 // Modifying the selection counts as accepting any inline autocompletion, so 1681 // track "changes" made by clicking the mouse button. 1682 ScopedFreeze freeze(this, GetTextObjectModel()); 1683 OnBeforePossibleChange(); 1684 1685 DefWindowProc(WM_LBUTTONDBLCLK, keys, 1686 MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); 1687 1688 // Rich Edit 4.1 doesn't select the last word when the user double clicks 1689 // past the text. Do it manually. 1690 CHARRANGE selection; 1691 GetSelection(selection); 1692 // The default window proc for Rich Edit 4.1 seems to select the CHARRANGE 1693 // {text_length, text_length + 1} after a double click past the text. 1694 int length = GetTextLength(); 1695 if (selection.cpMin == length && selection.cpMax == length + 1) { 1696 string16 text = GetText(); 1697 int word_break = WordBreakProc(&text[0], length, length, WB_LEFT); 1698 selection.cpMin = word_break; 1699 selection.cpMax = length; 1700 SetSelectionRange(selection); 1701 } 1702 1703 OnAfterPossibleChange(); 1704 1705 gaining_focus_.reset(); // See NOTE in OnMouseActivate(). 1706} 1707 1708void OmniboxViewWin::OnLButtonDown(UINT keys, const CPoint& point) { 1709 TrackMousePosition(kLeft, point); 1710 if (gaining_focus_.get()) { 1711 // When Chrome was already the activated app, we haven't reached 1712 // OnSetFocus() yet. When we get there, don't restore the saved selection, 1713 // since it will just screw up the user's interaction with the edit. 1714 saved_selection_for_focus_change_.cpMin = -1; 1715 1716 // Crazy hack: In this particular case, the CRichEditCtrl seems to have an 1717 // internal flag that discards the next WM_LBUTTONDOWN without processing 1718 // it, so that clicks on the edit when its owning app is not activated are 1719 // eaten rather than processed (despite whatever the return value of 1720 // DefWindowProc(WM_MOUSEACTIVATE, ...) may say). This behavior is 1721 // confusing and we want the click to be treated normally. So, to reset the 1722 // CRichEditCtrl's internal flag, we pass it an extra WM_LBUTTONDOWN here 1723 // (as well as a matching WM_LBUTTONUP, just in case we'd be confusing some 1724 // kind of state tracking otherwise). 1725 DefWindowProc(WM_LBUTTONDOWN, keys, MAKELPARAM(point.x, point.y)); 1726 DefWindowProc(WM_LBUTTONUP, keys, MAKELPARAM(point.x, point.y)); 1727 } 1728 1729 // Check for triple click, then reset tracker. Should be safe to subtract 1730 // double_click_time_ from the current message's time even if the timer has 1731 // wrapped in between. 1732 const bool is_triple_click = tracking_double_click_ && 1733 views::NativeTextfieldWin::IsDoubleClick(double_click_point_, point, 1734 GetCurrentMessage()->time - double_click_time_); 1735 tracking_double_click_ = false; 1736 1737 if (!gaining_focus_.get() && !is_triple_click) 1738 OnPossibleDrag(point); 1739 1740 // Modifying the selection counts as accepting any inline autocompletion, so 1741 // track "changes" made by clicking the mouse button. 1742 ScopedFreeze freeze(this, GetTextObjectModel()); 1743 OnBeforePossibleChange(); 1744 DefWindowProc(WM_LBUTTONDOWN, keys, 1745 MAKELPARAM(ClipXCoordToVisibleText(point.x, is_triple_click), 1746 point.y)); 1747 OnAfterPossibleChange(); 1748 1749 gaining_focus_.reset(); 1750} 1751 1752void OmniboxViewWin::OnLButtonUp(UINT keys, const CPoint& point) { 1753 // default processing should happen first so we can see the result of the 1754 // selection 1755 ScopedFreeze freeze(this, GetTextObjectModel()); 1756 DefWindowProc(WM_LBUTTONUP, keys, 1757 MAKELPARAM(ClipXCoordToVisibleText(point.x, false), point.y)); 1758 1759 SelectAllIfNecessary(kLeft, point); 1760 1761 tracking_click_[kLeft] = false; 1762 1763 possible_drag_ = false; 1764} 1765 1766void OmniboxViewWin::OnMButtonDblClk(UINT /*keys*/, const CPoint& /*point*/) { 1767 gaining_focus_.reset(); // See NOTE in OnMouseActivate(). 1768 1769 // By default, the edit responds to middle-clicks by capturing the mouse and 1770 // ignoring all subsequent events until it receives another click (of any of 1771 // the left, middle, or right buttons). This bizarre behavior is not only 1772 // useless but can cause the UI to appear unresponsive if a user accidentally 1773 // middle-clicks the edit (instead of a tab above it), so we purposefully eat 1774 // this message (instead of calling SetMsgHandled(false)) to avoid triggering 1775 // this. 1776} 1777 1778void OmniboxViewWin::OnMButtonDown(UINT /*keys*/, const CPoint& /*point*/) { 1779 tracking_double_click_ = false; 1780 1781 // See note in OnMButtonDblClk above. 1782} 1783 1784void OmniboxViewWin::OnMButtonUp(UINT /*keys*/, const CPoint& /*point*/) { 1785 possible_drag_ = false; 1786 1787 // See note in OnMButtonDblClk above. 1788} 1789 1790LRESULT OmniboxViewWin::OnMouseActivate(HWND window, 1791 UINT hit_test, 1792 UINT mouse_message) { 1793 // First, give other handlers a chance to handle the message to see if we are 1794 // actually going to activate and gain focus. 1795 LRESULT result = DefWindowProc(WM_MOUSEACTIVATE, 1796 reinterpret_cast<WPARAM>(window), 1797 MAKELPARAM(hit_test, mouse_message)); 1798 // Check if we're getting focus from a click. We have to do this here rather 1799 // than in OnXButtonDown() since in many scenarios OnSetFocus() will be 1800 // reached before OnXButtonDown(), preventing us from detecting this properly 1801 // there. Also in those cases, we need to already know in OnSetFocus() that 1802 // we should not restore the saved selection. 1803 if ((!model()->has_focus() || 1804 (model()->focus_state() == OMNIBOX_FOCUS_INVISIBLE)) && 1805 ((mouse_message == WM_LBUTTONDOWN || mouse_message == WM_RBUTTONDOWN)) && 1806 (result == MA_ACTIVATE)) { 1807 if (gaining_focus_) { 1808 // On Windows 8 in metro mode, we get two WM_MOUSEACTIVATE messages when 1809 // we click on the omnibox with the mouse. 1810 DCHECK(win8::IsSingleWindowMetroMode()); 1811 return result; 1812 } 1813 gaining_focus_.reset(new ScopedFreeze(this, GetTextObjectModel())); 1814 1815 // Restore caret visibility whenever the user clicks in the omnibox in a 1816 // way that would give it focus. We must handle this case separately here 1817 // because if the omnibox currently has invisible focus, the mouse event 1818 // won't trigger either SetFocus() or OmniboxEditModel::OnSetFocus(). 1819 model()->SetCaretVisibility(true); 1820 1821 // NOTE: Despite |mouse_message| being WM_XBUTTONDOWN here, we're not 1822 // guaranteed to call OnXButtonDown() later! Specifically, if this is the 1823 // second click of a double click, we'll reach here but later call 1824 // OnXButtonDblClk(). Make sure |gaining_focus_| gets reset both places, 1825 // or we'll have visual glitchiness and then DCHECK failures. 1826 1827 // Don't restore saved selection, it will just screw up our interaction 1828 // with this edit. 1829 saved_selection_for_focus_change_.cpMin = -1; 1830 } 1831 return result; 1832} 1833 1834void OmniboxViewWin::OnMouseMove(UINT keys, const CPoint& point) { 1835 if (possible_drag_) { 1836 StartDragIfNecessary(point); 1837 // Don't fall through to default mouse handling, otherwise a second 1838 // drag session may start. 1839 return; 1840 } 1841 1842 if (tracking_click_[kLeft] && !IsDrag(click_point_[kLeft], point)) 1843 return; 1844 1845 tracking_click_[kLeft] = false; 1846 1847 // Return quickly if this can't change the selection/cursor, so we don't 1848 // create a ScopedFreeze (and thus do an UpdateWindow()) on every 1849 // WM_MOUSEMOVE. 1850 if (!(keys & MK_LBUTTON)) { 1851 DefWindowProc(WM_MOUSEMOVE, keys, MAKELPARAM(point.x, point.y)); 1852 return; 1853 } 1854 1855 // Clamp the selection to the visible text so the user can't drag to select 1856 // the "phantom newline". In theory we could achieve this by clipping the X 1857 // coordinate, but in practice the edit seems to behave nondeterministically 1858 // with similar sequences of clipped input coordinates fed to it. Maybe it's 1859 // reading the mouse cursor position directly? 1860 // 1861 // This solution has a minor visual flaw, however: if there's a visible cursor 1862 // at the edge of the text (only true when there's no selection), dragging the 1863 // mouse around outside that edge repaints the cursor on every WM_MOUSEMOVE 1864 // instead of allowing it to blink normally. To fix this, we special-case 1865 // this exact case and discard the WM_MOUSEMOVE messages instead of passing 1866 // them along. 1867 // 1868 // But even this solution has a flaw! (Argh.) In the case where the user has 1869 // a selection that starts at the edge of the edit, and proceeds to the middle 1870 // of the edit, and the user is dragging back past the start edge to remove 1871 // the selection, there's a redraw problem where the change between having the 1872 // last few bits of text still selected and having nothing selected can be 1873 // slow to repaint (which feels noticeably strange). This occurs if you only 1874 // let the edit receive a single WM_MOUSEMOVE past the edge of the text. I 1875 // think on each WM_MOUSEMOVE the edit is repainting its previous state, then 1876 // updating its internal variables to the new state but not repainting. To 1877 // fix this, we allow one more WM_MOUSEMOVE through after the selection has 1878 // supposedly been shrunk to nothing; this makes the edit redraw the selection 1879 // quickly so it feels smooth. 1880 CHARRANGE selection; 1881 GetSel(selection); 1882 const bool possibly_can_discard_mousemove = 1883 (selection.cpMin == selection.cpMax) && 1884 (((selection.cpMin == 0) && 1885 (ClipXCoordToVisibleText(point.x, false) > point.x)) || 1886 ((selection.cpMin == GetTextLength()) && 1887 (ClipXCoordToVisibleText(point.x, false) < point.x))); 1888 if (!can_discard_mousemove_ || !possibly_can_discard_mousemove) { 1889 can_discard_mousemove_ = possibly_can_discard_mousemove; 1890 ScopedFreeze freeze(this, GetTextObjectModel()); 1891 OnBeforePossibleChange(); 1892 // Force the Y coordinate to the center of the clip rect. The edit 1893 // behaves strangely when the cursor is dragged vertically: if the cursor 1894 // is in the middle of the text, drags inside the clip rect do nothing, 1895 // and drags outside the clip rect act as if the cursor jumped to the 1896 // left edge of the text. When the cursor is at the right edge, drags of 1897 // just a few pixels vertically end up selecting the "phantom newline"... 1898 // sometimes. 1899 RECT r; 1900 GetRect(&r); 1901 DefWindowProc(WM_MOUSEMOVE, keys, 1902 MAKELPARAM(point.x, (r.bottom - r.top) / 2)); 1903 OnAfterPossibleChange(); 1904 } 1905} 1906 1907void OmniboxViewWin::OnPaint(HDC bogus_hdc) { 1908 // We need to paint over the top of the edit. If we simply let the edit do 1909 // its default painting, then do ours into the window DC, the screen is 1910 // updated in between and we can get flicker. To avoid this, we force the 1911 // edit to paint into a memory DC, which we also paint onto, then blit the 1912 // whole thing to the screen. 1913 1914 // Don't paint if not necessary. 1915 CRect paint_clip_rect; 1916 if (!GetUpdateRect(&paint_clip_rect, true)) 1917 return; 1918 1919 // Begin painting, and create a memory DC for the edit to paint into. 1920 CPaintDC paint_dc(m_hWnd); 1921 CDC memory_dc(CreateCompatibleDC(paint_dc)); 1922 CRect rect; 1923 GetClientRect(&rect); 1924 // NOTE: This next call uses |paint_dc| instead of |memory_dc| because 1925 // |memory_dc| contains a 1x1 monochrome bitmap by default, which will cause 1926 // |memory_bitmap| to be monochrome, which isn't what we want. 1927 CBitmap memory_bitmap(CreateCompatibleBitmap(paint_dc, rect.Width(), 1928 rect.Height())); 1929 HBITMAP old_bitmap = memory_dc.SelectBitmap(memory_bitmap); 1930 1931 // Tell our intercept functions to supply our memory DC to the edit when it 1932 // tries to call BeginPaint(). 1933 // 1934 // The sane way to do this would be to use WM_PRINTCLIENT to ask the edit to 1935 // paint into our desired DC. Unfortunately, the Rich Edit 3.0 that ships 1936 // with Windows 2000/XP/Vista doesn't handle WM_PRINTCLIENT correctly; it 1937 // treats it just like WM_PAINT and calls BeginPaint(), ignoring our provided 1938 // DC. The Rich Edit 6.0 that ships with Office 2007 handles this better, but 1939 // has other issues, and we can't redistribute that DLL anyway. So instead, 1940 // we use this scary hack. 1941 // 1942 // NOTE: It's possible to get nested paint calls (!) (try setting the 1943 // permanent URL to something longer than the edit width, then selecting the 1944 // contents of the edit, typing a character, and hitting <esc>), so we can't 1945 // DCHECK(!edit_hwnd_) here. Instead, just save off the old HWND, which most 1946 // of the time will be NULL. 1947 HWND old_edit_hwnd = edit_hwnd; 1948 edit_hwnd = m_hWnd; 1949 paint_struct = paint_dc.m_ps; 1950 paint_struct.hdc = memory_dc; 1951 DefWindowProc(WM_PAINT, reinterpret_cast<WPARAM>(bogus_hdc), 0); 1952 1953 // Make the selection look better. 1954 EraseTopOfSelection(&memory_dc, rect, paint_clip_rect); 1955 1956 // Draw a slash through the scheme if this is insecure. 1957 if (insecure_scheme_component_.is_nonempty()) 1958 DrawSlashForInsecureScheme(memory_dc, rect, paint_clip_rect); 1959 1960 // Draw the drop highlight. 1961 if (drop_highlight_position_ != -1) 1962 DrawDropHighlight(memory_dc, rect, paint_clip_rect); 1963 1964 // Blit the memory DC to the actual paint DC and clean up. 1965 BitBlt(paint_dc, rect.left, rect.top, rect.Width(), rect.Height(), memory_dc, 1966 rect.left, rect.top, SRCCOPY); 1967 memory_dc.SelectBitmap(old_bitmap); 1968 edit_hwnd = old_edit_hwnd; 1969 1970 // If textfield has focus, reaffirm its caret visibility (without focus, a new 1971 // caret could be created and confuse the user as to where the focus is). This 1972 // needs to be called regardless of the current visibility of the caret. This 1973 // is because the underlying edit control will automatically re-create the 1974 // caret when it receives certain events that trigger repaints, e.g. window 1975 // resize events. This also checks for the existence of selected text, in 1976 // which case there shouldn't be a recreated caret since this would create 1977 // both a highlight and a blinking caret. 1978 if (model()->has_focus()) { 1979 CHARRANGE sel; 1980 GetSel(sel); 1981 if (sel.cpMin == sel.cpMax) 1982 ApplyCaretVisibility(); 1983 } 1984} 1985 1986void OmniboxViewWin::OnPaste() { 1987 // Replace the selection if we have something to paste. 1988 const string16 text(GetClipboardText()); 1989 if (!text.empty()) { 1990 // Record this paste, so we can do different behavior. 1991 model()->on_paste(); 1992 // Force a Paste operation to trigger the text_changed code in 1993 // OnAfterPossibleChange(), even if identical contents are pasted into the 1994 // text box. 1995 text_before_change_.clear(); 1996 ReplaceSel(text.c_str(), true); 1997 } 1998} 1999 2000void OmniboxViewWin::OnRButtonDblClk(UINT /*keys*/, const CPoint& /*point*/) { 2001 gaining_focus_.reset(); // See NOTE in OnMouseActivate(). 2002 SetMsgHandled(false); 2003} 2004 2005void OmniboxViewWin::OnRButtonDown(UINT /*keys*/, const CPoint& point) { 2006 TrackMousePosition(kRight, point); 2007 tracking_double_click_ = false; 2008 possible_drag_ = false; 2009 gaining_focus_.reset(); 2010 SetMsgHandled(false); 2011} 2012 2013void OmniboxViewWin::OnRButtonUp(UINT /*keys*/, const CPoint& point) { 2014 SelectAllIfNecessary(kRight, point); 2015 tracking_click_[kRight] = false; 2016 SetMsgHandled(false); 2017} 2018 2019void OmniboxViewWin::OnSetFocus(HWND focus_wnd) { 2020 views::FocusManager* focus_manager = location_bar_->GetFocusManager(); 2021 if (focus_manager) { 2022 // Notify the FocusManager that the focused view is now the location bar 2023 // (our parent view). 2024 focus_manager->SetFocusedView(location_bar_); 2025 } else { 2026 NOTREACHED(); 2027 } 2028 2029 model()->OnSetFocus(GetKeyState(VK_CONTROL) < 0); 2030 2031 // Restore saved selection if available. 2032 if (saved_selection_for_focus_change_.cpMin != -1) { 2033 SetSelectionRange(saved_selection_for_focus_change_); 2034 saved_selection_for_focus_change_.cpMin = -1; 2035 } 2036 2037 if (!tsf_event_router_) { 2038 SetMsgHandled(false); 2039 } else { 2040 DefWindowProc(); 2041 // Document manager created by RichEdit can be obtained only after 2042 // WM_SETFOCUS event is handled. 2043 tsf_event_router_->SetManager( 2044 ui::TSFBridge::GetInstance()->GetThreadManager()); 2045 SetMsgHandled(true); 2046 } 2047} 2048 2049LRESULT OmniboxViewWin::OnSetText(const wchar_t* text) { 2050 // Ignore all IME messages while we process this WM_SETTEXT message. 2051 // When SetWindowText() is called while an IME is composing text, the IME 2052 // calls SendMessage() to send a WM_IME_COMPOSITION message. When we receive 2053 // this WM_IME_COMPOSITION message, we update the omnibox and may call 2054 // SetWindowText() again. To stop this recursive message-handler call, we 2055 // stop updating the omnibox while we process a WM_SETTEXT message. 2056 // We wouldn't need to do this update anyway, because either we're in the 2057 // middle of updating the omnibox already or the caller of SetWindowText() 2058 // is going to update the omnibox next. 2059 base::AutoReset<bool> auto_reset_ignore_ime_messages( 2060 &ignore_ime_messages_, true); 2061 return DefWindowProc(WM_SETTEXT, 0, reinterpret_cast<LPARAM>(text)); 2062} 2063 2064void OmniboxViewWin::OnSysChar(TCHAR ch, 2065 UINT repeat_count, 2066 UINT flags) { 2067 DCHECK(flags & KF_ALTDOWN); 2068 // Explicitly show the system menu at a good location on [Alt]+[Space]. 2069 // Nearly all other [Alt]+<xxx> combos result in beeping rather than doing 2070 // something useful, so discard those. Note that [Ctrl]+[Alt]+<xxx> generates 2071 // WM_CHAR instead of WM_SYSCHAR, so it is not handled here. 2072 if (ch == VK_SPACE) { 2073 ui::ShowSystemMenu( 2074 native_view_host_->GetWidget()->GetTopLevelWidget()->GetNativeWindow()); 2075 } 2076} 2077 2078void OmniboxViewWin::OnWindowPosChanging(WINDOWPOS* window_pos) { 2079 if (force_hidden_) 2080 window_pos->flags &= ~SWP_SHOWWINDOW; 2081 SetMsgHandled(true); 2082} 2083 2084BOOL OmniboxViewWin::OnMouseWheel(UINT flags, short delta, CPoint point) { 2085 // Forward the mouse-wheel message to the window under the mouse. 2086 if (!ui::RerouteMouseWheel(m_hWnd, MAKEWPARAM(flags, delta), 2087 MAKELPARAM(point.x, point.y))) 2088 SetMsgHandled(false); 2089 return 0; 2090} 2091 2092void OmniboxViewWin::HandleKeystroke(UINT message, 2093 TCHAR key, 2094 UINT repeat_count, 2095 UINT flags) { 2096 ScopedFreeze freeze(this, GetTextObjectModel()); 2097 OnBeforePossibleChange(); 2098 2099 if (key == ui::VKEY_HOME || key == ui::VKEY_END) { 2100 // DefWindowProc() might reset the keyboard layout when it receives a 2101 // keydown event for VKEY_HOME or VKEY_END. When the window was created 2102 // with WS_EX_LAYOUTRTL and the current keyboard layout is not a RTL one, 2103 // if the input text is pure LTR text, the layout changes to the first RTL 2104 // keyboard layout in keyboard layout queue; if the input text is 2105 // bidirectional text, the layout changes to the keyboard layout of the 2106 // first RTL character in input text. When the window was created without 2107 // WS_EX_LAYOUTRTL and the current keyboard layout is not a LTR one, if the 2108 // input text is pure RTL text, the layout changes to English; if the input 2109 // text is bidirectional text, the layout changes to the keyboard layout of 2110 // the first LTR character in input text. Such keyboard layout change 2111 // behavior is surprising and inconsistent with keyboard behavior 2112 // elsewhere, so reset the layout in this case. 2113 HKL layout = GetKeyboardLayout(0); 2114 DefWindowProc(message, key, MAKELPARAM(repeat_count, flags)); 2115 ActivateKeyboardLayout(layout, KLF_REORDER); 2116 } else { 2117 DefWindowProc(message, key, MAKELPARAM(repeat_count, flags)); 2118 } 2119 2120 // CRichEditCtrl automatically turns on IMF_AUTOKEYBOARD when the user 2121 // inputs an RTL character, making it difficult for the user to control 2122 // what language is set as they type. Force this off to make the edit's 2123 // behavior more stable. 2124 const int lang_options = SendMessage(EM_GETLANGOPTIONS, 0, 0); 2125 if (lang_options & IMF_AUTOKEYBOARD) 2126 SendMessage(EM_SETLANGOPTIONS, 0, lang_options & ~IMF_AUTOKEYBOARD); 2127 2128 OnAfterPossibleChange(); 2129} 2130 2131bool OmniboxViewWin::OnKeyDownOnlyWritable(TCHAR key, 2132 UINT repeat_count, 2133 UINT flags) { 2134 // NOTE: Annoyingly, ctrl-alt-<key> generates WM_KEYDOWN rather than 2135 // WM_SYSKEYDOWN, so we need to check (flags & KF_ALTDOWN) in various places 2136 // in this function even with a WM_SYSKEYDOWN handler. 2137 2138 // If adding a new key that could possibly be an accelerator then you'll need 2139 // to update LocationBarView::SkipDefaultKeyEventProcessing() as well. 2140 int count = repeat_count; 2141 switch (key) { 2142 case VK_RIGHT: 2143 // TODO(sky): figure out RTL. 2144 if (base::i18n::IsRTL()) 2145 return false; 2146 { 2147 CHARRANGE selection; 2148 GetSel(selection); 2149 return (selection.cpMin == selection.cpMax) && 2150 (selection.cpMin == GetTextLength()) && 2151 model()->CommitSuggestedText(); 2152 } 2153 2154 case VK_RETURN: 2155 model()->AcceptInput((flags & KF_ALTDOWN) ? 2156 NEW_FOREGROUND_TAB : CURRENT_TAB, false); 2157 return true; 2158 2159 case VK_PRIOR: 2160 case VK_NEXT: 2161 count = model()->result().size(); 2162 // FALL THROUGH 2163 case VK_UP: 2164 case VK_DOWN: 2165 // Ignore alt + numpad, but treat alt + (non-numpad) like (non-numpad). 2166 if ((flags & KF_ALTDOWN) && !(flags & KF_EXTENDED)) 2167 return false; 2168 2169 model()->OnUpOrDownKeyPressed(((key == VK_PRIOR) || (key == VK_UP)) ? 2170 -count : count); 2171 return true; 2172 2173 // Hijacking Editing Commands 2174 // 2175 // We hijack the keyboard short-cuts for Cut, Copy, and Paste here so that 2176 // they go through our clipboard routines. This allows us to be smarter 2177 // about how we interact with the clipboard and avoid bugs in the 2178 // CRichEditCtrl. If we didn't hijack here, the edit control would handle 2179 // these internally with sending the WM_CUT, WM_COPY, or WM_PASTE messages. 2180 // 2181 // Cut: Shift-Delete and Ctrl-x are treated as cut. Ctrl-Shift-Delete and 2182 // Ctrl-Shift-x are not treated as cut even though the underlying 2183 // CRichTextEdit would treat them as such. Also note that we bring 2184 // up 'clear browsing data' on control-shift-delete. 2185 // Copy: Ctrl-Insert and Ctrl-c is treated as copy. Shift-Ctrl-c is not. 2186 // (This is handled in OnKeyDownAllModes().) 2187 // Paste: Shift-Insert and Ctrl-v are treated as paste. Ctrl-Shift-Insert 2188 // and Ctrl-Shift-v are not. 2189 // 2190 // This behavior matches most, but not all Windows programs, and largely 2191 // conforms to what users expect. 2192 2193 case VK_DELETE: 2194 if (flags & KF_ALTDOWN) 2195 return false; 2196 if (GetKeyState(VK_SHIFT) >= 0) { 2197 if (GetKeyState(VK_CONTROL) >= 0) { 2198 CHARRANGE selection; 2199 GetSel(selection); 2200 delete_at_end_pressed_ = ((selection.cpMin == selection.cpMax) && 2201 (selection.cpMin == GetTextLength())); 2202 } 2203 return false; 2204 } 2205 if (GetKeyState(VK_CONTROL) >= 0) { 2206 // Cut text if possible. 2207 CHARRANGE selection; 2208 GetSel(selection); 2209 if (selection.cpMin != selection.cpMax) { 2210 ScopedFreeze freeze(this, GetTextObjectModel()); 2211 OnBeforePossibleChange(); 2212 Cut(); 2213 OnAfterPossibleChange(); 2214 } else { 2215 if (model()->popup_model()->IsOpen()) { 2216 // This is a bit overloaded, but we hijack Shift-Delete in this 2217 // case to delete the current item from the pop-up. We prefer 2218 // cutting to this when possible since that's the behavior more 2219 // people expect from Shift-Delete, and it's more commonly useful. 2220 model()->popup_model()->TryDeletingCurrentItem(); 2221 } 2222 } 2223 } 2224 return true; 2225 2226 case 'X': 2227 if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0)) 2228 return false; 2229 if (GetKeyState(VK_SHIFT) >= 0) { 2230 ScopedFreeze freeze(this, GetTextObjectModel()); 2231 OnBeforePossibleChange(); 2232 Cut(); 2233 OnAfterPossibleChange(); 2234 } 2235 return true; 2236 2237 case VK_INSERT: 2238 // Ignore insert by itself, so we don't turn overtype mode on/off. 2239 if (!(flags & KF_ALTDOWN) && (GetKeyState(VK_SHIFT) >= 0) && 2240 (GetKeyState(VK_CONTROL) >= 0)) 2241 return true; 2242 // FALL THROUGH 2243 case 'V': 2244 if ((flags & KF_ALTDOWN) || 2245 (GetKeyState((key == 'V') ? VK_CONTROL : VK_SHIFT) >= 0)) 2246 return false; 2247 if (GetKeyState((key == 'V') ? VK_SHIFT : VK_CONTROL) >= 0) { 2248 ScopedFreeze freeze(this, GetTextObjectModel()); 2249 OnBeforePossibleChange(); 2250 Paste(); 2251 OnAfterPossibleChange(); 2252 } 2253 return true; 2254 2255 case VK_BACK: { 2256 if ((flags & KF_ALTDOWN) || model()->is_keyword_hint() || 2257 model()->keyword().empty()) 2258 return false; 2259 2260 { 2261 CHARRANGE selection; 2262 GetSel(selection); 2263 if ((selection.cpMin != selection.cpMax) || (selection.cpMin != 0)) 2264 return false; 2265 } 2266 2267 // We're showing a keyword and the user pressed backspace at the beginning 2268 // of the text. Delete the selected keyword. 2269 ScopedFreeze freeze(this, GetTextObjectModel()); 2270 model()->ClearKeyword(GetText()); 2271 return true; 2272 } 2273 2274 case VK_TAB: { 2275 const bool shift_pressed = GetKeyState(VK_SHIFT) < 0; 2276 if (model()->is_keyword_hint() && !shift_pressed) { 2277 // Accept the keyword. 2278 ScopedFreeze freeze(this, GetTextObjectModel()); 2279 model()->AcceptKeyword(ENTERED_KEYWORD_MODE_VIA_TAB); 2280 } else if (shift_pressed && 2281 model()->popup_model()->selected_line_state() == 2282 OmniboxPopupModel::KEYWORD) { 2283 model()->ClearKeyword(GetText()); 2284 } else { 2285 model()->OnUpOrDownKeyPressed(shift_pressed ? -count : count); 2286 } 2287 return true; 2288 } 2289 2290 case 0xbb: // Ctrl-'='. Triggers subscripting (even in plain text mode). 2291 // We don't use VK_OEM_PLUS in case the macro isn't defined. 2292 // (e.g., we don't have this symbol in embeded environment). 2293 return true; 2294 2295 default: 2296 return false; 2297 } 2298} 2299 2300bool OmniboxViewWin::OnKeyDownAllModes(TCHAR key, 2301 UINT repeat_count, 2302 UINT flags) { 2303 // See KF_ALTDOWN comment atop OnKeyDownOnlyWritable(). 2304 2305 switch (key) { 2306 case VK_CONTROL: 2307 model()->OnControlKeyChanged(true); 2308 return false; 2309 2310 case VK_INSERT: 2311 case 'C': 2312 // See more detailed comments in OnKeyDownOnlyWritable(). 2313 if ((flags & KF_ALTDOWN) || (GetKeyState(VK_CONTROL) >= 0)) 2314 return false; 2315 if (GetKeyState(VK_SHIFT) >= 0) 2316 Copy(); 2317 return true; 2318 2319 default: 2320 return false; 2321 } 2322} 2323 2324void OmniboxViewWin::GetSelection(CHARRANGE& sel) const { 2325 GetSel(sel); 2326 2327 // See if we need to reverse the direction of the selection. 2328 ITextDocument* const text_object_model = GetTextObjectModel(); 2329 if (!text_object_model) 2330 return; 2331 base::win::ScopedComPtr<ITextSelection> selection; 2332 const HRESULT hr = text_object_model->GetSelection(selection.Receive()); 2333 DCHECK_EQ(S_OK, hr); 2334 long flags; 2335 selection->GetFlags(&flags); 2336 if (flags & tomSelStartActive) 2337 std::swap(sel.cpMin, sel.cpMax); 2338} 2339 2340string16 OmniboxViewWin::GetSelectedText() const { 2341 CHARRANGE sel; 2342 GetSel(sel); 2343 string16 str; 2344 if (sel.cpMin != sel.cpMax) 2345 GetSelText(WriteInto(&str, sel.cpMax - sel.cpMin + 1)); 2346 return str; 2347} 2348 2349void OmniboxViewWin::SetSelection(LONG start, LONG end) { 2350 SetSel(start, end); 2351 2352 if (start <= end) 2353 return; 2354 2355 // We need to reverse the direction of the selection. 2356 ITextDocument* const text_object_model = GetTextObjectModel(); 2357 if (!text_object_model) 2358 return; 2359 base::win::ScopedComPtr<ITextSelection> selection; 2360 const HRESULT hr = text_object_model->GetSelection(selection.Receive()); 2361 DCHECK_EQ(S_OK, hr); 2362 selection->SetFlags(tomSelStartActive); 2363} 2364 2365void OmniboxViewWin::PlaceCaretAt(string16::size_type pos) { 2366 SetSelection(static_cast<LONG>(pos), static_cast<LONG>(pos)); 2367} 2368 2369bool OmniboxViewWin::IsSelectAllForRange(const CHARRANGE& sel) const { 2370 const int text_length = GetTextLength(); 2371 return ((sel.cpMin == 0) && (sel.cpMax >= text_length)) || 2372 ((sel.cpMax == 0) && (sel.cpMin >= text_length)); 2373} 2374 2375LONG OmniboxViewWin::ClipXCoordToVisibleText(LONG x, 2376 bool is_triple_click) const { 2377 // Clip the X coordinate to the left edge of the text. Careful: 2378 // PosFromChar(0) may return a negative X coordinate if the beginning of the 2379 // text has scrolled off the edit, so don't go past the clip rect's edge. 2380 PARAFORMAT2 pf2; 2381 GetParaFormat(pf2); 2382 // Calculation of the clipped coordinate is more complicated if the paragraph 2383 // layout is RTL layout, or if there is RTL characters inside the LTR layout 2384 // paragraph. 2385 const bool ltr_text_in_ltr_layout = !(pf2.wEffects & PFE_RTLPARA) && 2386 !base::i18n::StringContainsStrongRTLChars(GetText()); 2387 const int length = GetTextLength(); 2388 RECT r; 2389 GetRect(&r); 2390 // The values returned by PosFromChar() seem to refer always 2391 // to the left edge of the character's bounding box. 2392 const LONG first_position_x = PosFromChar(0).x; 2393 LONG min_x = first_position_x; 2394 if (!ltr_text_in_ltr_layout) { 2395 for (int i = 1; i < length; ++i) 2396 min_x = std::min(min_x, PosFromChar(i).x); 2397 } 2398 const LONG left_bound = std::max(r.left, min_x); 2399 // PosFromChar(length) is a phantom character past the end of the text. It is 2400 // not necessarily a right bound; in RTL controls it may be a left bound. So 2401 // treat it as a right bound only if it is to the right of the first 2402 // character. 2403 LONG right_bound = r.right; 2404 LONG end_position_x = PosFromChar(length).x; 2405 if (end_position_x >= first_position_x) { 2406 right_bound = std::min(right_bound, end_position_x); // LTR case. 2407 } 2408 // For trailing characters that are 2 pixels wide or less (like "l" in some 2409 // fonts), we have a problem: 2410 // * Clicks on any pixel within the character will place the cursor before 2411 // the character. 2412 // * Clicks on the pixel just after the character will not allow triple- 2413 // click to work properly (true for any last character width). 2414 // So, we move to the last pixel of the character when this is a 2415 // triple-click, and moving to one past the last pixel in all other 2416 // scenarios. This way, all clicks that can move the cursor will place it at 2417 // the end of the text, but triple-click will still work. 2418 if (x < left_bound) { 2419 return (is_triple_click && ltr_text_in_ltr_layout) ? left_bound - 1 : 2420 left_bound; 2421 } 2422 if ((length == 0) || (x < right_bound)) 2423 return x; 2424 return is_triple_click ? (right_bound - 1) : right_bound; 2425} 2426 2427int OmniboxViewWin::GetOmniboxTextLength() const { 2428 return static_cast<int>(GetTextLength()); 2429} 2430 2431void OmniboxViewWin::EmphasizeURLComponents() { 2432 ITextDocument* const text_object_model = GetTextObjectModel(); 2433 ScopedFreeze freeze(this, text_object_model); 2434 ScopedSuspendUndo suspend_undo(text_object_model); 2435 2436 // Save the selection. 2437 CHARRANGE saved_sel; 2438 GetSelection(saved_sel); 2439 2440 // See whether the contents are a URL with a non-empty host portion, which we 2441 // should emphasize. To check for a URL, rather than using the type returned 2442 // by Parse(), ask the model, which will check the desired page transition for 2443 // this input. This can tell us whether an UNKNOWN input string is going to 2444 // be treated as a search or a navigation, and is the same method the Paste 2445 // And Go system uses. 2446 url_parse::Component scheme, host; 2447 string16 text(GetText()); 2448 AutocompleteInput::ParseForEmphasizeComponents(text, &scheme, &host); 2449 2450 // Set the baseline emphasis. 2451 CHARFORMAT cf = {0}; 2452 cf.dwMask = CFM_COLOR; 2453 // If we're going to emphasize parts of the text, then the baseline state 2454 // should be "de-emphasized". If not, then everything should be rendered in 2455 // the standard text color unless we should grey out the entire URL. 2456 bool grey_out_url = text.substr(scheme.begin, scheme.len) == 2457 UTF8ToUTF16(extensions::kExtensionScheme); 2458 bool grey_base = model()->CurrentTextIsURL() && 2459 (host.is_nonempty() || grey_out_url); 2460 cf.crTextColor = skia::SkColorToCOLORREF(location_bar_->GetColor( 2461 security_level_, 2462 grey_base ? LocationBarView::DEEMPHASIZED_TEXT : LocationBarView::TEXT)); 2463 // NOTE: Don't use SetDefaultCharFormat() instead of the below; that sets 2464 // the format that will get applied to text added in the future, not to text 2465 // already in the edit. 2466 SelectAll(false); 2467 SetSelectionCharFormat(cf); 2468 if (host.is_nonempty() && !grey_out_url) { 2469 // We've found a host name and we should provide emphasis to host names, 2470 // so emphasize it. 2471 cf.crTextColor = skia::SkColorToCOLORREF(location_bar_->GetColor( 2472 security_level_, LocationBarView::TEXT)); 2473 SetSelection(host.begin, host.end()); 2474 SetSelectionCharFormat(cf); 2475 } 2476 2477 // Emphasize the scheme for security UI display purposes (if necessary). 2478 insecure_scheme_component_.reset(); 2479 if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() && 2480 scheme.is_nonempty() && (security_level_ != ToolbarModel::NONE)) { 2481 if (security_level_ == ToolbarModel::SECURITY_ERROR) { 2482 insecure_scheme_component_.begin = scheme.begin; 2483 insecure_scheme_component_.len = scheme.len; 2484 } 2485 cf.crTextColor = skia::SkColorToCOLORREF(location_bar_->GetColor( 2486 security_level_, LocationBarView::SECURITY_TEXT)); 2487 SetSelection(scheme.begin, scheme.end()); 2488 SetSelectionCharFormat(cf); 2489 } 2490 2491 // Restore the selection. 2492 SetSelectionRange(saved_sel); 2493} 2494 2495void OmniboxViewWin::EraseTopOfSelection(CDC* dc, 2496 const CRect& client_rect, 2497 const CRect& paint_clip_rect) { 2498 // Find the area we care about painting. We could calculate the rect 2499 // containing just the selected portion, but there's no harm in simply erasing 2500 // the whole top of the client area, and at least once I saw us manage to 2501 // select the "phantom newline" briefly, which looks very weird if not clipped 2502 // off at the same height. 2503 CRect erase_rect(client_rect.left, client_rect.top, client_rect.right, 2504 client_rect.top + font_y_adjustment_); 2505 erase_rect.IntersectRect(erase_rect, paint_clip_rect); 2506 2507 // Erase to the background color. 2508 if (!erase_rect.IsRectNull()) 2509 dc->FillSolidRect(&erase_rect, background_color_); 2510} 2511 2512void OmniboxViewWin::DrawSlashForInsecureScheme(HDC hdc, 2513 const CRect& client_rect, 2514 const CRect& paint_clip_rect) { 2515 DCHECK(insecure_scheme_component_.is_nonempty()); 2516 2517 // Calculate the rect, in window coordinates, containing the portion of the 2518 // scheme where we'll be drawing the slash. Vertically, we draw across one 2519 // x-height of text, plus an additional 3 stroke diameters (the stroke width 2520 // plus a half-stroke width of space between the stroke and the text, both 2521 // above and below the text). 2522 const int font_top = client_rect.top + font_y_adjustment_; 2523 const SkScalar kStrokeWidthPixels = SkIntToScalar(2); 2524 const int kAdditionalSpaceOutsideFont = 2525 static_cast<int>(ceil(kStrokeWidthPixels * 1.5f)); 2526 const int font_ascent = font_.GetBaseline(); 2527 const CRect scheme_rect( 2528 PosFromChar(insecure_scheme_component_.begin).x, 2529 font_top + font_ascent - font_x_height_ - kAdditionalSpaceOutsideFont, 2530 PosFromChar(insecure_scheme_component_.end()).x, 2531 font_top + font_ascent + kAdditionalSpaceOutsideFont); 2532 2533 // Clip to the portion we care about and translate to canvas coordinates 2534 // (see the canvas creation below) for use later. 2535 CRect canvas_clip_rect, canvas_paint_clip_rect; 2536 canvas_clip_rect.IntersectRect(scheme_rect, client_rect); 2537 canvas_paint_clip_rect.IntersectRect(canvas_clip_rect, paint_clip_rect); 2538 if (canvas_paint_clip_rect.IsRectNull()) 2539 return; // We don't need to paint any of this region, so just bail early. 2540 canvas_clip_rect.OffsetRect(-scheme_rect.left, -scheme_rect.top); 2541 canvas_paint_clip_rect.OffsetRect(-scheme_rect.left, -scheme_rect.top); 2542 2543 // Create a paint context for drawing the antialiased stroke. 2544 SkPaint paint; 2545 paint.setAntiAlias(true); 2546 paint.setStrokeWidth(kStrokeWidthPixels); 2547 paint.setStrokeCap(SkPaint::kRound_Cap); 2548 2549 // Create a canvas as large as |scheme_rect| to do our drawing, and initialize 2550 // it to fully transparent so any antialiasing will look nice when painted 2551 // atop the edit. 2552 gfx::Canvas canvas(gfx::Size(scheme_rect.Width(), scheme_rect.Height()), 2553 ui::SCALE_FACTOR_100P, false); 2554 SkCanvas* sk_canvas = canvas.sk_canvas(); 2555 sk_canvas->getDevice()->accessBitmap(true).eraseARGB(0, 0, 0, 0); 2556 2557 // Calculate the start and end of the stroke, which are just the lower left 2558 // and upper right corners of the canvas, inset by the radius of the endcap 2559 // so we don't clip the endcap off. 2560 const SkScalar kEndCapRadiusPixels = kStrokeWidthPixels / SkIntToScalar(2); 2561 const SkPoint start_point = { 2562 kEndCapRadiusPixels, 2563 SkIntToScalar(scheme_rect.Height()) - kEndCapRadiusPixels }; 2564 const SkPoint end_point = { 2565 SkIntToScalar(scheme_rect.Width()) - kEndCapRadiusPixels, 2566 kEndCapRadiusPixels }; 2567 2568 // Calculate the selection rectangle in canvas coordinates, which we'll use 2569 // to clip the stroke so we can draw the unselected and selected portions. 2570 CHARRANGE sel; 2571 GetSel(sel); 2572 const SkRect selection_rect = { 2573 SkIntToScalar(PosFromChar(sel.cpMin).x - scheme_rect.left), 2574 SkIntToScalar(0), 2575 SkIntToScalar(PosFromChar(sel.cpMax).x - scheme_rect.left), 2576 SkIntToScalar(scheme_rect.Height()) }; 2577 2578 // Draw the unselected portion of the stroke. 2579 sk_canvas->save(); 2580 if (selection_rect.isEmpty() || 2581 sk_canvas->clipRect(selection_rect, SkRegion::kDifference_Op)) { 2582 paint.setColor(location_bar_->GetColor(security_level_, 2583 LocationBarView::SECURITY_TEXT)); 2584 sk_canvas->drawLine(start_point.fX, start_point.fY, 2585 end_point.fX, end_point.fY, paint); 2586 } 2587 sk_canvas->restore(); 2588 2589 // Draw the selected portion of the stroke. 2590 if (!selection_rect.isEmpty() && sk_canvas->clipRect(selection_rect)) { 2591 paint.setColor(location_bar_->GetColor(security_level_, 2592 LocationBarView::SELECTED_TEXT)); 2593 sk_canvas->drawLine(start_point.fX, start_point.fY, 2594 end_point.fX, end_point.fY, paint); 2595 } 2596 2597 // Now copy what we drew to the target HDC. 2598 skia::DrawToNativeContext(sk_canvas, hdc, 2599 scheme_rect.left + canvas_paint_clip_rect.left - canvas_clip_rect.left, 2600 std::max(scheme_rect.top, client_rect.top) + canvas_paint_clip_rect.top - 2601 canvas_clip_rect.top, &canvas_paint_clip_rect); 2602} 2603 2604void OmniboxViewWin::DrawDropHighlight(HDC hdc, 2605 const CRect& client_rect, 2606 const CRect& paint_clip_rect) { 2607 DCHECK_NE(-1, drop_highlight_position_); 2608 2609 const int highlight_y = client_rect.top + font_y_adjustment_; 2610 const int highlight_x = PosFromChar(drop_highlight_position_).x - 1; 2611 const CRect highlight_rect(highlight_x, 2612 highlight_y, 2613 highlight_x + 1, 2614 highlight_y + font_.GetHeight()); 2615 2616 // Clip the highlight to the region being painted. 2617 CRect clip_rect; 2618 clip_rect.IntersectRect(highlight_rect, paint_clip_rect); 2619 if (clip_rect.IsRectNull()) 2620 return; 2621 2622 HGDIOBJ last_pen = SelectObject(hdc, CreatePen(PS_SOLID, 1, RGB(0, 0, 0))); 2623 MoveToEx(hdc, clip_rect.left, clip_rect.top, NULL); 2624 LineTo(hdc, clip_rect.left, clip_rect.bottom); 2625 DeleteObject(SelectObject(hdc, last_pen)); 2626} 2627 2628void OmniboxViewWin::TextChanged() { 2629 ScopedFreeze freeze(this, GetTextObjectModel()); 2630 OmniboxView::TextChanged(); 2631} 2632 2633ITextDocument* OmniboxViewWin::GetTextObjectModel() const { 2634 if (!text_object_model_) { 2635 // This is lazily initialized, instead of being initialized in the 2636 // constructor, in order to avoid hurting startup performance. 2637 base::win::ScopedComPtr<IRichEditOle, NULL> ole_interface; 2638 ole_interface.Attach(GetOleInterface()); 2639 if (ole_interface) { 2640 ole_interface.QueryInterface( 2641 __uuidof(ITextDocument), 2642 reinterpret_cast<void**>(&text_object_model_)); 2643 } 2644 } 2645 return text_object_model_; 2646} 2647 2648void OmniboxViewWin::StartDragIfNecessary(const CPoint& point) { 2649 if (initiated_drag_ || !IsDrag(click_point_[kLeft], point)) 2650 return; 2651 2652 ui::OSExchangeData data; 2653 2654 DWORD supported_modes = DROPEFFECT_COPY; 2655 2656 CHARRANGE sel; 2657 GetSelection(sel); 2658 2659 // We're about to start a drag session, but the edit is expecting a mouse up 2660 // that it uses to reset internal state. If we don't send a mouse up now, 2661 // when the mouse moves back into the edit the edit will reset the selection. 2662 // So, we send the event now which resets the selection. We then restore the 2663 // selection and start the drag. We always send lbuttonup as otherwise we 2664 // might trigger a context menu (right up). This seems scary, but doesn't 2665 // seem to cause problems. 2666 { 2667 ScopedFreeze freeze(this, GetTextObjectModel()); 2668 DefWindowProc(WM_LBUTTONUP, 0, 2669 MAKELPARAM(click_point_[kLeft].x, click_point_[kLeft].y)); 2670 SetSelectionRange(sel); 2671 } 2672 2673 const string16 start_text(GetText()); 2674 string16 text_to_write(GetSelectedText()); 2675 GURL url; 2676 bool write_url; 2677 const bool is_all_selected = IsSelectAllForRange(sel); 2678 2679 // |sel| was set by GetSelection(), which preserves selection direction, so 2680 // sel.cpMin may not be the smaller value. 2681 model()->AdjustTextForCopy(std::min(sel.cpMin, sel.cpMax), is_all_selected, 2682 &text_to_write, &url, &write_url); 2683 2684 if (write_url) { 2685 string16 title; 2686 gfx::Image favicon; 2687 if (is_all_selected) 2688 model()->GetDataForURLExport(&url, &title, &favicon); 2689 button_drag_utils::SetURLAndDragImage(url, title, favicon.AsImageSkia(), 2690 &data, native_view_host_->GetWidget()); 2691 supported_modes |= DROPEFFECT_LINK; 2692 content::RecordAction(UserMetricsAction("Omnibox_DragURL")); 2693 } else { 2694 supported_modes |= DROPEFFECT_MOVE; 2695 content::RecordAction(UserMetricsAction("Omnibox_DragString")); 2696 } 2697 2698 data.SetString(text_to_write); 2699 2700 scoped_refptr<ui::DragSourceWin> drag_source(new ui::DragSourceWin); 2701 DWORD dropped_mode; 2702 base::AutoReset<bool> auto_reset_in_drag(&in_drag_, true); 2703 if (DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data), 2704 drag_source, supported_modes, &dropped_mode) == 2705 DRAGDROP_S_DROP) { 2706 if ((dropped_mode == DROPEFFECT_MOVE) && (start_text == GetText())) { 2707 ScopedFreeze freeze(this, GetTextObjectModel()); 2708 OnBeforePossibleChange(); 2709 SetSelectionRange(sel); 2710 ReplaceSel(L"", true); 2711 OnAfterPossibleChange(); 2712 } 2713 // else case, not a move or it was a move and the drop was on us. 2714 // If the drop was on us, EditDropTarget took care of the move so that 2715 // we don't have to delete the text. 2716 possible_drag_ = false; 2717 } else { 2718 // Drag was canceled or failed. The mouse may still be down and 2719 // over us, in which case we need possible_drag_ to remain true so 2720 // that we don't forward mouse move events to the edit which will 2721 // start another drag. 2722 // 2723 // NOTE: we didn't use mouse capture during the mouse down as DoDragDrop 2724 // does its own capture. 2725 CPoint cursor_location; 2726 GetCursorPos(&cursor_location); 2727 2728 CRect client_rect; 2729 GetClientRect(&client_rect); 2730 2731 CPoint client_origin_on_screen(client_rect.left, client_rect.top); 2732 ClientToScreen(&client_origin_on_screen); 2733 client_rect.MoveToXY(client_origin_on_screen.x, 2734 client_origin_on_screen.y); 2735 possible_drag_ = (client_rect.PtInRect(cursor_location) && 2736 ((GetKeyState(VK_LBUTTON) != 0) || 2737 (GetKeyState(VK_MBUTTON) != 0) || 2738 (GetKeyState(VK_RBUTTON) != 0))); 2739 } 2740 2741 initiated_drag_ = true; 2742 tracking_click_[kLeft] = false; 2743} 2744 2745void OmniboxViewWin::OnPossibleDrag(const CPoint& point) { 2746 if (possible_drag_) 2747 return; 2748 2749 click_point_[kLeft] = point; 2750 initiated_drag_ = false; 2751 2752 CHARRANGE selection; 2753 GetSel(selection); 2754 if (selection.cpMin != selection.cpMax) { 2755 const POINT min_sel_location(PosFromChar(selection.cpMin)); 2756 const POINT max_sel_location(PosFromChar(selection.cpMax)); 2757 // NOTE: we don't consider the y location here as we always pass a 2758 // y-coordinate in the middle to the default handler which always triggers 2759 // a drag regardless of the y-coordinate. 2760 possible_drag_ = (point.x >= min_sel_location.x) && 2761 (point.x < max_sel_location.x); 2762 } 2763} 2764 2765void OmniboxViewWin::RepaintDropHighlight(int position) { 2766 if ((position != -1) && (position <= GetTextLength())) { 2767 const POINT min_loc(PosFromChar(position)); 2768 const RECT highlight_bounds = {min_loc.x - 1, font_y_adjustment_, 2769 min_loc.x + 2, font_.GetHeight() + font_y_adjustment_}; 2770 InvalidateRect(&highlight_bounds, false); 2771 } 2772} 2773 2774void OmniboxViewWin::BuildContextMenu() { 2775 if (context_menu_contents_.get()) 2776 return; 2777 2778 context_menu_contents_.reset(new ui::SimpleMenuModel(this)); 2779 // Set up context menu. 2780 if (popup_window_mode_) { 2781 context_menu_contents_->AddItemWithStringId(IDC_COPY, IDS_COPY); 2782 } else { 2783 context_menu_contents_->AddItemWithStringId(IDS_UNDO, IDS_UNDO); 2784 context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR); 2785 context_menu_contents_->AddItemWithStringId(IDC_CUT, IDS_CUT); 2786 context_menu_contents_->AddItemWithStringId(IDC_COPY, IDS_COPY); 2787 if (chrome::IsQueryExtractionEnabled(model()->profile())) 2788 context_menu_contents_->AddItemWithStringId(IDC_COPY_URL, IDS_COPY_URL); 2789 context_menu_contents_->AddItemWithStringId(IDC_PASTE, IDS_PASTE); 2790 // GetContextualLabel() will override this next label with the 2791 // IDS_PASTE_AND_SEARCH label as needed. 2792 context_menu_contents_->AddItemWithStringId(IDS_PASTE_AND_GO, 2793 IDS_PASTE_AND_GO); 2794 context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR); 2795 context_menu_contents_->AddItemWithStringId(IDS_SELECT_ALL, IDS_SELECT_ALL); 2796 context_menu_contents_->AddSeparator(ui::NORMAL_SEPARATOR); 2797 context_menu_contents_->AddItemWithStringId(IDC_EDIT_SEARCH_ENGINES, 2798 IDS_EDIT_SEARCH_ENGINES); 2799 } 2800} 2801 2802void OmniboxViewWin::SelectAllIfNecessary(MouseButton button, 2803 const CPoint& point) { 2804 // When the user has clicked and released to give us focus, select all. 2805 if (tracking_click_[button] && 2806 !IsDrag(click_point_[button], point)) { 2807 // Select all in the reverse direction so as not to scroll the caret 2808 // into view and shift the contents jarringly. 2809 SelectAll(true); 2810 possible_drag_ = false; 2811 } 2812} 2813 2814void OmniboxViewWin::TrackMousePosition(MouseButton button, 2815 const CPoint& point) { 2816 if (gaining_focus_.get()) { 2817 // This click is giving us focus, so we need to track how much the mouse 2818 // moves to see if it's a drag or just a click. Clicks should select all 2819 // the text. 2820 tracking_click_[button] = true; 2821 click_point_[button] = point; 2822 } 2823} 2824 2825int OmniboxViewWin::GetHorizontalMargin() const { 2826 RECT rect; 2827 GetRect(&rect); 2828 RECT client_rect; 2829 GetClientRect(&client_rect); 2830 return (rect.left - client_rect.left) + (client_rect.right - rect.right); 2831} 2832 2833int OmniboxViewWin::WidthNeededToDisplay(const string16& text) const { 2834 // Use font_.GetStringWidth() instead of PosFromChar(GetTextLength()) because 2835 // PosFromChar() is apparently buggy. In both LTR UI and RTL UI with 2836 // left-to-right layout, PosFromChar(i) might return 0 when i is greater than 2837 // 1. 2838 return font_.GetStringWidth(text) + GetHorizontalMargin(); 2839} 2840