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/ui/views/autocomplete/autocomplete_popup_contents_view.h"
6
7 #include "base/compiler_specific.h"
8 #include "base/utf_string_conversions.h"
9 #include "chrome/browser/autocomplete/autocomplete_edit_view.h"
10 #include "chrome/browser/autocomplete/autocomplete_popup_model.h"
11 #include "chrome/browser/instant/instant_confirm_dialog.h"
12 #include "chrome/browser/instant/promo_counter.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/views/autocomplete/autocomplete_result_view.h"
15 #include "chrome/browser/ui/views/bubble/bubble_border.h"
16 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
17 #include "grit/chromium_strings.h"
18 #include "grit/generated_resources.h"
19 #include "grit/theme_resources.h"
20 #include "third_party/skia/include/core/SkShader.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "ui/base/resource/resource_bundle.h"
23 #include "ui/base/theme_provider.h"
24 #include "ui/gfx/canvas_skia.h"
25 #include "ui/gfx/insets.h"
26 #include "ui/gfx/path.h"
27 #include "unicode/ubidi.h"
28 #include "views/controls/button/text_button.h"
29 #include "views/controls/label.h"
30 #include "views/layout/grid_layout.h"
31 #include "views/layout/layout_constants.h"
32 #include "views/painter.h"
33 #include "views/widget/widget.h"
34 #include "views/window/window.h"
35
36 #if defined(OS_WIN)
37 #include <commctrl.h>
38 #include <dwmapi.h>
39 #include <objidl.h>
40
41 #include "base/win/scoped_gdi_object.h"
42 #include "views/widget/widget_win.h"
43 #endif
44
45 #if defined(OS_LINUX)
46 #include "ui/gfx/skia_utils_gtk.h"
47 #endif
48
49 namespace {
50
51 const SkAlpha kGlassPopupAlpha = 240;
52 const SkAlpha kOpaquePopupAlpha = 255;
53 // The size delta between the font used for the edit and the result rows. Passed
54 // to gfx::Font::DeriveFont.
55 #if defined(OS_CHROMEOS)
56 // Don't adjust the size on Chrome OS (http://crbug.com/61433).
57 const int kEditFontAdjust = 0;
58 #else
59 const int kEditFontAdjust = -1;
60 #endif
61
62 // Horizontal padding between the buttons on the opt in promo.
63 const int kOptInButtonPadding = 2;
64
65 // Padding around the opt in view.
66 const int kOptInLeftPadding = 12;
67 const int kOptInRightPadding = 10;
68 const int kOptInTopPadding = 6;
69 const int kOptInBottomPadding = 5;
70
71 // Horizontal/Vertical inset of the promo background.
72 const int kOptInBackgroundHInset = 6;
73 const int kOptInBackgroundVInset = 2;
74
75 // Border for instant opt-in buttons. Consists of two 9 patch painters: one for
76 // the normal state, the other for the pressed state.
77 class OptInButtonBorder : public views::Border {
78 public:
OptInButtonBorder()79 OptInButtonBorder() {
80 border_painter_.reset(CreatePainter(IDR_OPT_IN_BUTTON));
81 border_pushed_painter_.reset(CreatePainter(IDR_OPT_IN_BUTTON_P));
82 }
83
Paint(const views::View & view,gfx::Canvas * canvas) const84 virtual void Paint(const views::View& view, gfx::Canvas* canvas) const {
85 views::Painter* painter;
86 if (static_cast<const views::CustomButton&>(view).state() ==
87 views::CustomButton::BS_PUSHED) {
88 painter = border_pushed_painter_.get();
89 } else {
90 painter = border_painter_.get();
91 }
92 painter->Paint(view.width(), view.height(), canvas);
93 }
94
GetInsets(gfx::Insets * insets) const95 virtual void GetInsets(gfx::Insets* insets) const {
96 insets->Set(3, 8, 3, 8);
97 }
98
99 private:
100 // Creates 9 patch painter from the image with the id |image_id|.
CreatePainter(int image_id)101 views::Painter* CreatePainter(int image_id) {
102 SkBitmap* image =
103 ResourceBundle::GetSharedInstance().GetBitmapNamed(image_id);
104 int w = image->width() / 2;
105 if (image->width() % 2 == 0)
106 w--;
107 int h = image->height() / 2;
108 if (image->height() % 2 == 0)
109 h--;
110 gfx::Insets insets(h, w, h, w);
111 return views::Painter::CreateImagePainter(*image, insets, true);
112 }
113
114 scoped_ptr<views::Painter> border_painter_;
115 scoped_ptr<views::Painter> border_pushed_painter_;
116
117 DISALLOW_COPY_AND_ASSIGN(OptInButtonBorder);
118 };
119
120 } // namespace
121
122 class AutocompletePopupContentsView::InstantOptInView
123 : public views::View,
124 public views::ButtonListener {
125 public:
InstantOptInView(AutocompletePopupContentsView * contents_view,const gfx::Font & label_font,const gfx::Font & button_font)126 InstantOptInView(AutocompletePopupContentsView* contents_view,
127 const gfx::Font& label_font,
128 const gfx::Font& button_font)
129 : contents_view_(contents_view),
130 bg_painter_(views::Painter::CreateVerticalGradient(
131 SkColorSetRGB(255, 242, 183),
132 SkColorSetRGB(250, 230, 145))) {
133 views::Label* label = new views::Label(
134 UTF16ToWide(l10n_util::GetStringUTF16(IDS_INSTANT_OPT_IN_LABEL)));
135 label->SetFont(label_font);
136
137 views::GridLayout* layout = new views::GridLayout(this);
138 layout->SetInsets(kOptInTopPadding, kOptInLeftPadding,
139 kOptInBottomPadding, kOptInRightPadding);
140 SetLayoutManager(layout);
141
142 const int first_column_set = 1;
143 views::GridLayout::Alignment v_align = views::GridLayout::CENTER;
144 views::ColumnSet* column_set = layout->AddColumnSet(first_column_set);
145 column_set->AddColumn(views::GridLayout::TRAILING, v_align, 1,
146 views::GridLayout::USE_PREF, 0, 0);
147 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
148 column_set->AddColumn(views::GridLayout::CENTER, v_align, 0,
149 views::GridLayout::USE_PREF, 0, 0);
150 column_set->AddPaddingColumn(0, kOptInButtonPadding);
151 column_set->AddColumn(views::GridLayout::CENTER, v_align, 0,
152 views::GridLayout::USE_PREF, 0, 0);
153 column_set->LinkColumnSizes(2, 4, -1);
154 layout->StartRow(0, first_column_set);
155 layout->AddView(label);
156 layout->AddView(CreateButton(IDS_INSTANT_OPT_IN_ENABLE, button_font));
157 layout->AddView(CreateButton(IDS_INSTANT_OPT_IN_NO_THANKS, button_font));
158 }
159
ButtonPressed(views::Button * sender,const views::Event & event)160 virtual void ButtonPressed(views::Button* sender, const views::Event& event) {
161 contents_view_->UserPressedOptIn(
162 sender->tag() == IDS_INSTANT_OPT_IN_ENABLE);
163 // WARNING: we've been deleted.
164 }
165
OnPaint(gfx::Canvas * canvas)166 virtual void OnPaint(gfx::Canvas* canvas) {
167 canvas->Save();
168 canvas->TranslateInt(kOptInBackgroundHInset, kOptInBackgroundVInset);
169 bg_painter_->Paint(width() - kOptInBackgroundHInset * 2,
170 height() - kOptInBackgroundVInset * 2, canvas);
171 canvas->DrawRectInt(ResourceBundle::toolbar_separator_color, 0, 0,
172 width() - kOptInBackgroundHInset * 2,
173 height() - kOptInBackgroundVInset * 2);
174 canvas->Restore();
175 }
176
177 private:
178 // Creates and returns a button configured for the opt-in promo.
CreateButton(int id,const gfx::Font & font)179 views::View* CreateButton(int id, const gfx::Font& font) {
180 // NOTE: we can't use NativeButton as the popup is a layered window and
181 // native buttons don't draw in layered windows.
182 // TODO: these buttons look crap. Figure out the right border/background to
183 // use.
184 views::TextButton* button =
185 new views::TextButton(this, UTF16ToWide(l10n_util::GetStringUTF16(id)));
186 button->set_border(new OptInButtonBorder());
187 button->SetNormalHasBorder(true);
188 button->set_tag(id);
189 button->SetFont(font);
190 button->set_animate_on_state_change(false);
191 return button;
192 }
193
194 AutocompletePopupContentsView* contents_view_;
195 scoped_ptr<views::Painter> bg_painter_;
196
197 DISALLOW_COPY_AND_ASSIGN(InstantOptInView);
198 };
199
200 ////////////////////////////////////////////////////////////////////////////////
201 // AutocompletePopupContentsView, public:
202
AutocompletePopupContentsView(const gfx::Font & font,AutocompleteEditView * edit_view,AutocompleteEditModel * edit_model,Profile * profile,const views::View * location_bar)203 AutocompletePopupContentsView::AutocompletePopupContentsView(
204 const gfx::Font& font,
205 AutocompleteEditView* edit_view,
206 AutocompleteEditModel* edit_model,
207 Profile* profile,
208 const views::View* location_bar)
209 : model_(new AutocompletePopupModel(this, edit_model, profile)),
210 opt_in_view_(NULL),
211 edit_view_(edit_view),
212 location_bar_(location_bar),
213 result_font_(font.DeriveFont(kEditFontAdjust)),
214 result_bold_font_(result_font_.DeriveFont(0, gfx::Font::BOLD)),
215 ignore_mouse_drag_(false),
216 ALLOW_THIS_IN_INITIALIZER_LIST(size_animation_(this)) {
217 // The following little dance is required because set_border() requires a
218 // pointer to a non-const object.
219 BubbleBorder* bubble_border = new BubbleBorder(BubbleBorder::NONE);
220 bubble_border_ = bubble_border;
221 set_border(bubble_border);
222 // The contents is owned by the LocationBarView.
223 set_parent_owned(false);
224 }
225
~AutocompletePopupContentsView()226 AutocompletePopupContentsView::~AutocompletePopupContentsView() {
227 // We don't need to do anything with |popup_| here. The OS either has already
228 // closed the window, in which case it's been deleted, or it will soon, in
229 // which case there's nothing we need to do.
230 }
231
GetPopupBounds() const232 gfx::Rect AutocompletePopupContentsView::GetPopupBounds() const {
233 if (!size_animation_.is_animating())
234 return target_bounds_;
235
236 gfx::Rect current_frame_bounds = start_bounds_;
237 int total_height_delta = target_bounds_.height() - start_bounds_.height();
238 // Round |current_height_delta| instead of truncating so we won't leave single
239 // white pixels at the bottom of the popup as long when animating very small
240 // height differences.
241 int current_height_delta = static_cast<int>(
242 size_animation_.GetCurrentValue() * total_height_delta - 0.5);
243 current_frame_bounds.set_height(
244 current_frame_bounds.height() + current_height_delta);
245 return current_frame_bounds;
246 }
247
LayoutChildren()248 void AutocompletePopupContentsView::LayoutChildren() {
249 gfx::Rect contents_rect = GetContentsBounds();
250 int top = contents_rect.y();
251 for (int i = 0; i < child_count(); ++i) {
252 View* v = GetChildViewAt(i);
253 if (v->IsVisible()) {
254 v->SetBounds(contents_rect.x(), top, contents_rect.width(),
255 v->GetPreferredSize().height());
256 top = v->bounds().bottom();
257 }
258 }
259 }
260
261 ////////////////////////////////////////////////////////////////////////////////
262 // AutocompletePopupContentsView, AutocompletePopupView overrides:
263
IsOpen() const264 bool AutocompletePopupContentsView::IsOpen() const {
265 return (popup_ != NULL);
266 }
267
InvalidateLine(size_t line)268 void AutocompletePopupContentsView::InvalidateLine(size_t line) {
269 GetChildViewAt(static_cast<int>(line))->SchedulePaint();
270 }
271
UpdatePopupAppearance()272 void AutocompletePopupContentsView::UpdatePopupAppearance() {
273 if (model_->result().empty()) {
274 // No matches, close any existing popup.
275 if (popup_ != NULL) {
276 size_animation_.Stop();
277 // NOTE: Do NOT use CloseNow() here, as we may be deep in a callstack
278 // triggered by the popup receiving a message (e.g. LBUTTONUP), and
279 // destroying the popup would cause us to read garbage when we unwind back
280 // to that level.
281 popup_->Close(); // This will eventually delete the popup.
282 popup_.reset();
283 }
284 return;
285 }
286
287 // Update the match cached by each row, in the process of doing so make sure
288 // we have enough row views.
289 size_t child_rv_count = child_count();
290 if (opt_in_view_) {
291 DCHECK(child_rv_count > 0);
292 child_rv_count--;
293 }
294 for (size_t i = 0; i < model_->result().size(); ++i) {
295 AutocompleteResultView* result_view;
296 if (i >= child_rv_count) {
297 result_view =
298 CreateResultView(this, i, result_font_, result_bold_font_);
299 AddChildViewAt(result_view, static_cast<int>(i));
300 } else {
301 result_view = static_cast<AutocompleteResultView*>(GetChildViewAt(i));
302 result_view->SetVisible(true);
303 }
304 result_view->SetMatch(GetMatchAtIndex(i));
305 }
306 for (size_t i = model_->result().size(); i < child_rv_count; ++i)
307 GetChildViewAt(i)->SetVisible(false);
308
309 PromoCounter* counter = model_->profile()->GetInstantPromoCounter();
310 if (!opt_in_view_ && counter && counter->ShouldShow(base::Time::Now())) {
311 opt_in_view_ = new InstantOptInView(this, result_bold_font_, result_font_);
312 AddChildView(opt_in_view_);
313 } else if (opt_in_view_ && (!counter ||
314 !counter->ShouldShow(base::Time::Now()))) {
315 delete opt_in_view_;
316 opt_in_view_ = NULL;
317 }
318
319 gfx::Rect new_target_bounds = CalculateTargetBounds(CalculatePopupHeight());
320
321 // If we're animating and our target height changes, reset the animation.
322 // NOTE: If we just reset blindly on _every_ update, then when the user types
323 // rapidly we could get "stuck" trying repeatedly to animate shrinking by the
324 // last few pixels to get to one visible result.
325 if (new_target_bounds.height() != target_bounds_.height())
326 size_animation_.Reset();
327 target_bounds_ = new_target_bounds;
328
329 if (popup_ == NULL) {
330 // If the popup is currently closed, we need to create it.
331 popup_ = (new AutocompletePopupClass)->AsWeakPtr();
332 views::Widget::CreateParams params(views::Widget::CreateParams::TYPE_POPUP);
333 params.can_activate = false;
334 params.transparent = true;
335 popup_->SetCreateParams(params);
336 popup_->Init(location_bar_->GetWidget()->GetNativeView(), GetPopupBounds());
337 popup_->SetContentsView(this);
338 popup_->MoveAbove(popup_->GetRelativeWindowForPopup(
339 edit_view_->GetNativeView()));
340 popup_->Show();
341 } else {
342 // Animate the popup shrinking, but don't animate growing larger since that
343 // would make the popup feel less responsive.
344 start_bounds_ = GetWidget()->GetWindowScreenBounds();
345 if (target_bounds_.height() < start_bounds_.height())
346 size_animation_.Show();
347 else
348 start_bounds_ = target_bounds_;
349 popup_->SetBounds(GetPopupBounds());
350 }
351
352 SchedulePaint();
353 }
354
GetTargetBounds()355 gfx::Rect AutocompletePopupContentsView::GetTargetBounds() {
356 return target_bounds_;
357 }
358
PaintUpdatesNow()359 void AutocompletePopupContentsView::PaintUpdatesNow() {
360 // TODO(beng): remove this from the interface.
361 }
362
OnDragCanceled()363 void AutocompletePopupContentsView::OnDragCanceled() {
364 ignore_mouse_drag_ = true;
365 }
366
367 ////////////////////////////////////////////////////////////////////////////////
368 // AutocompletePopupContentsView, AutocompleteResultViewModel implementation:
369
IsSelectedIndex(size_t index) const370 bool AutocompletePopupContentsView::IsSelectedIndex(size_t index) const {
371 return HasMatchAt(index) ? index == model_->selected_line() : false;
372 }
373
IsHoveredIndex(size_t index) const374 bool AutocompletePopupContentsView::IsHoveredIndex(size_t index) const {
375 return HasMatchAt(index) ? index == model_->hovered_line() : false;
376 }
377
GetIconIfExtensionMatch(size_t index) const378 const SkBitmap* AutocompletePopupContentsView::GetIconIfExtensionMatch(
379 size_t index) const {
380 if (!HasMatchAt(index))
381 return NULL;
382 return model_->GetIconIfExtensionMatch(GetMatchAtIndex(index));
383 }
384
385 ////////////////////////////////////////////////////////////////////////////////
386 // AutocompletePopupContentsView, AnimationDelegate implementation:
387
AnimationProgressed(const ui::Animation * animation)388 void AutocompletePopupContentsView::AnimationProgressed(
389 const ui::Animation* animation) {
390 // We should only be running the animation when the popup is already visible.
391 DCHECK(popup_ != NULL);
392 popup_->SetBounds(GetPopupBounds());
393 }
394
395 ////////////////////////////////////////////////////////////////////////////////
396 // AutocompletePopupContentsView, views::View overrides:
397
Layout()398 void AutocompletePopupContentsView::Layout() {
399 UpdateBlurRegion();
400
401 // Size our children to the available content area.
402 LayoutChildren();
403
404 // We need to manually schedule a paint here since we are a layered window and
405 // won't implicitly require painting until we ask for one.
406 SchedulePaint();
407 }
408
GetEventHandlerForPoint(const gfx::Point & point)409 views::View* AutocompletePopupContentsView::GetEventHandlerForPoint(
410 const gfx::Point& point) {
411 // If there is no opt in view, then we want all mouse events. Otherwise let
412 // any descendants of the opt-in view get mouse events.
413 if (!opt_in_view_)
414 return this;
415
416 views::View* child = views::View::GetEventHandlerForPoint(point);
417 views::View* ancestor = child;
418 while (ancestor && ancestor != opt_in_view_)
419 ancestor = ancestor->parent();
420 return ancestor ? child : this;
421 }
422
OnMousePressed(const views::MouseEvent & event)423 bool AutocompletePopupContentsView::OnMousePressed(
424 const views::MouseEvent& event) {
425 ignore_mouse_drag_ = false; // See comment on |ignore_mouse_drag_| in header.
426 if (event.IsLeftMouseButton() || event.IsMiddleMouseButton()) {
427 size_t index = GetIndexForPoint(event.location());
428 model_->SetHoveredLine(index);
429 if (HasMatchAt(index) && event.IsLeftMouseButton())
430 model_->SetSelectedLine(index, false, false);
431 }
432 return true;
433 }
434
OnMouseDragged(const views::MouseEvent & event)435 bool AutocompletePopupContentsView::OnMouseDragged(
436 const views::MouseEvent& event) {
437 if (event.IsLeftMouseButton() || event.IsMiddleMouseButton()) {
438 size_t index = GetIndexForPoint(event.location());
439 model_->SetHoveredLine(index);
440 if (!ignore_mouse_drag_ && HasMatchAt(index) && event.IsLeftMouseButton())
441 model_->SetSelectedLine(index, false, false);
442 }
443 return true;
444 }
445
OnMouseReleased(const views::MouseEvent & event)446 void AutocompletePopupContentsView::OnMouseReleased(
447 const views::MouseEvent& event) {
448 if (ignore_mouse_drag_) {
449 OnMouseCaptureLost();
450 return;
451 }
452
453 size_t index = GetIndexForPoint(event.location());
454 if (event.IsOnlyMiddleMouseButton())
455 OpenIndex(index, NEW_BACKGROUND_TAB);
456 else if (event.IsOnlyLeftMouseButton())
457 OpenIndex(index, CURRENT_TAB);
458 }
459
OnMouseCaptureLost()460 void AutocompletePopupContentsView::OnMouseCaptureLost() {
461 ignore_mouse_drag_ = false;
462 }
463
OnMouseMoved(const views::MouseEvent & event)464 void AutocompletePopupContentsView::OnMouseMoved(
465 const views::MouseEvent& event) {
466 model_->SetHoveredLine(GetIndexForPoint(event.location()));
467 }
468
OnMouseEntered(const views::MouseEvent & event)469 void AutocompletePopupContentsView::OnMouseEntered(
470 const views::MouseEvent& event) {
471 model_->SetHoveredLine(GetIndexForPoint(event.location()));
472 }
473
OnMouseExited(const views::MouseEvent & event)474 void AutocompletePopupContentsView::OnMouseExited(
475 const views::MouseEvent& event) {
476 model_->SetHoveredLine(AutocompletePopupModel::kNoMatch);
477 }
478
479 ////////////////////////////////////////////////////////////////////////////////
480 // AutocompletePopupContentsView, protected:
481
PaintResultViews(gfx::CanvasSkia * canvas)482 void AutocompletePopupContentsView::PaintResultViews(gfx::CanvasSkia* canvas) {
483 canvas->drawColor(AutocompleteResultView::GetColor(
484 AutocompleteResultView::NORMAL, AutocompleteResultView::BACKGROUND));
485 View::PaintChildren(canvas);
486 }
487
CalculatePopupHeight()488 int AutocompletePopupContentsView::CalculatePopupHeight() {
489 DCHECK_GE(static_cast<size_t>(child_count()), model_->result().size());
490 int popup_height = 0;
491 for (size_t i = 0; i < model_->result().size(); ++i)
492 popup_height += GetChildViewAt(i)->GetPreferredSize().height();
493 return popup_height +
494 (opt_in_view_ ? opt_in_view_->GetPreferredSize().height() : 0);
495 }
496
CreateResultView(AutocompleteResultViewModel * model,int model_index,const gfx::Font & font,const gfx::Font & bold_font)497 AutocompleteResultView* AutocompletePopupContentsView::CreateResultView(
498 AutocompleteResultViewModel* model,
499 int model_index,
500 const gfx::Font& font,
501 const gfx::Font& bold_font) {
502 return new AutocompleteResultView(model, model_index, font, bold_font);
503 }
504
505 ////////////////////////////////////////////////////////////////////////////////
506 // AutocompletePopupContentsView, views::View overrides, protected:
507
OnPaint(gfx::Canvas * canvas)508 void AutocompletePopupContentsView::OnPaint(gfx::Canvas* canvas) {
509 // We paint our children in an unconventional way.
510 //
511 // Because the border of this view creates an anti-aliased round-rect region
512 // for the contents, we need to render our rectangular result child views into
513 // this round rect region. We can't use a simple clip because clipping is
514 // 1-bit and we get nasty jagged edges.
515 //
516 // Instead, we paint all our children into a second canvas and use that as a
517 // shader to fill a path representing the round-rect clipping region. This
518 // yields a nice anti-aliased edge.
519 gfx::CanvasSkia contents_canvas(width(), height(), true);
520 PaintResultViews(&contents_canvas);
521
522 // We want the contents background to be slightly transparent so we can see
523 // the blurry glass effect on DWM systems behind. We do this _after_ we paint
524 // the children since they paint text, and GDI will reset this alpha data if
525 // we paint text after this call.
526 MakeCanvasTransparent(&contents_canvas);
527
528 // Now paint the contents of the contents canvas into the actual canvas.
529 SkPaint paint;
530 paint.setAntiAlias(true);
531
532 SkShader* shader = SkShader::CreateBitmapShader(
533 contents_canvas.getDevice()->accessBitmap(false),
534 SkShader::kClamp_TileMode,
535 SkShader::kClamp_TileMode);
536 paint.setShader(shader);
537 shader->unref();
538
539 gfx::Path path;
540 MakeContentsPath(&path, GetContentsBounds());
541 canvas->AsCanvasSkia()->drawPath(path, paint);
542
543 // Now we paint the border, so it will be alpha-blended atop the contents.
544 // This looks slightly better in the corners than drawing the contents atop
545 // the border.
546 OnPaintBorder(canvas);
547 }
548
PaintChildren(gfx::Canvas * canvas)549 void AutocompletePopupContentsView::PaintChildren(gfx::Canvas* canvas) {
550 // We paint our children inside OnPaint().
551 }
552
553 ////////////////////////////////////////////////////////////////////////////////
554 // AutocompletePopupContentsView, private:
555
HasMatchAt(size_t index) const556 bool AutocompletePopupContentsView::HasMatchAt(size_t index) const {
557 return index < model_->result().size();
558 }
559
GetMatchAtIndex(size_t index) const560 const AutocompleteMatch& AutocompletePopupContentsView::GetMatchAtIndex(
561 size_t index) const {
562 return model_->result().match_at(index);
563 }
564
MakeContentsPath(gfx::Path * path,const gfx::Rect & bounding_rect)565 void AutocompletePopupContentsView::MakeContentsPath(
566 gfx::Path* path,
567 const gfx::Rect& bounding_rect) {
568 SkRect rect;
569 rect.set(SkIntToScalar(bounding_rect.x()),
570 SkIntToScalar(bounding_rect.y()),
571 SkIntToScalar(bounding_rect.right()),
572 SkIntToScalar(bounding_rect.bottom()));
573
574 SkScalar radius = SkIntToScalar(BubbleBorder::GetCornerRadius());
575 path->addRoundRect(rect, radius, radius);
576 }
577
UpdateBlurRegion()578 void AutocompletePopupContentsView::UpdateBlurRegion() {
579 #if defined(OS_WIN)
580 // We only support background blurring on Vista with Aero-Glass enabled.
581 if (!views::WidgetWin::IsAeroGlassEnabled() || !GetWidget())
582 return;
583
584 // Provide a blurred background effect within the contents region of the
585 // popup.
586 DWM_BLURBEHIND bb = {0};
587 bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
588 bb.fEnable = true;
589
590 // Translate the contents rect into widget coordinates, since that's what
591 // DwmEnableBlurBehindWindow expects a region in.
592 gfx::Rect contents_rect = GetContentsBounds();
593 gfx::Point origin(contents_rect.origin());
594 views::View::ConvertPointToWidget(this, &origin);
595 contents_rect.set_origin(origin);
596
597 gfx::Path contents_path;
598 MakeContentsPath(&contents_path, contents_rect);
599 base::win::ScopedGDIObject<HRGN> popup_region;
600 popup_region.Set(contents_path.CreateNativeRegion());
601 bb.hRgnBlur = popup_region.Get();
602 DwmEnableBlurBehindWindow(GetWidget()->GetNativeView(), &bb);
603 #endif
604 }
605
MakeCanvasTransparent(gfx::Canvas * canvas)606 void AutocompletePopupContentsView::MakeCanvasTransparent(
607 gfx::Canvas* canvas) {
608 // Allow the window blur effect to show through the popup background.
609 SkAlpha alpha = GetThemeProvider()->ShouldUseNativeFrame() ?
610 kGlassPopupAlpha : kOpaquePopupAlpha;
611 canvas->AsCanvasSkia()->drawColor(SkColorSetA(
612 AutocompleteResultView::GetColor(AutocompleteResultView::NORMAL,
613 AutocompleteResultView::BACKGROUND), alpha), SkXfermode::kDstIn_Mode);
614 }
615
OpenIndex(size_t index,WindowOpenDisposition disposition)616 void AutocompletePopupContentsView::OpenIndex(
617 size_t index,
618 WindowOpenDisposition disposition) {
619 if (!HasMatchAt(index))
620 return;
621
622 const AutocompleteMatch& match = model_->result().match_at(index);
623 // OpenURL() may close the popup, which will clear the result set and, by
624 // extension, |match| and its contents. So copy the relevant strings out to
625 // make sure they stay alive until the call completes.
626 const GURL url(match.destination_url);
627 string16 keyword;
628 const bool is_keyword_hint = model_->GetKeywordForMatch(match, &keyword);
629 edit_view_->OpenURL(url, disposition, match.transition, GURL(), index,
630 is_keyword_hint ? string16() : keyword);
631 }
632
GetIndexForPoint(const gfx::Point & point)633 size_t AutocompletePopupContentsView::GetIndexForPoint(
634 const gfx::Point& point) {
635 if (!HitTest(point))
636 return AutocompletePopupModel::kNoMatch;
637
638 int nb_match = model_->result().size();
639 DCHECK(nb_match <= child_count());
640 for (int i = 0; i < nb_match; ++i) {
641 views::View* child = GetChildViewAt(i);
642 gfx::Point point_in_child_coords(point);
643 View::ConvertPointToView(this, child, &point_in_child_coords);
644 if (child->HitTest(point_in_child_coords))
645 return i;
646 }
647 return AutocompletePopupModel::kNoMatch;
648 }
649
CalculateTargetBounds(int h)650 gfx::Rect AutocompletePopupContentsView::CalculateTargetBounds(int h) {
651 gfx::Rect location_bar_bounds(location_bar_->GetContentsBounds());
652 const views::Border* border = location_bar_->border();
653 if (border) {
654 // Adjust for the border so that the bubble and location bar borders are
655 // aligned.
656 gfx::Insets insets;
657 border->GetInsets(&insets);
658 location_bar_bounds.Inset(insets.left(), 0, insets.right(), 0);
659 } else {
660 // The normal location bar is drawn using a background graphic that includes
661 // the border, so we inset by enough to make the edges line up, and the
662 // bubble appear at the same height as the Star bubble.
663 location_bar_bounds.Inset(LocationBarView::kNormalHorizontalEdgeThickness,
664 0);
665 }
666 gfx::Point location_bar_origin(location_bar_bounds.origin());
667 views::View::ConvertPointToScreen(location_bar_, &location_bar_origin);
668 location_bar_bounds.set_origin(location_bar_origin);
669 return bubble_border_->GetBounds(
670 location_bar_bounds, gfx::Size(location_bar_bounds.width(), h));
671 }
672
UserPressedOptIn(bool opt_in)673 void AutocompletePopupContentsView::UserPressedOptIn(bool opt_in) {
674 delete opt_in_view_;
675 opt_in_view_ = NULL;
676 PromoCounter* counter = model_->profile()->GetInstantPromoCounter();
677 DCHECK(counter);
678 counter->Hide();
679 if (opt_in) {
680 browser::ShowInstantConfirmDialogIfNecessary(
681 location_bar_->GetWindow()->GetNativeWindow(), model_->profile());
682 }
683 UpdatePopupAppearance();
684 }
685