Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(347)

Side by Side Diff: chrome/browser/ui/views/autocomplete/autocomplete_popup_contents_view.cc

Issue 6349101: Create a new autocomplete popup for touch. This CL depends on (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: merged changes from refactoring CL Created 9 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/ui/views/autocomplete/autocomplete_popup_contents_view. h" 5 #include "chrome/browser/ui/views/autocomplete/autocomplete_popup_contents_view. h"
6 6
7 #include "base/compiler_specific.h" 7 #include "base/compiler_specific.h"
8 #include "base/i18n/bidi_line_iterator.h"
9 #include "base/i18n/rtl.h"
10 #include "base/utf_string_conversions.h" 8 #include "base/utf_string_conversions.h"
11 #include "chrome/browser/autocomplete/autocomplete_edit_view.h" 9 #include "chrome/browser/autocomplete/autocomplete_edit_view.h"
12 #include "chrome/browser/autocomplete/autocomplete_match.h"
13 #include "chrome/browser/autocomplete/autocomplete_popup_model.h" 10 #include "chrome/browser/autocomplete/autocomplete_popup_model.h"
14 #include "chrome/browser/instant/instant_confirm_dialog.h" 11 #include "chrome/browser/instant/instant_confirm_dialog.h"
15 #include "chrome/browser/instant/promo_counter.h" 12 #include "chrome/browser/instant/promo_counter.h"
16 #include "chrome/browser/profiles/profile.h" 13 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/ui/views/bubble_border.h" 14 #include "chrome/browser/ui/views/bubble_border.h"
18 #include "chrome/browser/ui/views/location_bar/location_bar_view.h" 15 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
19 #include "grit/chromium_strings.h" 16 #include "grit/chromium_strings.h"
20 #include "grit/generated_resources.h" 17 #include "grit/generated_resources.h"
21 #include "grit/theme_resources.h" 18 #include "grit/theme_resources.h"
22 #include "third_party/skia/include/core/SkShader.h" 19 #include "third_party/skia/include/core/SkShader.h"
23 #include "ui/base/l10n/l10n_util.h" 20 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/resource/resource_bundle.h" 21 #include "ui/base/resource/resource_bundle.h"
25 #include "ui/base/text/text_elider.h"
26 #include "ui/base/theme_provider.h" 22 #include "ui/base/theme_provider.h"
27 #include "ui/gfx/canvas_skia.h" 23 #include "ui/gfx/canvas_skia.h"
28 #include "ui/gfx/color_utils.h"
29 #include "ui/gfx/insets.h" 24 #include "ui/gfx/insets.h"
30 #include "ui/gfx/path.h" 25 #include "ui/gfx/path.h"
31 #include "unicode/ubidi.h" 26 #include "unicode/ubidi.h"
32 #include "views/controls/button/text_button.h" 27 #include "views/controls/button/text_button.h"
33 #include "views/controls/label.h" 28 #include "views/controls/label.h"
34 #include "views/layout/grid_layout.h" 29 #include "views/layout/grid_layout.h"
35 #include "views/layout/layout_constants.h" 30 #include "views/layout/layout_constants.h"
36 #include "views/painter.h" 31 #include "views/painter.h"
37 #include "views/widget/widget.h" 32 #include "views/widget/widget.h"
38 #include "views/window/window.h" 33 #include "views/window/window.h"
39 34
40 #if defined(OS_WIN) 35 #if defined(OS_WIN)
41 #include <commctrl.h> 36 #include <commctrl.h>
42 #include <dwmapi.h> 37 #include <dwmapi.h>
43 #include <objidl.h> 38 #include <objidl.h>
44 39
45 #include "base/win/scoped_gdi_object.h" 40 #include "base/win/scoped_gdi_object.h"
46 #include "views/widget/widget_win.h" 41 #include "views/widget/widget_win.h"
47 #endif 42 #endif
48 43
49 #if defined(OS_LINUX) 44 #if defined(OS_LINUX)
50 #include "chrome/browser/ui/gtk/gtk_util.h"
51 #include "ui/gfx/skia_utils_gtk.h" 45 #include "ui/gfx/skia_utils_gtk.h"
52 #endif 46 #endif
53 47
54 namespace { 48 namespace {
55 49
56 enum ResultViewState {
57 NORMAL = 0,
58 SELECTED,
59 HOVERED,
60 NUM_STATES
61 };
62
63 enum ColorKind {
64 BACKGROUND = 0,
65 TEXT,
66 DIMMED_TEXT,
67 URL,
68 NUM_KINDS
69 };
70
71 SkColor GetColor(ResultViewState state, ColorKind kind) {
72 static bool initialized = false;
73 static SkColor colors[NUM_STATES][NUM_KINDS];
74 if (!initialized) {
75 #if defined(OS_WIN)
76 colors[NORMAL][BACKGROUND] = color_utils::GetSysSkColor(COLOR_WINDOW);
77 colors[SELECTED][BACKGROUND] = color_utils::GetSysSkColor(COLOR_HIGHLIGHT);
78 colors[NORMAL][TEXT] = color_utils::GetSysSkColor(COLOR_WINDOWTEXT);
79 colors[SELECTED][TEXT] = color_utils::GetSysSkColor(COLOR_HIGHLIGHTTEXT);
80 #elif defined(OS_LINUX)
81 GdkColor bg_color, selected_bg_color, text_color, selected_text_color;
82 gtk_util::GetTextColors(
83 &bg_color, &selected_bg_color, &text_color, &selected_text_color);
84 colors[NORMAL][BACKGROUND] = gfx::GdkColorToSkColor(bg_color);
85 colors[SELECTED][BACKGROUND] = gfx::GdkColorToSkColor(selected_bg_color);
86 colors[NORMAL][TEXT] = gfx::GdkColorToSkColor(text_color);
87 colors[SELECTED][TEXT] = gfx::GdkColorToSkColor(selected_text_color);
88 #else
89 // TODO(beng): source from theme provider.
90 colors[NORMAL][BACKGROUND] = SK_ColorWHITE;
91 colors[SELECTED][BACKGROUND] = SK_ColorBLUE;
92 colors[NORMAL][TEXT] = SK_ColorBLACK;
93 colors[SELECTED][TEXT] = SK_ColorWHITE;
94 #endif
95 colors[HOVERED][BACKGROUND] =
96 color_utils::AlphaBlend(colors[SELECTED][BACKGROUND],
97 colors[NORMAL][BACKGROUND], 64);
98 colors[HOVERED][TEXT] = colors[NORMAL][TEXT];
99 for (int i = 0; i < NUM_STATES; ++i) {
100 colors[i][DIMMED_TEXT] =
101 color_utils::AlphaBlend(colors[i][TEXT], colors[i][BACKGROUND], 128);
102 colors[i][URL] = color_utils::GetReadableColor(SkColorSetRGB(0, 128, 0),
103 colors[i][BACKGROUND]);
104 }
105 initialized = true;
106 }
107
108 return colors[state][kind];
109 }
110
111 const char16 kEllipsis[] = { 0x2026 };
112
113 const SkAlpha kGlassPopupAlpha = 240; 50 const SkAlpha kGlassPopupAlpha = 240;
114 const SkAlpha kOpaquePopupAlpha = 255; 51 const SkAlpha kOpaquePopupAlpha = 255;
115 // The minimum distance between the top and bottom of the icon and the top or
116 // bottom of the row. "Minimum" is used because the vertical padding may be
117 // larger, depending on the size of the text.
118 const int kIconVerticalPadding = 2;
119 // The minimum distance between the top and bottom of the text and the top or
120 // bottom of the row. See comment about the use of "minimum" for
121 // kIconVerticalPadding.
122 const int kTextVerticalPadding = 3;
123 // The size delta between the font used for the edit and the result rows. Passed 52 // The size delta between the font used for the edit and the result rows. Passed
124 // to gfx::Font::DeriveFont. 53 // to gfx::Font::DeriveFont.
125 #if defined(OS_CHROMEOS) 54 #if defined(OS_CHROMEOS)
126 // Don't adjust the size on Chrome OS (http://crbug.com/61433). 55 // Don't adjust the size on Chrome OS (http://crbug.com/61433).
127 const int kEditFontAdjust = 0; 56 const int kEditFontAdjust = 0;
128 #else 57 #else
129 const int kEditFontAdjust = -1; 58 const int kEditFontAdjust = -1;
130 #endif 59 #endif
131 60
132 // Horizontal padding between the buttons on the opt in promo. 61 // Horizontal padding between the buttons on the opt in promo.
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after
260 button->set_animate_on_state_change(false); 189 button->set_animate_on_state_change(false);
261 return button; 190 return button;
262 } 191 }
263 192
264 AutocompletePopupContentsView* contents_view_; 193 AutocompletePopupContentsView* contents_view_;
265 scoped_ptr<views::Painter> bg_painter_; 194 scoped_ptr<views::Painter> bg_painter_;
266 195
267 DISALLOW_COPY_AND_ASSIGN(InstantOptInView); 196 DISALLOW_COPY_AND_ASSIGN(InstantOptInView);
268 }; 197 };
269 198
270 class AutocompleteResultView : public views::View {
271 public:
272 AutocompleteResultView(AutocompleteResultViewModel* model,
273 int model_index,
274 const gfx::Font& font,
275 const gfx::Font& bold_font);
276 virtual ~AutocompleteResultView();
277
278 // Updates the match used to paint the contents of this result view. We copy
279 // the match so that we can continue to paint the last result even after the
280 // model has changed.
281 void set_match(const AutocompleteMatch& match) { match_ = match; }
282
283 // Overridden from views::View:
284 virtual void Paint(gfx::Canvas* canvas);
285 virtual void Layout();
286 virtual gfx::Size GetPreferredSize();
287
288 // Returns the preferred height for a single row.
289 static int GetPreferredHeight(const gfx::Font& font,
290 const gfx::Font& bold_font);
291
292 private:
293 // Precalculated data used to draw the portion of a match classification that
294 // fits entirely within one run.
295 struct ClassificationData {
296 string16 text;
297 const gfx::Font* font;
298 SkColor color;
299 int pixel_width;
300 };
301 typedef std::vector<ClassificationData> Classifications;
302
303 // Precalculated data used to draw a complete visual run within the match.
304 // This will include all or part of at leasdt one, and possibly several,
305 // classifications.
306 struct RunData {
307 size_t run_start; // Offset within the match text where this run begins.
308 int visual_order; // Where this run occurs in visual order. The earliest
309 // run drawn is run 0.
310 bool is_rtl;
311 int pixel_width;
312 Classifications classifications; // Classification pieces within this run,
313 // in logical order.
314 };
315 typedef std::vector<RunData> Runs;
316
317 // Predicate functions for use when sorting the runs.
318 static bool SortRunsLogically(const RunData& lhs, const RunData& rhs);
319 static bool SortRunsVisually(const RunData& lhs, const RunData& rhs);
320
321 ResultViewState GetState() const;
322
323 const SkBitmap* GetIcon() const;
324
325 // Draws the specified |text| into the canvas, using highlighting provided by
326 // |classifications|. If |force_dim| is true, ACMatchClassification::DIM is
327 // added to all of the classifications. Returns the x position to the right
328 // of the string.
329 int DrawString(gfx::Canvas* canvas,
330 const string16& text,
331 const ACMatchClassifications& classifications,
332 bool force_dim,
333 int x,
334 int y);
335
336 // Elides |runs| to fit in |remaining_width|. The runs in |runs| should be in
337 // logical order.
338 //
339 // When we need to elide a run, the ellipsis will be placed at the end of that
340 // run. This means that if we elide a run whose visual direction is opposite
341 // that of the drawing context, the ellipsis will not be at the "end" of the
342 // drawn string. For example, if in an LTR context we have the LTR run
343 // "LTR_STRING" and the RTL run "RTL_STRING", the unelided text would be drawn
344 // like:
345 // LTR_STRING GNIRTS_LTR
346 // If we need to elide the RTL run, then it will be drawn like:
347 // LTR_STRING ...RTS_LTR
348 // Instead of:
349 // LTR_STRING RTS_LTR...
350 void Elide(Runs* runs, int remaining_width) const;
351
352 // This row's model and model index.
353 AutocompleteResultViewModel* model_;
354 size_t model_index_;
355
356 const gfx::Font normal_font_;
357 const gfx::Font bold_font_;
358
359 // Width of the ellipsis in the normal font.
360 int ellipsis_width_;
361
362 // A context used for mirroring regions.
363 class MirroringContext;
364 scoped_ptr<MirroringContext> mirroring_context_;
365
366 // Layout rects for various sub-components of the view.
367 gfx::Rect icon_bounds_;
368 gfx::Rect text_bounds_;
369
370 static int icon_size_;
371
372 AutocompleteMatch match_;
373
374 DISALLOW_COPY_AND_ASSIGN(AutocompleteResultView);
375 };
376
377 // static
378 int AutocompleteResultView::icon_size_ = 0;
379
380 // This class is a utility class for calculations affected by whether the result
381 // view is horizontally mirrored. The drawing functions can be written as if
382 // all drawing occurs left-to-right, and then use this class to get the actual
383 // coordinates to begin drawing onscreen.
384 class AutocompleteResultView::MirroringContext {
385 public:
386 MirroringContext() : center_(0), right_(0) {}
387
388 // Tells the mirroring context to use the provided range as the physical
389 // bounds of the drawing region. When coordinate mirroring is needed, the
390 // mirror point will be the center of this range.
391 void Initialize(int x, int width) {
392 center_ = x + width / 2;
393 right_ = x + width;
394 }
395
396 // Given a logical range within the drawing region, returns the coordinate of
397 // the possibly-mirrored "left" side. (This functions exactly like
398 // View::MirroredLeftPointForRect().)
399 int mirrored_left_coord(int left, int right) const {
400 return base::i18n::IsRTL() ? (center_ + (center_ - right)) : left;
401 }
402
403 // Given a logical coordinate within the drawing region, returns the remaining
404 // width available.
405 int remaining_width(int x) const {
406 return right_ - x;
407 }
408
409 private:
410 int center_;
411 int right_;
412
413 DISALLOW_COPY_AND_ASSIGN(MirroringContext);
414 };
415
416 AutocompleteResultView::AutocompleteResultView(
417 AutocompleteResultViewModel* model,
418 int model_index,
419 const gfx::Font& font,
420 const gfx::Font& bold_font)
421 : model_(model),
422 model_index_(model_index),
423 normal_font_(font),
424 bold_font_(bold_font),
425 ellipsis_width_(font.GetStringWidth(string16(kEllipsis))),
426 mirroring_context_(new MirroringContext()),
427 match_(NULL, 0, false, AutocompleteMatch::URL_WHAT_YOU_TYPED) {
428 CHECK(model_index >= 0);
429 if (icon_size_ == 0) {
430 icon_size_ = ResourceBundle::GetSharedInstance().GetBitmapNamed(
431 AutocompleteMatch::TypeToIcon(AutocompleteMatch::URL_WHAT_YOU_TYPED))->
432 width();
433 }
434 }
435
436 AutocompleteResultView::~AutocompleteResultView() {
437 }
438
439 void AutocompleteResultView::Paint(gfx::Canvas* canvas) {
440 const ResultViewState state = GetState();
441 if (state != NORMAL)
442 canvas->AsCanvasSkia()->drawColor(GetColor(state, BACKGROUND));
443
444 // Paint the icon.
445 canvas->DrawBitmapInt(*GetIcon(), GetMirroredXForRect(icon_bounds_),
446 icon_bounds_.y());
447
448 // Paint the text.
449 int x = GetMirroredXForRect(text_bounds_);
450 mirroring_context_->Initialize(x, text_bounds_.width());
451 x = DrawString(canvas, match_.contents, match_.contents_class, false, x,
452 text_bounds_.y());
453
454 // Paint the description.
455 // TODO(pkasting): Because we paint in multiple separate pieces, we can wind
456 // up with no space even for an ellipsis for one or both of these pieces.
457 // Instead, we should paint the entire match as a single long string. This
458 // would also let us use a more properly-localizable string than we get with
459 // just the IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR.
460 if (!match_.description.empty()) {
461 string16 separator =
462 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR);
463 ACMatchClassifications classifications;
464 classifications.push_back(
465 ACMatchClassification(0, ACMatchClassification::NONE));
466 x = DrawString(canvas, separator, classifications, true, x,
467 text_bounds_.y());
468
469 DrawString(canvas, match_.description, match_.description_class, true, x,
470 text_bounds_.y());
471 }
472 }
473
474 void AutocompleteResultView::Layout() {
475 icon_bounds_.SetRect(LocationBarView::kEdgeItemPadding,
476 (height() - icon_size_) / 2, icon_size_, icon_size_);
477 int text_x = icon_bounds_.right() + LocationBarView::kItemPadding;
478 int font_height = std::max(normal_font_.GetHeight(), bold_font_.GetHeight());
479 text_bounds_.SetRect(text_x, std::max(0, (height() - font_height) / 2),
480 std::max(bounds().width() - text_x - LocationBarView::kEdgeItemPadding,
481 0), font_height);
482 }
483
484 gfx::Size AutocompleteResultView::GetPreferredSize() {
485 return gfx::Size(0, GetPreferredHeight(normal_font_, bold_font_));
486 }
487
488 // static
489 int AutocompleteResultView::GetPreferredHeight(
490 const gfx::Font& font,
491 const gfx::Font& bold_font) {
492 int text_height = std::max(font.GetHeight(), bold_font.GetHeight()) +
493 (kTextVerticalPadding * 2);
494 int icon_height = icon_size_ + (kIconVerticalPadding * 2);
495 return std::max(icon_height, text_height);
496 }
497
498 // static
499 bool AutocompleteResultView::SortRunsLogically(const RunData& lhs,
500 const RunData& rhs) {
501 return lhs.run_start < rhs.run_start;
502 }
503
504 // static
505 bool AutocompleteResultView::SortRunsVisually(const RunData& lhs,
506 const RunData& rhs) {
507 return lhs.visual_order < rhs.visual_order;
508 }
509
510 ResultViewState AutocompleteResultView::GetState() const {
511 if (model_->IsSelectedIndex(model_index_))
512 return SELECTED;
513 return model_->IsHoveredIndex(model_index_) ? HOVERED : NORMAL;
514 }
515
516 const SkBitmap* AutocompleteResultView::GetIcon() const {
517 const SkBitmap* bitmap = model_->GetSpecialIcon(model_index_);
518 if (bitmap)
519 return bitmap;
520
521 int icon = match_.starred ?
522 IDR_OMNIBOX_STAR : AutocompleteMatch::TypeToIcon(match_.type);
523 if (model_->IsSelectedIndex(model_index_)) {
524 switch (icon) {
525 case IDR_OMNIBOX_HTTP: icon = IDR_OMNIBOX_HTTP_SELECTED; break;
526 case IDR_OMNIBOX_HISTORY: icon = IDR_OMNIBOX_HISTORY_SELECTED; break;
527 case IDR_OMNIBOX_SEARCH: icon = IDR_OMNIBOX_SEARCH_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
535 int AutocompleteResultView::DrawString(
536 gfx::Canvas* canvas,
537 const string16& 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 base::i18n::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,
673 y, j->pixel_width, j->font->GetHeight(), flags);
674 x += j->pixel_width;
675 }
676 }
677
678 return x;
679 }
680
681 void 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 string16 elided_text =
712 ui::ElideText(j->text, *j->font, remaining_width, false);
713 Classifications::reverse_iterator prior_classification(j);
714 ++prior_classification;
715 const bool on_first_classification =
716 (prior_classification == i->classifications.rend());
717 if (elided_text.empty() && (remaining_width >= ellipsis_width_) &&
718 on_first_classification) {
719 // Edge case: This classification is bold, we can't fit a bold ellipsis
720 // but we can fit a normal one, and this is the first classification in
721 // the run. We should display a lone normal ellipsis, because appending
722 // one to the end of the previous run might put it in the wrong visual
723 // location (if the previous run is reversed from the normal visual
724 // order).
725 // NOTE: If this isn't the first classification in the run, we don't
726 // need to bother with this; see note below.
727 elided_text = kEllipsis;
728 }
729 if (!elided_text.empty()) {
730 // Success. Elide this classification and stop.
731 j->text = elided_text;
732
733 // If we could only fit an ellipsis, then only make it bold if there was
734 // an immediate prior classification in this run that was also bold, or
735 // it will look orphaned.
736 if ((elided_text.length() == 1) &&
737 (on_first_classification ||
738 (prior_classification->font == &normal_font_)))
739 j->font = &normal_font_;
740
741 j->pixel_width = j->font->GetStringWidth(elided_text);
742
743 // Erase any other classifications that come after the elided one.
744 i->classifications.erase(j.base(), i->classifications.end());
745 runs->erase(i.base(), runs->end());
746 return;
747 }
748
749 // We couldn't fit an ellipsis. Move back one classification,
750 // append an ellipsis, and try again.
751 // NOTE: In the edge case that a bold ellipsis doesn't fit but a
752 // normal one would, and we reach here, then there is a previous
753 // classification in this run, and so either:
754 // * It's normal, and will be able to draw successfully with the
755 // ellipsis we'll append to it, or
756 // * It is also bold, in which case we don't want to fall back
757 // to a normal ellipsis anyway (see comment above).
758 }
759 }
760
761 // We couldn't draw anything.
762 runs->clear();
763 }
764
765 //////////////////////////////////////////////////////////////////////////////// 199 ////////////////////////////////////////////////////////////////////////////////
766 // AutocompletePopupContentsView, public: 200 // AutocompletePopupContentsView, public:
767 201
768 AutocompletePopupContentsView::AutocompletePopupContentsView( 202 AutocompletePopupContentsView::AutocompletePopupContentsView(
769 const gfx::Font& font, 203 const gfx::Font& font,
770 AutocompleteEditView* edit_view, 204 AutocompleteEditView* edit_view,
771 AutocompleteEditModel* edit_model, 205 AutocompleteEditModel* edit_model,
772 Profile* profile, 206 Profile* profile,
773 const views::View* location_bar) 207 const views::View* location_bar)
774 : model_(new AutocompletePopupModel(this, edit_model, profile)), 208 : model_(new AutocompletePopupModel(this, edit_model, profile)),
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
829 // destroying the popup would cause us to read garbage when we unwind back 263 // destroying the popup would cause us to read garbage when we unwind back
830 // to that level. 264 // to that level.
831 popup_->Close(); // This will eventually delete the popup. 265 popup_->Close(); // This will eventually delete the popup.
832 popup_.reset(); 266 popup_.reset();
833 } 267 }
834 return; 268 return;
835 } 269 }
836 270
837 // Update the match cached by each row, in the process of doing so make sure 271 // Update the match cached by each row, in the process of doing so make sure
838 // we have enough row views. 272 // we have enough row views.
839 int total_child_height = 0;
840 size_t child_rv_count = child_count(); 273 size_t child_rv_count = child_count();
841 if (opt_in_view_) { 274 if (opt_in_view_) {
842 DCHECK(child_rv_count > 0); 275 DCHECK(child_rv_count > 0);
843 child_rv_count--; 276 child_rv_count--;
844 } 277 }
845 for (size_t i = 0; i < model_->result().size(); ++i) { 278 for (size_t i = 0; i < model_->result().size(); ++i) {
846 AutocompleteResultView* result_view; 279 AutocompleteResultView* result_view;
847 if (i >= child_rv_count) { 280 if (i >= child_rv_count) {
848 result_view = 281 result_view =
849 new AutocompleteResultView(this, i, result_font_, result_bold_font_); 282 CreateResultView(this, i, result_font_, result_bold_font_);
850 AddChildViewAt(result_view, static_cast<int>(i)); 283 AddChildViewAt(result_view, static_cast<int>(i));
851 } else { 284 } else {
852 result_view = static_cast<AutocompleteResultView*>(GetChildViewAt(i)); 285 result_view = static_cast<AutocompleteResultView*>(GetChildViewAt(i));
853 result_view->SetVisible(true); 286 result_view->SetVisible(true);
854 } 287 }
855 result_view->set_match(GetMatchAtIndex(i)); 288 result_view->set_match(GetMatchAtIndex(i));
856 total_child_height += result_view->GetPreferredSize().height();
857 } 289 }
858 for (size_t i = model_->result().size(); i < child_rv_count; ++i) 290 for (size_t i = model_->result().size(); i < child_rv_count; ++i)
859 GetChildViewAt(i)->SetVisible(false); 291 GetChildViewAt(i)->SetVisible(false);
860 292
861 PromoCounter* counter = model_->profile()->GetInstantPromoCounter(); 293 PromoCounter* counter = model_->profile()->GetInstantPromoCounter();
862 if (!opt_in_view_ && counter && counter->ShouldShow(base::Time::Now())) { 294 if (!opt_in_view_ && counter && counter->ShouldShow(base::Time::Now())) {
863 opt_in_view_ = new InstantOptInView(this, result_bold_font_, result_font_); 295 opt_in_view_ = new InstantOptInView(this, result_bold_font_, result_font_);
864 AddChildView(opt_in_view_); 296 AddChildView(opt_in_view_);
865 } else if (opt_in_view_ && (!counter || 297 } else if (opt_in_view_ && (!counter ||
866 !counter->ShouldShow(base::Time::Now()))) { 298 !counter->ShouldShow(base::Time::Now()))) {
867 delete opt_in_view_; 299 delete opt_in_view_;
868 opt_in_view_ = NULL; 300 opt_in_view_ = NULL;
869 } 301 }
870 302
871 if (opt_in_view_) 303 gfx::Rect new_target_bounds = CalculateTargetBounds(CalculatePopupHeight());
872 total_child_height += opt_in_view_->GetPreferredSize().height();
873
874 gfx::Rect new_target_bounds = CalculateTargetBounds(total_child_height);
875 304
876 // If we're animating and our target height changes, reset the animation. 305 // If we're animating and our target height changes, reset the animation.
877 // NOTE: If we just reset blindly on _every_ update, then when the user types 306 // NOTE: If we just reset blindly on _every_ update, then when the user types
878 // rapidly we could get "stuck" trying repeatedly to animate shrinking by the 307 // rapidly we could get "stuck" trying repeatedly to animate shrinking by the
879 // last few pixels to get to one visible result. 308 // last few pixels to get to one visible result.
880 if (new_target_bounds.height() != target_bounds_.height()) 309 if (new_target_bounds.height() != target_bounds_.height())
881 size_animation_.Reset(); 310 size_animation_.Reset();
882 target_bounds_ = new_target_bounds; 311 target_bounds_ = new_target_bounds;
883 312
884 if (popup_ == NULL) { 313 if (popup_ == NULL) {
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
950 // 379 //
951 // Because the border of this view creates an anti-aliased round-rect region 380 // Because the border of this view creates an anti-aliased round-rect region
952 // for the contents, we need to render our rectangular result child views into 381 // for the contents, we need to render our rectangular result child views into
953 // this round rect region. We can't use a simple clip because clipping is 382 // this round rect region. We can't use a simple clip because clipping is
954 // 1-bit and we get nasty jagged edges. 383 // 1-bit and we get nasty jagged edges.
955 // 384 //
956 // Instead, we paint all our children into a second canvas and use that as a 385 // Instead, we paint all our children into a second canvas and use that as a
957 // shader to fill a path representing the round-rect clipping region. This 386 // shader to fill a path representing the round-rect clipping region. This
958 // yields a nice anti-aliased edge. 387 // yields a nice anti-aliased edge.
959 gfx::CanvasSkia contents_canvas(width(), height(), true); 388 gfx::CanvasSkia contents_canvas(width(), height(), true);
960 contents_canvas.drawColor(GetColor(NORMAL, BACKGROUND)); 389 PaintChildren(&contents_canvas);
961 View::PaintChildren(&contents_canvas); 390
962 // We want the contents background to be slightly transparent so we can see 391 // We want the contents background to be slightly transparent so we can see
963 // the blurry glass effect on DWM systems behind. We do this _after_ we paint 392 // the blurry glass effect on DWM systems behind. We do this _after_ we paint
964 // the children since they paint text, and GDI will reset this alpha data if 393 // the children since they paint text, and GDI will reset this alpha data if
965 // we paint text after this call. 394 // we paint text after this call.
966 MakeCanvasTransparent(&contents_canvas); 395 MakeCanvasTransparent(&contents_canvas);
967 396
968 // Now paint the contents of the contents canvas into the actual canvas. 397 // Now paint the contents of the contents canvas into the actual canvas.
969 SkPaint paint; 398 SkPaint paint;
970 paint.setAntiAlias(true); 399 paint.setAntiAlias(true);
971 400
972 SkShader* shader = SkShader::CreateBitmapShader( 401 SkShader* shader = SkShader::CreateBitmapShader(
973 contents_canvas.getDevice()->accessBitmap(false), 402 contents_canvas.getDevice()->accessBitmap(false),
974 SkShader::kClamp_TileMode, 403 SkShader::kClamp_TileMode,
975 SkShader::kClamp_TileMode); 404 SkShader::kClamp_TileMode);
976 paint.setShader(shader); 405 paint.setShader(shader);
977 shader->unref(); 406 shader->unref();
978 407
979 gfx::Path path; 408 gfx::Path path;
980 MakeContentsPath(&path, GetContentsBounds()); 409 MakeContentsPath(&path, GetContentsBounds());
981 canvas->AsCanvasSkia()->drawPath(path, paint); 410 canvas->AsCanvasSkia()->drawPath(path, paint);
982 411
983 // Now we paint the border, so it will be alpha-blended atop the contents. 412 // Now we paint the border, so it will be alpha-blended atop the contents.
984 // This looks slightly better in the corners than drawing the contents atop 413 // This looks slightly better in the corners than drawing the contents atop
985 // the border. 414 // the border.
986 PaintBorder(canvas); 415 PaintBorder(canvas);
987 } 416 }
988 417
418 void AutocompletePopupContentsView::PaintChildren(gfx::CanvasSkia* canvas) {
419 canvas->drawColor(AutocompleteResultView::GetColor(
420 AutocompleteResultView::NORMAL, AutocompleteResultView::BACKGROUND));
421 View::PaintChildren(canvas);
422 }
423
989 void AutocompletePopupContentsView::Layout() { 424 void AutocompletePopupContentsView::Layout() {
990 UpdateBlurRegion(); 425 UpdateBlurRegion();
991 426
992 // Size our children to the available content area. 427 // Size our children to the available content area.
428 LayoutChildren();
429
430 // We need to manually schedule a paint here since we are a layered window and
431 // won't implicitly require painting until we ask for one.
432 SchedulePaint();
433 }
434
435 void AutocompletePopupContentsView::LayoutChildren() {
993 gfx::Rect contents_rect = GetContentsBounds(); 436 gfx::Rect contents_rect = GetContentsBounds();
994 int top = contents_rect.y(); 437 int top = contents_rect.y();
995 for (int i = 0; i < child_count(); ++i) { 438 for (int i = 0; i < child_count(); ++i) {
996 View* v = GetChildViewAt(i); 439 View* v = GetChildViewAt(i);
997 if (v->IsVisible()) { 440 if (v->IsVisible()) {
998 v->SetBounds(contents_rect.x(), top, contents_rect.width(), 441 v->SetBounds(contents_rect.x(), top, contents_rect.width(),
999 v->GetPreferredSize().height()); 442 v->GetPreferredSize().height());
1000 top = v->bounds().bottom(); 443 top = v->bounds().bottom();
1001 } 444 }
1002 } 445 }
1003
1004 // We need to manually schedule a paint here since we are a layered window and
1005 // won't implicitly require painting until we ask for one.
1006 SchedulePaint();
1007 } 446 }
1008 447
1009
1010 void AutocompletePopupContentsView::OnMouseEntered( 448 void AutocompletePopupContentsView::OnMouseEntered(
1011 const views::MouseEvent& event) { 449 const views::MouseEvent& event) {
1012 model_->SetHoveredLine(GetIndexForPoint(event.location())); 450 model_->SetHoveredLine(GetIndexForPoint(event.location()));
1013 } 451 }
1014 452
1015 void AutocompletePopupContentsView::OnMouseMoved( 453 void AutocompletePopupContentsView::OnMouseMoved(
1016 const views::MouseEvent& event) { 454 const views::MouseEvent& event) {
1017 model_->SetHoveredLine(GetIndexForPoint(event.location())); 455 model_->SetHoveredLine(GetIndexForPoint(event.location()));
1018 } 456 }
1019 457
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
1067 if (!opt_in_view_) 505 if (!opt_in_view_)
1068 return this; 506 return this;
1069 507
1070 views::View* child = views::View::GetViewForPoint(point); 508 views::View* child = views::View::GetViewForPoint(point);
1071 views::View* ancestor = child; 509 views::View* ancestor = child;
1072 while (ancestor && ancestor != opt_in_view_) 510 while (ancestor && ancestor != opt_in_view_)
1073 ancestor = ancestor->parent(); 511 ancestor = ancestor->parent();
1074 return ancestor ? child : this; 512 return ancestor ? child : this;
1075 } 513 }
1076 514
515 ////////////////////////////////////////////////////////////////////////////////
516 // AutocompletePopupContentsView, protected:
517
518 int AutocompletePopupContentsView::CalculatePopupHeight() {
519 DCHECK_GE(static_cast<size_t>(child_count()), model_->result().size());
520 int popup_height = 0;
521 for (size_t i = 0; i < model_->result().size(); ++i)
522 popup_height += GetChildViewAt(i)->GetPreferredSize().height();
523 return popup_height +
524 (opt_in_view_ ? opt_in_view_->GetPreferredSize().height() : 0);
525 }
526
527 AutocompleteResultView* AutocompletePopupContentsView::CreateResultView(
528 AutocompleteResultViewModel* model,
529 int model_index,
530 const gfx::Font& font,
531 const gfx::Font& bold_font) {
532 return new AutocompleteResultView(model, model_index, font, bold_font);
533 }
1077 534
1078 //////////////////////////////////////////////////////////////////////////////// 535 ////////////////////////////////////////////////////////////////////////////////
1079 // AutocompletePopupContentsView, private: 536 // AutocompletePopupContentsView, private:
1080 537
1081 bool AutocompletePopupContentsView::HasMatchAt(size_t index) const { 538 bool AutocompletePopupContentsView::HasMatchAt(size_t index) const {
1082 return index < model_->result().size(); 539 return index < model_->result().size();
1083 } 540 }
1084 541
1085 const AutocompleteMatch& AutocompletePopupContentsView::GetMatchAtIndex( 542 const AutocompleteMatch& AutocompletePopupContentsView::GetMatchAtIndex(
1086 size_t index) const { 543 size_t index) const {
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
1126 bb.hRgnBlur = popup_region.Get(); 583 bb.hRgnBlur = popup_region.Get();
1127 DwmEnableBlurBehindWindow(GetWidget()->GetNativeView(), &bb); 584 DwmEnableBlurBehindWindow(GetWidget()->GetNativeView(), &bb);
1128 #endif 585 #endif
1129 } 586 }
1130 587
1131 void AutocompletePopupContentsView::MakeCanvasTransparent( 588 void AutocompletePopupContentsView::MakeCanvasTransparent(
1132 gfx::Canvas* canvas) { 589 gfx::Canvas* canvas) {
1133 // Allow the window blur effect to show through the popup background. 590 // Allow the window blur effect to show through the popup background.
1134 SkAlpha alpha = GetThemeProvider()->ShouldUseNativeFrame() ? 591 SkAlpha alpha = GetThemeProvider()->ShouldUseNativeFrame() ?
1135 kGlassPopupAlpha : kOpaquePopupAlpha; 592 kGlassPopupAlpha : kOpaquePopupAlpha;
1136 canvas->AsCanvasSkia()->drawColor( 593 canvas->AsCanvasSkia()->drawColor(SkColorSetA(
1137 SkColorSetA(GetColor(NORMAL, BACKGROUND), alpha), 594 AutocompleteResultView::GetColor(AutocompleteResultView::NORMAL,
1138 SkXfermode::kDstIn_Mode); 595 AutocompleteResultView::BACKGROUND), alpha), SkXfermode::kDstIn_Mode);
1139 } 596 }
1140 597
1141 void AutocompletePopupContentsView::OpenIndex( 598 void AutocompletePopupContentsView::OpenIndex(
1142 size_t index, 599 size_t index,
1143 WindowOpenDisposition disposition) { 600 WindowOpenDisposition disposition) {
1144 if (!HasMatchAt(index)) 601 if (!HasMatchAt(index))
1145 return; 602 return;
1146 603
1147 const AutocompleteMatch& match = model_->result().match_at(index); 604 const AutocompleteMatch& match = model_->result().match_at(index);
1148 // OpenURL() may close the popup, which will clear the result set and, by 605 // OpenURL() may close the popup, which will clear the result set and, by
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
1200 opt_in_view_ = NULL; 657 opt_in_view_ = NULL;
1201 PromoCounter* counter = model_->profile()->GetInstantPromoCounter(); 658 PromoCounter* counter = model_->profile()->GetInstantPromoCounter();
1202 DCHECK(counter); 659 DCHECK(counter);
1203 counter->Hide(); 660 counter->Hide();
1204 if (opt_in) { 661 if (opt_in) {
1205 browser::ShowInstantConfirmDialogIfNecessary( 662 browser::ShowInstantConfirmDialogIfNecessary(
1206 location_bar_->GetWindow()->GetNativeWindow(), model_->profile()); 663 location_bar_->GetWindow()->GetNativeWindow(), model_->profile());
1207 } 664 }
1208 UpdatePopupAppearance(); 665 UpdatePopupAppearance();
1209 } 666 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698