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