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