autocomplete_popup_contents_view.cc revision 4a5e2dc747d50c653511c68ccb2cfbfb740bd5a7
1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/views/autocomplete/autocomplete_popup_contents_view.h"
6
7#include "app/bidi_line_iterator.h"
8#include "app/l10n_util.h"
9#include "app/resource_bundle.h"
10#include "app/theme_provider.h"
11#include "app/text_elider.h"
12#include "base/compiler_specific.h"
13#include "base/i18n/rtl.h"
14#include "base/utf_string_conversions.h"
15#include "chrome/browser/autocomplete/autocomplete_edit_view.h"
16#include "chrome/browser/autocomplete/autocomplete_match.h"
17#include "chrome/browser/autocomplete/autocomplete_popup_model.h"
18#include "chrome/browser/instant/instant_confirm_dialog.h"
19#include "chrome/browser/instant/promo_counter.h"
20#include "chrome/browser/profile.h"
21#include "chrome/browser/views/bubble_border.h"
22#include "chrome/browser/views/location_bar/location_bar_view.h"
23#include "gfx/canvas_skia.h"
24#include "gfx/color_utils.h"
25#include "gfx/insets.h"
26#include "gfx/path.h"
27#include "grit/chromium_strings.h"
28#include "grit/generated_resources.h"
29#include "grit/theme_resources.h"
30#include "third_party/skia/include/core/SkShader.h"
31#include "unicode/ubidi.h"
32#include "views/controls/button/text_button.h"
33#include "views/controls/label.h"
34#include "views/grid_layout.h"
35#include "views/painter.h"
36#include "views/standard_layout.h"
37#include "views/widget/widget.h"
38#include "views/window/window.h"
39
40#if defined(OS_WIN)
41#include <objidl.h>
42#include <commctrl.h>
43#include <dwmapi.h>
44
45#include "app/win_util.h"
46#endif
47
48#if defined(OS_LINUX)
49#include "chrome/browser/gtk/gtk_util.h"
50#include "gfx/skia_utils_gtk.h"
51#endif
52
53namespace {
54
55enum ResultViewState {
56  NORMAL = 0,
57  SELECTED,
58  HOVERED,
59  NUM_STATES
60};
61
62enum ColorKind {
63  BACKGROUND = 0,
64  TEXT,
65  DIMMED_TEXT,
66  URL,
67  NUM_KINDS
68};
69
70SkColor GetColor(ResultViewState state, ColorKind kind) {
71  static bool initialized = false;
72  static SkColor colors[NUM_STATES][NUM_KINDS];
73  if (!initialized) {
74#if defined(OS_WIN)
75    colors[NORMAL][BACKGROUND] = color_utils::GetSysSkColor(COLOR_WINDOW);
76    colors[SELECTED][BACKGROUND] = color_utils::GetSysSkColor(COLOR_HIGHLIGHT);
77    colors[NORMAL][TEXT] = color_utils::GetSysSkColor(COLOR_WINDOWTEXT);
78    colors[SELECTED][TEXT] = color_utils::GetSysSkColor(COLOR_HIGHLIGHTTEXT);
79#elif defined(OS_LINUX)
80    GdkColor bg_color, selected_bg_color, text_color, selected_text_color;
81    gtk_util::GetTextColors(
82        &bg_color, &selected_bg_color, &text_color, &selected_text_color);
83    colors[NORMAL][BACKGROUND] = gfx::GdkColorToSkColor(bg_color);
84    colors[SELECTED][BACKGROUND] = gfx::GdkColorToSkColor(selected_bg_color);
85    colors[NORMAL][TEXT] = gfx::GdkColorToSkColor(text_color);
86    colors[SELECTED][TEXT] = gfx::GdkColorToSkColor(selected_text_color);
87#else
88    // TODO(beng): source from theme provider.
89    colors[NORMAL][BACKGROUND] = SK_ColorWHITE;
90    colors[SELECTED][BACKGROUND] = SK_ColorBLUE;
91    colors[NORMAL][TEXT] = SK_ColorBLACK;
92    colors[SELECTED][TEXT] = SK_ColorWHITE;
93#endif
94    colors[HOVERED][BACKGROUND] =
95        color_utils::AlphaBlend(colors[SELECTED][BACKGROUND],
96                                colors[NORMAL][BACKGROUND], 64);
97    colors[HOVERED][TEXT] = colors[NORMAL][TEXT];
98    for (int i = 0; i < NUM_STATES; ++i) {
99      colors[i][DIMMED_TEXT] =
100          color_utils::AlphaBlend(colors[i][TEXT], colors[i][BACKGROUND], 128);
101      colors[i][URL] = color_utils::GetReadableColor(SkColorSetRGB(0, 128, 0),
102                                                     colors[i][BACKGROUND]);
103    }
104    initialized = true;
105  }
106
107  return colors[state][kind];
108}
109
110const wchar_t kEllipsis[] = L"\x2026";
111
112const SkAlpha kGlassPopupAlpha = 240;
113const SkAlpha kOpaquePopupAlpha = 255;
114// The minimum distance between the top and bottom of the icon and the top or
115// bottom of the row. "Minimum" is used because the vertical padding may be
116// larger, depending on the size of the text.
117const int kIconVerticalPadding = 2;
118// The minimum distance between the top and bottom of the text and the top or
119// bottom of the row. See comment about the use of "minimum" for
120// kIconVerticalPadding.
121const int kTextVerticalPadding = 3;
122// The size delta between the font used for the edit and the result rows. Passed
123// to gfx::Font::DeriveFont.
124#if !defined(OS_CHROMEOS)
125const int kEditFontAdjust = -1;
126#else
127// Don't adjust font on chromeos as it becomes too small.
128const int kEditFontAdjust = 0;
129#endif
130
131// Horizontal padding between the buttons on the opt in promo.
132const int kOptInButtonPadding = 2;
133
134// Padding around the opt in view.
135const int kOptInLeftPadding = 12;
136const int kOptInRightPadding = 10;
137const int kOptInTopPadding = 6;
138const int kOptInBottomPadding = 5;
139
140// Horizontal/Vertical inset of the promo background.
141const int kOptInBackgroundHInset = 6;
142const int kOptInBackgroundVInset = 2;
143
144// Border for instant opt-in buttons. Consists of two 9 patch painters: one for
145// the normal state, the other for the pressed state.
146class OptInButtonBorder : public views::Border {
147 public:
148  OptInButtonBorder() {
149    border_painter_.reset(CreatePainter(IDR_OPT_IN_BUTTON));
150    border_pushed_painter_.reset(CreatePainter(IDR_OPT_IN_BUTTON_P));
151  }
152
153  virtual void Paint(const views::View& view, gfx::Canvas* canvas) const {
154    views::Painter* painter;
155    if (static_cast<const views::CustomButton&>(view).state() ==
156        views::CustomButton::BS_PUSHED) {
157      painter = border_pushed_painter_.get();
158    } else {
159      painter = border_painter_.get();
160    }
161    painter->Paint(view.width(), view.height(), canvas);
162  }
163
164  virtual void GetInsets(gfx::Insets* insets) const {
165    insets->Set(3, 8, 3, 8);
166  }
167
168 private:
169  // Creates 9 patch painter from the image with the id |image_id|.
170  views::Painter* CreatePainter(int image_id) {
171    SkBitmap* image =
172        ResourceBundle::GetSharedInstance().GetBitmapNamed(image_id);
173    int w = image->width() / 2;
174    if (image->width() % 2 == 0)
175      w--;
176    int h = image->height() / 2;
177    if (image->height() % 2 == 0)
178      h--;
179    gfx::Insets insets(h, w, h, w);
180    return views::Painter::CreateImagePainter(*image, insets, true);
181  }
182
183  scoped_ptr<views::Painter> border_painter_;
184  scoped_ptr<views::Painter> border_pushed_painter_;
185
186  DISALLOW_COPY_AND_ASSIGN(OptInButtonBorder);
187};
188
189}  // namespace
190
191class AutocompletePopupContentsView::InstantOptInView
192    : public views::View,
193      public views::ButtonListener {
194 public:
195  InstantOptInView(AutocompletePopupContentsView* contents_view,
196                   const gfx::Font& label_font,
197                   const gfx::Font& button_font)
198      : contents_view_(contents_view),
199        bg_painter_(views::Painter::CreateVerticalGradient(
200                        SkColorSetRGB(255, 242, 183),
201                        SkColorSetRGB(250, 230, 145))) {
202    views::Label* label =
203        new views::Label(l10n_util::GetString(IDS_INSTANT_OPT_IN_LABEL));
204    label->SetFont(label_font);
205
206    views::GridLayout* layout = new views::GridLayout(this);
207    layout->SetInsets(kOptInTopPadding, kOptInLeftPadding,
208                      kOptInBottomPadding, kOptInRightPadding);
209    SetLayoutManager(layout);
210
211    const int first_column_set = 1;
212    views::GridLayout::Alignment v_align = views::GridLayout::CENTER;
213    views::ColumnSet* column_set = layout->AddColumnSet(first_column_set);
214    column_set->AddColumn(views::GridLayout::TRAILING, v_align, 1,
215                          views::GridLayout::USE_PREF, 0, 0);
216    column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing);
217    column_set->AddColumn(views::GridLayout::CENTER, v_align, 0,
218                          views::GridLayout::USE_PREF, 0, 0);
219    column_set->AddPaddingColumn(0, kOptInButtonPadding);
220    column_set->AddColumn(views::GridLayout::CENTER, v_align, 0,
221                          views::GridLayout::USE_PREF, 0, 0);
222    column_set->LinkColumnSizes(2, 4, -1);
223    layout->StartRow(0, first_column_set);
224    layout->AddView(label);
225    layout->AddView(CreateButton(IDS_INSTANT_OPT_IN_ENABLE, button_font));
226    layout->AddView(CreateButton(IDS_INSTANT_OPT_IN_NO_THANKS, button_font));
227  }
228
229  virtual void ButtonPressed(views::Button* sender, const views::Event& event) {
230    contents_view_->UserPressedOptIn(
231        sender->tag() == IDS_INSTANT_OPT_IN_ENABLE);
232    // WARNING: we've been deleted.
233  }
234
235  virtual void Paint(gfx::Canvas* canvas) {
236    canvas->Save();
237    canvas->TranslateInt(kOptInBackgroundHInset, kOptInBackgroundVInset);
238    bg_painter_->Paint(width() - kOptInBackgroundHInset * 2,
239                       height() - kOptInBackgroundVInset * 2, canvas);
240    canvas->DrawRectInt(ResourceBundle::toolbar_separator_color, 0, 0,
241                        width() - kOptInBackgroundHInset * 2,
242                        height() - kOptInBackgroundVInset * 2);
243    canvas->Restore();
244  }
245
246 private:
247  // Creates and returns a button configured for the opt-in promo.
248  views::View* CreateButton(int id, const gfx::Font& font) {
249    // NOTE: we can't use NativeButton as the popup is a layered window and
250    // native buttons don't draw  in layered windows.
251    // TODO: these buttons look crap. Figure out the right border/background to
252    // use.
253    views::TextButton* button =
254        new views::TextButton(this, l10n_util::GetString(id));
255    button->set_border(new OptInButtonBorder());
256    button->SetNormalHasBorder(true);
257    button->set_tag(id);
258    button->SetFont(font);
259    button->set_animate_on_state_change(false);
260    return button;
261  }
262
263  AutocompletePopupContentsView* contents_view_;
264  scoped_ptr<views::Painter> bg_painter_;
265
266  DISALLOW_COPY_AND_ASSIGN(InstantOptInView);
267};
268
269class AutocompleteResultView : public views::View {
270 public:
271  AutocompleteResultView(AutocompleteResultViewModel* model,
272                         int model_index,
273                         const gfx::Font& font,
274                         const gfx::Font& bold_font);
275  virtual ~AutocompleteResultView();
276
277  // Updates the match used to paint the contents of this result view. We copy
278  // the match so that we can continue to paint the last result even after the
279  // model has changed.
280  void set_match(const AutocompleteMatch& match) { match_ = match; }
281
282  // Overridden from views::View:
283  virtual void Paint(gfx::Canvas* canvas);
284  virtual void Layout();
285  virtual gfx::Size GetPreferredSize();
286
287  // Returns the preferred height for a single row.
288  static int GetPreferredHeight(const gfx::Font& font,
289                                const gfx::Font& bold_font);
290
291 private:
292  // Precalculated data used to draw the portion of a match classification that
293  // fits entirely within one run.
294  struct ClassificationData {
295    std::wstring text;
296    const gfx::Font* font;
297    SkColor color;
298    int pixel_width;
299  };
300  typedef std::vector<ClassificationData> Classifications;
301
302  // Precalculated data used to draw a complete visual run within the match.
303  // This will include all or part of at leasdt one, and possibly several,
304  // classifications.
305  struct RunData {
306    size_t run_start;  // Offset within the match text where this run begins.
307    int visual_order;  // Where this run occurs in visual order.  The earliest
308                       // run drawn is run 0.
309    bool is_rtl;
310    int pixel_width;
311    Classifications classifications;  // Classification pieces within this run,
312                                      // in logical order.
313  };
314  typedef std::vector<RunData> Runs;
315
316  // Predicate functions for use when sorting the runs.
317  static bool SortRunsLogically(const RunData& lhs, const RunData& rhs);
318  static bool SortRunsVisually(const RunData& lhs, const RunData& rhs);
319
320  ResultViewState GetState() const;
321
322  const SkBitmap* GetIcon() const;
323
324  // Draws the specified |text| into the canvas, using highlighting provided by
325  // |classifications|. If |force_dim| is true, ACMatchClassification::DIM is
326  // added to all of the classifications. Returns the x position to the right
327  // of the string.
328  int DrawString(gfx::Canvas* canvas,
329                 const std::wstring& text,
330                 const ACMatchClassifications& classifications,
331                 bool force_dim,
332                 int x,
333                 int y);
334
335  // Elides |runs| to fit in |remaining_width|.  The runs in |runs| should be in
336  // logical order.
337  //
338  // When we need to elide a run, the ellipsis will be placed at the end of that
339  // run.  This means that if we elide a run whose visual direction is opposite
340  // that of the drawing context, the ellipsis will not be at the "end" of the
341  // drawn string.  For example, if in an LTR context we have the LTR run
342  // "LTR_STRING" and the RTL run "RTL_STRING", the unelided text would be drawn
343  // like:
344  //     LTR_STRING GNIRTS_LTR
345  // If we need to elide the RTL run, then it will be drawn like:
346  //     LTR_STRING ...RTS_LTR
347  // Instead of:
348  //     LTR_STRING RTS_LTR...
349  void Elide(Runs* runs, int remaining_width) const;
350
351  // This row's model and model index.
352  AutocompleteResultViewModel* model_;
353  size_t model_index_;
354
355  const gfx::Font normal_font_;
356  const gfx::Font bold_font_;
357
358  // Width of the ellipsis in the normal font.
359  int ellipsis_width_;
360
361  // A context used for mirroring regions.
362  class MirroringContext;
363  scoped_ptr<MirroringContext> mirroring_context_;
364
365  // Layout rects for various sub-components of the view.
366  gfx::Rect icon_bounds_;
367  gfx::Rect text_bounds_;
368
369  static int icon_size_;
370
371  AutocompleteMatch match_;
372
373  DISALLOW_COPY_AND_ASSIGN(AutocompleteResultView);
374};
375
376// static
377int AutocompleteResultView::icon_size_ = 0;
378
379// This class is a utility class for calculations affected by whether the result
380// view is horizontally mirrored.  The drawing functions can be written as if
381// all drawing occurs left-to-right, and then use this class to get the actual
382// coordinates to begin drawing onscreen.
383class AutocompleteResultView::MirroringContext {
384 public:
385  MirroringContext() : center_(0), right_(0) {}
386
387  // Tells the mirroring context to use the provided range as the physical
388  // bounds of the drawing region.  When coordinate mirroring is needed, the
389  // mirror point will be the center of this range.
390  void Initialize(int x, int width) {
391    center_ = x + width / 2;
392    right_ = x + width;
393  }
394
395  // Given a logical range within the drawing region, returns the coordinate of
396  // the possibly-mirrored "left" side.  (This functions exactly like
397  // View::MirroredLeftPointForRect().)
398  int mirrored_left_coord(int left, int right) const {
399    return base::i18n::IsRTL() ? (center_ + (center_ - right)) : left;
400  }
401
402  // Given a logical coordinate within the drawing region, returns the remaining
403  // width available.
404  int remaining_width(int x) const {
405    return right_ - x;
406  }
407
408 private:
409  int center_;
410  int right_;
411
412  DISALLOW_COPY_AND_ASSIGN(MirroringContext);
413};
414
415AutocompleteResultView::AutocompleteResultView(
416    AutocompleteResultViewModel* model,
417    int model_index,
418    const gfx::Font& font,
419    const gfx::Font& bold_font)
420    : model_(model),
421      model_index_(model_index),
422      normal_font_(font),
423      bold_font_(bold_font),
424      ellipsis_width_(font.GetStringWidth(kEllipsis)),
425      mirroring_context_(new MirroringContext()),
426      match_(NULL, 0, false, AutocompleteMatch::URL_WHAT_YOU_TYPED) {
427  CHECK(model_index >= 0);
428  if (icon_size_ == 0) {
429    icon_size_ = ResourceBundle::GetSharedInstance().GetBitmapNamed(
430        AutocompleteMatch::TypeToIcon(AutocompleteMatch::URL_WHAT_YOU_TYPED))->
431        width();
432  }
433}
434
435AutocompleteResultView::~AutocompleteResultView() {
436}
437
438void AutocompleteResultView::Paint(gfx::Canvas* canvas) {
439  const ResultViewState state = GetState();
440  if (state != NORMAL)
441    canvas->AsCanvasSkia()->drawColor(GetColor(state, BACKGROUND));
442
443  // Paint the icon.
444  canvas->DrawBitmapInt(*GetIcon(), MirroredLeftPointForRect(icon_bounds_),
445                        icon_bounds_.y());
446
447  // Paint the text.
448  int x = MirroredLeftPointForRect(text_bounds_);
449  mirroring_context_->Initialize(x, text_bounds_.width());
450  x = DrawString(canvas, match_.contents, match_.contents_class, false, x,
451                 text_bounds_.y());
452
453  // Paint the description.
454  // TODO(pkasting): Because we paint in multiple separate pieces, we can wind
455  // up with no space even for an ellipsis for one or both of these pieces.
456  // Instead, we should paint the entire match as a single long string.  This
457  // would also let us use a more properly-localizable string than we get with
458  // just the IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR.
459  if (!match_.description.empty()) {
460    std::wstring separator =
461        l10n_util::GetString(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR);
462    ACMatchClassifications classifications;
463    classifications.push_back(
464        ACMatchClassification(0, ACMatchClassification::NONE));
465    x = DrawString(canvas, separator, classifications, true, x,
466                   text_bounds_.y());
467
468    DrawString(canvas, match_.description, match_.description_class, true, x,
469               text_bounds_.y());
470  }
471}
472
473void AutocompleteResultView::Layout() {
474  icon_bounds_.SetRect(LocationBarView::kEdgeItemPadding,
475                       (height() - icon_size_) / 2, icon_size_, icon_size_);
476  int text_x = icon_bounds_.right() + LocationBarView::kItemPadding;
477  int font_height = std::max(normal_font_.GetHeight(), bold_font_.GetHeight());
478  text_bounds_.SetRect(text_x, std::max(0, (height() - font_height) / 2),
479      std::max(bounds().width() - text_x - LocationBarView::kEdgeItemPadding,
480      0), font_height);
481}
482
483gfx::Size AutocompleteResultView::GetPreferredSize() {
484  return gfx::Size(0, GetPreferredHeight(normal_font_, bold_font_));
485}
486
487// static
488int AutocompleteResultView::GetPreferredHeight(
489    const gfx::Font& font,
490    const gfx::Font& bold_font) {
491  int text_height = std::max(font.GetHeight(), bold_font.GetHeight()) +
492      (kTextVerticalPadding * 2);
493  int icon_height = icon_size_ + (kIconVerticalPadding * 2);
494  return std::max(icon_height, text_height);
495}
496
497// static
498bool AutocompleteResultView::SortRunsLogically(const RunData& lhs,
499                                               const RunData& rhs) {
500  return lhs.run_start < rhs.run_start;
501}
502
503// static
504bool AutocompleteResultView::SortRunsVisually(const RunData& lhs,
505                                              const RunData& rhs) {
506  return lhs.visual_order < rhs.visual_order;
507}
508
509ResultViewState AutocompleteResultView::GetState() const {
510  if (model_->IsSelectedIndex(model_index_))
511    return SELECTED;
512  return model_->IsHoveredIndex(model_index_) ? HOVERED : NORMAL;
513}
514
515const SkBitmap* AutocompleteResultView::GetIcon() const {
516  const SkBitmap* bitmap = model_->GetSpecialIcon(model_index_);
517  if (bitmap)
518    return bitmap;
519
520  int icon = match_.starred ?
521      IDR_OMNIBOX_STAR : AutocompleteMatch::TypeToIcon(match_.type);
522  if (model_->IsSelectedIndex(model_index_)) {
523    switch (icon) {
524      case IDR_OMNIBOX_HTTP:    icon = IDR_OMNIBOX_HTTP_SELECTED; break;
525      case IDR_OMNIBOX_HISTORY: icon = IDR_OMNIBOX_HISTORY_SELECTED; break;
526      case IDR_OMNIBOX_SEARCH:  icon = IDR_OMNIBOX_SEARCH_SELECTED; break;
527      case IDR_OMNIBOX_MORE:    icon = IDR_OMNIBOX_MORE_SELECTED; break;
528      case IDR_OMNIBOX_STAR:    icon = IDR_OMNIBOX_STAR_SELECTED; break;
529      default:             NOTREACHED(); break;
530    }
531  }
532  return ResourceBundle::GetSharedInstance().GetBitmapNamed(icon);
533}
534
535int AutocompleteResultView::DrawString(
536    gfx::Canvas* canvas,
537    const std::wstring& text,
538    const ACMatchClassifications& classifications,
539    bool force_dim,
540    int x,
541    int y) {
542  if (text.empty())
543    return x;
544
545  // Check whether or not this text is a URL.  URLs are always displayed LTR
546  // regardless of locale.
547  bool is_url = true;
548  for (ACMatchClassifications::const_iterator i(classifications.begin());
549       i != classifications.end(); ++i) {
550    if (!(i->style & ACMatchClassification::URL)) {
551      is_url = false;
552      break;
553    }
554  }
555
556  // Split the text into visual runs.  We do this first so that we don't need to
557  // worry about whether our eliding might change the visual display in
558  // unintended ways, e.g. by removing directional markings or by adding an
559  // ellipsis that's not enclosed in appropriate markings.
560  BiDiLineIterator bidi_line;
561  if (!bidi_line.Open(text, base::i18n::IsRTL(), is_url))
562    return x;
563  const int num_runs = bidi_line.CountRuns();
564  Runs runs;
565  for (int run = 0; run < num_runs; ++run) {
566    int run_start_int = 0, run_length_int = 0;
567    // The index we pass to GetVisualRun corresponds to the position of the run
568    // in the displayed text. For example, the string "Google in HEBREW" (where
569    // HEBREW is text in the Hebrew language) has two runs: "Google in " which
570    // is an LTR run, and "HEBREW" which is an RTL run. In an LTR context, the
571    // run "Google in " has the index 0 (since it is the leftmost run
572    // displayed). In an RTL context, the same run has the index 1 because it
573    // is the rightmost run. This is why the order in which we traverse the
574    // runs is different depending on the locale direction.
575    const UBiDiDirection run_direction = bidi_line.GetVisualRun(
576        (base::i18n::IsRTL() && !is_url) ? (num_runs - run - 1) : run,
577        &run_start_int, &run_length_int);
578    DCHECK_GT(run_length_int, 0);
579    runs.push_back(RunData());
580    RunData* current_run = &runs.back();
581    current_run->run_start = run_start_int;
582    const size_t run_end = current_run->run_start + run_length_int;
583    current_run->visual_order = run;
584    current_run->is_rtl = !is_url && (run_direction == UBIDI_RTL);
585    current_run->pixel_width = 0;
586
587    // Compute classifications for this run.
588    for (size_t i = 0; i < classifications.size(); ++i) {
589      const size_t text_start =
590          std::max(classifications[i].offset, current_run->run_start);
591      if (text_start >= run_end)
592        break;  // We're past the last classification in the run.
593
594      const size_t text_end = (i < (classifications.size() - 1)) ?
595          std::min(classifications[i + 1].offset, run_end) : run_end;
596      if (text_end <= current_run->run_start)
597        continue;  // We haven't reached the first classification in the run.
598
599      current_run->classifications.push_back(ClassificationData());
600      ClassificationData* current_data =
601          &current_run->classifications.back();
602      current_data->text = text.substr(text_start, text_end - text_start);
603
604      // Calculate style-related data.
605      const int style = classifications[i].style;
606      const bool use_bold_font = !!(style & ACMatchClassification::MATCH);
607      current_data->font = &(use_bold_font ? bold_font_ : normal_font_);
608      const ResultViewState state = GetState();
609      if (style & ACMatchClassification::URL)
610        current_data->color = GetColor(state, URL);
611      else if (style & ACMatchClassification::DIM)
612        current_data->color = GetColor(state, DIMMED_TEXT);
613      else
614        current_data->color = GetColor(state, force_dim ? DIMMED_TEXT : TEXT);
615      current_data->pixel_width =
616          current_data->font->GetStringWidth(current_data->text);
617      current_run->pixel_width += current_data->pixel_width;
618    }
619    DCHECK(!current_run->classifications.empty());
620  }
621  DCHECK(!runs.empty());
622
623  // Sort into logical order so we can elide logically.
624  std::sort(runs.begin(), runs.end(), &SortRunsLogically);
625
626  // Now determine what to elide, if anything.  Several subtle points:
627  //   * Because we have the run data, we can get edge cases correct, like
628  //     whether to place an ellipsis before or after the end of a run when the
629  //     text needs to be elided at the run boundary.
630  //   * The "or one before it" comments below refer to cases where an earlier
631  //     classification fits completely, but leaves too little space for an
632  //     ellipsis that turns out to be needed later.  These cases are commented
633  //     more completely in Elide().
634  int remaining_width = mirroring_context_->remaining_width(x);
635  for (Runs::iterator i(runs.begin()); i != runs.end(); ++i) {
636    if (i->pixel_width > remaining_width) {
637      // This run or one before it needs to be elided.
638      for (Classifications::iterator j(i->classifications.begin());
639           j != i->classifications.end(); ++j) {
640        if (j->pixel_width > remaining_width) {
641          // This classification or one before it needs to be elided.  Erase all
642          // further classifications and runs so Elide() can simply reverse-
643          // iterate over everything to find the specific classification to
644          // elide.
645          i->classifications.erase(++j, i->classifications.end());
646          runs.erase(++i, runs.end());
647          Elide(&runs, remaining_width);
648          break;
649        }
650        remaining_width -= j->pixel_width;
651      }
652      break;
653    }
654    remaining_width -= i->pixel_width;
655  }
656
657  // Sort back into visual order so we can display the runs correctly.
658  std::sort(runs.begin(), runs.end(), &SortRunsVisually);
659
660  // Draw the runs.
661  for (Runs::iterator i(runs.begin()); i != runs.end(); ++i) {
662    const bool reverse_visible_order = (i->is_rtl != base::i18n::IsRTL());
663    int flags = gfx::Canvas::NO_ELLIPSIS;  // We've already elided.
664    if (reverse_visible_order) {
665      std::reverse(i->classifications.begin(), i->classifications.end());
666      if (i->is_rtl)
667        flags |= gfx::Canvas::FORCE_RTL_DIRECTIONALITY;
668    }
669    for (Classifications::const_iterator j(i->classifications.begin());
670         j != i->classifications.end(); ++j) {
671      int left = mirroring_context_->mirrored_left_coord(x, x + j->pixel_width);
672      canvas->DrawStringInt(j->text, *j->font, j->color, left, y,
673                            j->pixel_width, j->font->GetHeight(), flags);
674      x += j->pixel_width;
675    }
676  }
677
678  return x;
679}
680
681void AutocompleteResultView::Elide(Runs* runs, int remaining_width) const {
682  // The complexity of this function is due to edge cases like the following:
683  // We have 100 px of available space, an initial classification that takes 86
684  // px, and a font that has a 15 px wide ellipsis character.  Now if the first
685  // classification is followed by several very narrow classifications (e.g. 3
686  // px wide each), we don't know whether we need to elide or not at the time we
687  // see the first classification -- it depends on how many subsequent
688  // classifications follow, and some of those may be in the next run (or
689  // several runs!).  This is why instead we let our caller move forward until
690  // we know we definitely need to elide, and then in this function we move
691  // backward again until we find a string that we can successfully do the
692  // eliding on.
693  bool first_classification = true;
694  for (Runs::reverse_iterator i(runs->rbegin()); i != runs->rend(); ++i) {
695    for (Classifications::reverse_iterator j(i->classifications.rbegin());
696         j != i->classifications.rend(); ++j) {
697      if (!first_classification) {
698        // For all but the first classification we consider, we need to append
699        // an ellipsis, since there isn't enough room to draw it after this
700        // classification.
701        j->text += kEllipsis;
702
703        // We also add this classification's width (sans ellipsis) back to the
704        // available width since we want to consider the available space we'll
705        // have when we draw this classification.
706        remaining_width += j->pixel_width;
707      }
708      first_classification = false;
709
710      // Can we fit at least an ellipsis?
711      std::wstring elided_text(UTF16ToWideHack(
712          gfx::ElideText(WideToUTF16Hack(j->text), *j->font, remaining_width,
713                         false)));
714      Classifications::reverse_iterator prior_classification(j);
715      ++prior_classification;
716      const bool on_first_classification =
717        (prior_classification == i->classifications.rend());
718      if (elided_text.empty() && (remaining_width >= ellipsis_width_) &&
719          on_first_classification) {
720        // Edge case: This classification is bold, we can't fit a bold ellipsis
721        // but we can fit a normal one, and this is the first classification in
722        // the run.  We should display a lone normal ellipsis, because appending
723        // one to the end of the previous run might put it in the wrong visual
724        // location (if the previous run is reversed from the normal visual
725        // order).
726        // NOTE: If this isn't the first classification in the run, we don't
727        // need to bother with this; see note below.
728        elided_text = kEllipsis;
729      }
730      if (!elided_text.empty()) {
731        // Success.  Elide this classification and stop.
732        j->text = elided_text;
733
734        // If we could only fit an ellipsis, then only make it bold if there was
735        // an immediate prior classification in this run that was also bold, or
736        // it will look orphaned.
737        if ((elided_text.length() == 1) &&
738            (on_first_classification ||
739             (prior_classification->font == &normal_font_)))
740          j->font = &normal_font_;
741
742        j->pixel_width = j->font->GetStringWidth(elided_text);
743
744        // Erase any other classifications that come after the elided one.
745        i->classifications.erase(j.base(), i->classifications.end());
746        runs->erase(i.base(), runs->end());
747        return;
748      }
749
750      // We couldn't fit an ellipsis.  Move back one classification,
751      // append an ellipsis, and try again.
752      // NOTE: In the edge case that a bold ellipsis doesn't fit but a
753      // normal one would, and we reach here, then there is a previous
754      // classification in this run, and so either:
755      //   * It's normal, and will be able to draw successfully with the
756      //     ellipsis we'll append to it, or
757      //   * It is also bold, in which case we don't want to fall back
758      //     to a normal ellipsis anyway (see comment above).
759    }
760  }
761
762  // We couldn't draw anything.
763  runs->clear();
764}
765
766////////////////////////////////////////////////////////////////////////////////
767// AutocompletePopupContentsView, public:
768
769AutocompletePopupContentsView::AutocompletePopupContentsView(
770    const gfx::Font& font,
771    AutocompleteEditView* edit_view,
772    AutocompleteEditModel* edit_model,
773    Profile* profile,
774    const views::View* location_bar)
775    : model_(new AutocompletePopupModel(this, edit_model, profile)),
776      edit_view_(edit_view),
777      location_bar_(location_bar),
778      result_font_(font.DeriveFont(kEditFontAdjust)),
779      result_bold_font_(result_font_.DeriveFont(0, gfx::Font::BOLD)),
780      ignore_mouse_drag_(false),
781      ALLOW_THIS_IN_INITIALIZER_LIST(size_animation_(this)),
782      opt_in_view_(NULL) {
783  // The following little dance is required because set_border() requires a
784  // pointer to a non-const object.
785  BubbleBorder* bubble_border = new BubbleBorder(BubbleBorder::NONE);
786  bubble_border_ = bubble_border;
787  set_border(bubble_border);
788}
789
790AutocompletePopupContentsView::~AutocompletePopupContentsView() {
791  // We don't need to do anything with |popup_| here.  The OS either has already
792  // closed the window, in which case it's been deleted, or it will soon, in
793  // which case there's nothing we need to do.
794}
795
796gfx::Rect AutocompletePopupContentsView::GetPopupBounds() const {
797  if (!size_animation_.is_animating())
798    return target_bounds_;
799
800  gfx::Rect current_frame_bounds = start_bounds_;
801  int total_height_delta = target_bounds_.height() - start_bounds_.height();
802  // Round |current_height_delta| instead of truncating so we won't leave single
803  // white pixels at the bottom of the popup as long when animating very small
804  // height differences.
805  int current_height_delta = static_cast<int>(
806      size_animation_.GetCurrentValue() * total_height_delta - 0.5);
807  current_frame_bounds.set_height(
808      current_frame_bounds.height() + current_height_delta);
809  return current_frame_bounds;
810}
811
812////////////////////////////////////////////////////////////////////////////////
813// AutocompletePopupContentsView, AutocompletePopupView overrides:
814
815bool AutocompletePopupContentsView::IsOpen() const {
816  return (popup_ != NULL);
817}
818
819void AutocompletePopupContentsView::InvalidateLine(size_t line) {
820  GetChildViewAt(static_cast<int>(line))->SchedulePaint();
821}
822
823void AutocompletePopupContentsView::UpdatePopupAppearance() {
824  if (model_->result().empty()) {
825    // No matches, close any existing popup.
826    if (popup_ != NULL) {
827      size_animation_.Stop();
828      // NOTE: Do NOT use CloseNow() here, as we may be deep in a callstack
829      // triggered by the popup receiving a message (e.g. LBUTTONUP), and
830      // destroying the popup would cause us to read garbage when we unwind back
831      // to that level.
832      popup_->Close();  // This will eventually delete the popup.
833      popup_.reset();
834    }
835    return;
836  }
837
838  // Update the match cached by each row, in the process of doing so make sure
839  // we have enough row views.
840  int total_child_height = 0;
841  size_t child_rv_count = GetChildViewCount();
842  if (opt_in_view_) {
843    DCHECK(child_rv_count > 0);
844    child_rv_count--;
845  }
846  for (size_t i = 0; i < model_->result().size(); ++i) {
847    AutocompleteResultView* result_view;
848    if (i >= child_rv_count) {
849      result_view =
850          new AutocompleteResultView(this, i, result_font_, result_bold_font_);
851      AddChildView(static_cast<int>(i), result_view);
852    } else {
853      result_view = static_cast<AutocompleteResultView*>(GetChildViewAt(i));
854      result_view->SetVisible(true);
855    }
856    result_view->set_match(GetMatchAtIndex(i));
857    total_child_height += result_view->GetPreferredSize().height();
858  }
859  for (size_t i = model_->result().size(); i < child_rv_count; ++i)
860    GetChildViewAt(i)->SetVisible(false);
861
862  PromoCounter* counter = model_->profile()->GetInstantPromoCounter();
863  if (!opt_in_view_ && counter && counter->ShouldShow(base::Time::Now())) {
864    opt_in_view_ = new InstantOptInView(this, result_bold_font_, result_font_);
865    AddChildView(opt_in_view_);
866  } else if (opt_in_view_ && (!counter ||
867                              !counter->ShouldShow(base::Time::Now()))) {
868    delete opt_in_view_;
869    opt_in_view_ = NULL;
870  }
871
872  if (opt_in_view_)
873    total_child_height += opt_in_view_->GetPreferredSize().height();
874
875  gfx::Rect new_target_bounds = CalculateTargetBounds(total_child_height);
876
877  // If we're animating and our target height changes, reset the animation.
878  // NOTE: If we just reset blindly on _every_ update, then when the user types
879  // rapidly we could get "stuck" trying repeatedly to animate shrinking by the
880  // last few pixels to get to one visible result.
881  if (new_target_bounds.height() != target_bounds_.height())
882    size_animation_.Reset();
883  target_bounds_ = new_target_bounds;
884
885  if (popup_ == NULL) {
886    // If the popup is currently closed, we need to create it.
887    popup_ = (new AutocompletePopupClass(edit_view_, this))->AsWeakPtr();
888  } else {
889    // Animate the popup shrinking, but don't animate growing larger since that
890    // would make the popup feel less responsive.
891    GetWidget()->GetBounds(&start_bounds_, true);
892    if (target_bounds_.height() < start_bounds_.height())
893      size_animation_.Show();
894    else
895      start_bounds_ = target_bounds_;
896    popup_->SetBounds(GetPopupBounds());
897  }
898
899  SchedulePaint();
900}
901
902gfx::Rect AutocompletePopupContentsView::GetTargetBounds() {
903  return target_bounds_;
904}
905
906void AutocompletePopupContentsView::PaintUpdatesNow() {
907  // TODO(beng): remove this from the interface.
908}
909
910void AutocompletePopupContentsView::OnDragCanceled() {
911  ignore_mouse_drag_ = true;
912}
913
914AutocompletePopupModel* AutocompletePopupContentsView::GetModel() {
915  return model_.get();
916}
917
918////////////////////////////////////////////////////////////////////////////////
919// AutocompletePopupContentsView, AutocompleteResultViewModel implementation:
920
921bool AutocompletePopupContentsView::IsSelectedIndex(size_t index) const {
922  return HasMatchAt(index) ? index == model_->selected_line() : false;
923}
924
925bool AutocompletePopupContentsView::IsHoveredIndex(size_t index) const {
926  return HasMatchAt(index) ? index == model_->hovered_line() : false;
927}
928
929const SkBitmap* AutocompletePopupContentsView::GetSpecialIcon(
930    size_t index) const {
931  if (!HasMatchAt(index))
932    return NULL;
933  return model_->GetSpecialIconForMatch(GetMatchAtIndex(index));
934}
935
936////////////////////////////////////////////////////////////////////////////////
937// AutocompletePopupContentsView, AnimationDelegate implementation:
938
939void AutocompletePopupContentsView::AnimationProgressed(
940    const Animation* animation) {
941  // We should only be running the animation when the popup is already visible.
942  DCHECK(popup_ != NULL);
943  popup_->SetBounds(GetPopupBounds());
944}
945
946////////////////////////////////////////////////////////////////////////////////
947// AutocompletePopupContentsView, views::View overrides:
948
949void AutocompletePopupContentsView::Paint(gfx::Canvas* canvas) {
950  // We paint our children in an unconventional way.
951  //
952  // Because the border of this view creates an anti-aliased round-rect region
953  // for the contents, we need to render our rectangular result child views into
954  // this round rect region. We can't use a simple clip because clipping is
955  // 1-bit and we get nasty jagged edges.
956  //
957  // Instead, we paint all our children into a second canvas and use that as a
958  // shader to fill a path representing the round-rect clipping region. This
959  // yields a nice anti-aliased edge.
960  gfx::CanvasSkia contents_canvas(width(), height(), true);
961  contents_canvas.drawColor(GetColor(NORMAL, BACKGROUND));
962  View::PaintChildren(&contents_canvas);
963  // We want the contents background to be slightly transparent so we can see
964  // the blurry glass effect on DWM systems behind. We do this _after_ we paint
965  // the children since they paint text, and GDI will reset this alpha data if
966  // we paint text after this call.
967  MakeCanvasTransparent(&contents_canvas);
968
969  // Now paint the contents of the contents canvas into the actual canvas.
970  SkPaint paint;
971  paint.setAntiAlias(true);
972
973  SkShader* shader = SkShader::CreateBitmapShader(
974      contents_canvas.getDevice()->accessBitmap(false),
975      SkShader::kClamp_TileMode,
976      SkShader::kClamp_TileMode);
977  paint.setShader(shader);
978  shader->unref();
979
980  gfx::Path path;
981  MakeContentsPath(&path, GetLocalBounds(false));
982  canvas->AsCanvasSkia()->drawPath(path, paint);
983
984  // Now we paint the border, so it will be alpha-blended atop the contents.
985  // This looks slightly better in the corners than drawing the contents atop
986  // the border.
987  PaintBorder(canvas);
988}
989
990void AutocompletePopupContentsView::Layout() {
991  UpdateBlurRegion();
992
993  // Size our children to the available content area.
994  gfx::Rect contents_rect = GetLocalBounds(false);
995  int child_count = GetChildViewCount();
996  int top = contents_rect.y();
997  for (int i = 0; i < child_count; ++i) {
998    View* v = GetChildViewAt(i);
999    if (v->IsVisible()) {
1000      v->SetBounds(contents_rect.x(), top, contents_rect.width(),
1001                   v->GetPreferredSize().height());
1002      top = v->bounds().bottom();
1003    }
1004  }
1005
1006  // We need to manually schedule a paint here since we are a layered window and
1007  // won't implicitly require painting until we ask for one.
1008  SchedulePaint();
1009}
1010
1011
1012void AutocompletePopupContentsView::OnMouseEntered(
1013    const views::MouseEvent& event) {
1014  model_->SetHoveredLine(GetIndexForPoint(event.location()));
1015}
1016
1017void AutocompletePopupContentsView::OnMouseMoved(
1018    const views::MouseEvent& event) {
1019  model_->SetHoveredLine(GetIndexForPoint(event.location()));
1020}
1021
1022void AutocompletePopupContentsView::OnMouseExited(
1023    const views::MouseEvent& event) {
1024  model_->SetHoveredLine(AutocompletePopupModel::kNoMatch);
1025}
1026
1027bool AutocompletePopupContentsView::OnMousePressed(
1028    const views::MouseEvent& event) {
1029  ignore_mouse_drag_ = false;  // See comment on |ignore_mouse_drag_| in header.
1030  if (event.IsLeftMouseButton() || event.IsMiddleMouseButton()) {
1031    size_t index = GetIndexForPoint(event.location());
1032    model_->SetHoveredLine(index);
1033    if (HasMatchAt(index) && event.IsLeftMouseButton())
1034      model_->SetSelectedLine(index, false);
1035  }
1036  return true;
1037}
1038
1039void AutocompletePopupContentsView::OnMouseReleased(
1040    const views::MouseEvent& event,
1041    bool canceled) {
1042  if (canceled || ignore_mouse_drag_) {
1043    ignore_mouse_drag_ = false;
1044    return;
1045  }
1046
1047  size_t index = GetIndexForPoint(event.location());
1048  if (event.IsOnlyMiddleMouseButton())
1049    OpenIndex(index, NEW_BACKGROUND_TAB);
1050  else if (event.IsOnlyLeftMouseButton())
1051    OpenIndex(index, CURRENT_TAB);
1052}
1053
1054bool AutocompletePopupContentsView::OnMouseDragged(
1055    const views::MouseEvent& event) {
1056  if (event.IsLeftMouseButton() || event.IsMiddleMouseButton()) {
1057    size_t index = GetIndexForPoint(event.location());
1058    model_->SetHoveredLine(index);
1059    if (!ignore_mouse_drag_ && HasMatchAt(index) && event.IsLeftMouseButton())
1060      model_->SetSelectedLine(index, false);
1061  }
1062  return true;
1063}
1064
1065views::View* AutocompletePopupContentsView::GetViewForPoint(
1066    const gfx::Point& point) {
1067  // If there is no opt in view, then we want all mouse events. Otherwise let
1068  // any descendants of the opt-in view get mouse events.
1069  if (!opt_in_view_)
1070    return this;
1071
1072  views::View* child = views::View::GetViewForPoint(point);
1073  views::View* ancestor = child;
1074  while (ancestor && ancestor != opt_in_view_)
1075    ancestor = ancestor->GetParent();
1076  return ancestor ? child : this;
1077}
1078
1079
1080////////////////////////////////////////////////////////////////////////////////
1081// AutocompletePopupContentsView, private:
1082
1083bool AutocompletePopupContentsView::HasMatchAt(size_t index) const {
1084  return index < model_->result().size();
1085}
1086
1087const AutocompleteMatch& AutocompletePopupContentsView::GetMatchAtIndex(
1088    size_t index) const {
1089  return model_->result().match_at(index);
1090}
1091
1092void AutocompletePopupContentsView::MakeContentsPath(
1093    gfx::Path* path,
1094    const gfx::Rect& bounding_rect) {
1095  SkRect rect;
1096  rect.set(SkIntToScalar(bounding_rect.x()),
1097           SkIntToScalar(bounding_rect.y()),
1098           SkIntToScalar(bounding_rect.right()),
1099           SkIntToScalar(bounding_rect.bottom()));
1100
1101  SkScalar radius = SkIntToScalar(BubbleBorder::GetCornerRadius());
1102  path->addRoundRect(rect, radius, radius);
1103}
1104
1105void AutocompletePopupContentsView::UpdateBlurRegion() {
1106#if defined(OS_WIN)
1107  // We only support background blurring on Vista with Aero-Glass enabled.
1108  if (!win_util::ShouldUseVistaFrame() || !GetWidget())
1109    return;
1110
1111  // Provide a blurred background effect within the contents region of the
1112  // popup.
1113  DWM_BLURBEHIND bb = {0};
1114  bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
1115  bb.fEnable = true;
1116
1117  // Translate the contents rect into widget coordinates, since that's what
1118  // DwmEnableBlurBehindWindow expects a region in.
1119  gfx::Rect contents_rect = GetLocalBounds(false);
1120  gfx::Point origin(contents_rect.origin());
1121  views::View::ConvertPointToWidget(this, &origin);
1122  contents_rect.set_origin(origin);
1123
1124  gfx::Path contents_path;
1125  MakeContentsPath(&contents_path, contents_rect);
1126  ScopedGDIObject<HRGN> popup_region;
1127  popup_region.Set(contents_path.CreateNativeRegion());
1128  bb.hRgnBlur = popup_region.Get();
1129  DwmEnableBlurBehindWindow(GetWidget()->GetNativeView(), &bb);
1130#endif
1131}
1132
1133void AutocompletePopupContentsView::MakeCanvasTransparent(
1134    gfx::Canvas* canvas) {
1135  // Allow the window blur effect to show through the popup background.
1136  SkAlpha alpha = GetThemeProvider()->ShouldUseNativeFrame() ?
1137      kGlassPopupAlpha : kOpaquePopupAlpha;
1138  canvas->AsCanvasSkia()->drawColor(
1139      SkColorSetA(GetColor(NORMAL, BACKGROUND), alpha),
1140      SkXfermode::kDstIn_Mode);
1141}
1142
1143void AutocompletePopupContentsView::OpenIndex(
1144    size_t index,
1145    WindowOpenDisposition disposition) {
1146  if (!HasMatchAt(index))
1147    return;
1148
1149  const AutocompleteMatch& match = model_->result().match_at(index);
1150  // OpenURL() may close the popup, which will clear the result set and, by
1151  // extension, |match| and its contents.  So copy the relevant strings out to
1152  // make sure they stay alive until the call completes.
1153  const GURL url(match.destination_url);
1154  std::wstring keyword;
1155  const bool is_keyword_hint = model_->GetKeywordForMatch(match, &keyword);
1156  edit_view_->OpenURL(url, disposition, match.transition, GURL(), index,
1157                      is_keyword_hint ? std::wstring() : keyword);
1158}
1159
1160size_t AutocompletePopupContentsView::GetIndexForPoint(
1161    const gfx::Point& point) {
1162  if (!HitTest(point))
1163    return AutocompletePopupModel::kNoMatch;
1164
1165  int nb_match = model_->result().size();
1166  DCHECK(nb_match <= GetChildViewCount());
1167  for (int i = 0; i < nb_match; ++i) {
1168    views::View* child = GetChildViewAt(i);
1169    gfx::Point point_in_child_coords(point);
1170    View::ConvertPointToView(this, child, &point_in_child_coords);
1171    if (child->HitTest(point_in_child_coords))
1172      return i;
1173  }
1174  return AutocompletePopupModel::kNoMatch;
1175}
1176
1177gfx::Rect AutocompletePopupContentsView::CalculateTargetBounds(int h) {
1178  gfx::Rect location_bar_bounds(gfx::Point(), location_bar_->size());
1179  const views::Border* border = location_bar_->border();
1180  if (border) {
1181    // Adjust for the border so that the bubble and location bar borders are
1182    // aligned.
1183    gfx::Insets insets;
1184    border->GetInsets(&insets);
1185    location_bar_bounds.Inset(insets.left(), 0, insets.right(), 0);
1186  } else {
1187    // The normal location bar is drawn using a background graphic that includes
1188    // the border, so we inset by enough to make the edges line up, and the
1189    // bubble appear at the same height as the Star bubble.
1190    location_bar_bounds.Inset(LocationBarView::kNormalHorizontalEdgeThickness,
1191                              0);
1192  }
1193  gfx::Point location_bar_origin(location_bar_bounds.origin());
1194  views::View::ConvertPointToScreen(location_bar_, &location_bar_origin);
1195  location_bar_bounds.set_origin(location_bar_origin);
1196  return bubble_border_->GetBounds(
1197      location_bar_bounds, gfx::Size(location_bar_bounds.width(), h));
1198}
1199
1200void AutocompletePopupContentsView::UserPressedOptIn(bool opt_in) {
1201  delete opt_in_view_;
1202  opt_in_view_ = NULL;
1203  PromoCounter* counter = model_->profile()->GetInstantPromoCounter();
1204  DCHECK(counter);
1205  counter->Hide();
1206  if (opt_in) {
1207    browser::ShowInstantConfirmDialogIfNecessary(
1208        location_bar_->GetWindow()->GetNativeWindow(), model_->profile());
1209  }
1210  UpdatePopupAppearance();
1211}
1212