1 // Copyright (c) 2012 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.
5 // For WinDDK ATL compatibility, these ATL headers must come first.
6 #include "build/build_config.h"
8 #include <atlbase.h> // NOLINT
9 #include <atlwin.h> // NOLINT
12 #include "chrome/browser/ui/views/omnibox/omnibox_result_view.h"
14 #include <algorithm> // NOLINT
16 #include "base/i18n/bidi_line_iterator.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_util.h"
19 #include "chrome/browser/themes/theme_properties.h"
20 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
21 #include "chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h"
22 #include "chrome/grit/generated_resources.h"
23 #include "components/omnibox/browser/omnibox_popup_model.h"
24 #include "grit/components_scaled_resources.h"
25 #include "grit/theme_resources.h"
26 #include "third_party/skia/include/core/SkColor.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/base/resource/material_design/material_design_controller.h"
29 #include "ui/base/resource/resource_bundle.h"
30 #include "ui/base/theme_provider.h"
31 #include "ui/gfx/canvas.h"
32 #include "ui/gfx/color_utils.h"
33 #include "ui/gfx/image/image.h"
34 #include "ui/gfx/range/range.h"
35 #include "ui/gfx/render_text.h"
36 #include "ui/gfx/text_utils.h"
38 using ui::NativeTheme
;
42 // A mapping from OmniboxResultView's ResultViewState/ColorKind types to
43 // NativeTheme colors.
44 struct TranslationTable
{
45 ui::NativeTheme::ColorId id
;
46 OmniboxResultView::ResultViewState state
;
47 OmniboxResultView::ColorKind kind
;
48 } static const kTranslationTable
[] = {
49 { NativeTheme::kColorId_ResultsTableNormalBackground
,
50 OmniboxResultView::NORMAL
, OmniboxResultView::BACKGROUND
},
51 { NativeTheme::kColorId_ResultsTableHoveredBackground
,
52 OmniboxResultView::HOVERED
, OmniboxResultView::BACKGROUND
},
53 { NativeTheme::kColorId_ResultsTableSelectedBackground
,
54 OmniboxResultView::SELECTED
, OmniboxResultView::BACKGROUND
},
55 { NativeTheme::kColorId_ResultsTableNormalText
,
56 OmniboxResultView::NORMAL
, OmniboxResultView::TEXT
},
57 { NativeTheme::kColorId_ResultsTableHoveredText
,
58 OmniboxResultView::HOVERED
, OmniboxResultView::TEXT
},
59 { NativeTheme::kColorId_ResultsTableSelectedText
,
60 OmniboxResultView::SELECTED
, OmniboxResultView::TEXT
},
61 { NativeTheme::kColorId_ResultsTableNormalDimmedText
,
62 OmniboxResultView::NORMAL
, OmniboxResultView::DIMMED_TEXT
},
63 { NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
64 OmniboxResultView::HOVERED
, OmniboxResultView::DIMMED_TEXT
},
65 { NativeTheme::kColorId_ResultsTableSelectedDimmedText
,
66 OmniboxResultView::SELECTED
, OmniboxResultView::DIMMED_TEXT
},
67 { NativeTheme::kColorId_ResultsTableNormalUrl
,
68 OmniboxResultView::NORMAL
, OmniboxResultView::URL
},
69 { NativeTheme::kColorId_ResultsTableHoveredUrl
,
70 OmniboxResultView::HOVERED
, OmniboxResultView::URL
},
71 { NativeTheme::kColorId_ResultsTableSelectedUrl
,
72 OmniboxResultView::SELECTED
, OmniboxResultView::URL
},
73 { NativeTheme::kColorId_ResultsTableNormalDivider
,
74 OmniboxResultView::NORMAL
, OmniboxResultView::DIVIDER
},
75 { NativeTheme::kColorId_ResultsTableHoveredDivider
,
76 OmniboxResultView::HOVERED
, OmniboxResultView::DIVIDER
},
77 { NativeTheme::kColorId_ResultsTableSelectedDivider
,
78 OmniboxResultView::SELECTED
, OmniboxResultView::DIVIDER
},
82 ui::ResourceBundle::FontStyle font
;
83 ui::NativeTheme::ColorId colors
[OmniboxResultView::NUM_STATES
];
84 gfx::BaselineStyle baseline
;
85 } const kTextStyles
[] = {
87 {ui::ResourceBundle::LargeFont
,
88 {NativeTheme::kColorId_ResultsTableNormalText
,
89 NativeTheme::kColorId_ResultsTableHoveredText
,
90 NativeTheme::kColorId_ResultsTableSelectedText
},
91 gfx::NORMAL_BASELINE
},
93 {ui::ResourceBundle::LargeFont
,
94 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
95 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
96 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
97 gfx::NORMAL_BASELINE
},
99 {ui::ResourceBundle::LargeFont
,
100 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
101 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
102 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
104 // 4 DESCRIPTION_TEXT
105 {ui::ResourceBundle::BaseFont
,
106 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
107 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
108 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
109 gfx::NORMAL_BASELINE
},
110 // 5 DESCRIPTION_TEXT_NEGATIVE
111 {ui::ResourceBundle::LargeFont
,
112 {NativeTheme::kColorId_ResultsTableNegativeText
,
113 NativeTheme::kColorId_ResultsTableNegativeHoveredText
,
114 NativeTheme::kColorId_ResultsTableNegativeSelectedText
},
116 // 6 DESCRIPTION_TEXT_POSITIVE
117 {ui::ResourceBundle::LargeFont
,
118 {NativeTheme::kColorId_ResultsTablePositiveText
,
119 NativeTheme::kColorId_ResultsTablePositiveHoveredText
,
120 NativeTheme::kColorId_ResultsTablePositiveSelectedText
},
123 {ui::ResourceBundle::BaseFont
,
124 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
125 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
126 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
129 {ui::ResourceBundle::BaseFont
,
130 {NativeTheme::kColorId_ResultsTableNormalText
,
131 NativeTheme::kColorId_ResultsTableHoveredText
,
132 NativeTheme::kColorId_ResultsTableSelectedText
},
133 gfx::NORMAL_BASELINE
},
134 // 9 SUGGESTION_TEXT_POSITIVE
135 {ui::ResourceBundle::BaseFont
,
136 {NativeTheme::kColorId_ResultsTablePositiveText
,
137 NativeTheme::kColorId_ResultsTablePositiveHoveredText
,
138 NativeTheme::kColorId_ResultsTablePositiveSelectedText
},
139 gfx::NORMAL_BASELINE
},
140 // 10 SUGGESTION_TEXT_NEGATIVE
141 {ui::ResourceBundle::BaseFont
,
142 {NativeTheme::kColorId_ResultsTableNegativeText
,
143 NativeTheme::kColorId_ResultsTableNegativeHoveredText
,
144 NativeTheme::kColorId_ResultsTableNegativeSelectedText
},
145 gfx::NORMAL_BASELINE
},
146 // 11 SUGGESTION_LINK_COLOR
147 {ui::ResourceBundle::BaseFont
,
148 {NativeTheme::kColorId_ResultsTableNormalUrl
,
149 NativeTheme::kColorId_ResultsTableHoveredUrl
,
150 NativeTheme::kColorId_ResultsTableSelectedUrl
},
151 gfx::NORMAL_BASELINE
},
153 {ui::ResourceBundle::LargeFont
,
154 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
155 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
156 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
158 // 13 PERSONALIZED_SUGGESTION_TEXT
159 {ui::ResourceBundle::BaseFont
,
160 {NativeTheme::kColorId_ResultsTableNormalText
,
161 NativeTheme::kColorId_ResultsTableHoveredText
,
162 NativeTheme::kColorId_ResultsTableSelectedText
},
163 gfx::NORMAL_BASELINE
},
166 const TextStyle
& GetTextStyle(int type
) {
167 if (type
< 1 || static_cast<size_t>(type
) > arraysize(kTextStyles
))
169 // Subtract one because the types are one based (not zero based).
170 return kTextStyles
[type
- 1];
175 ////////////////////////////////////////////////////////////////////////////////
176 // OmniboxResultView, public:
178 // This class is a utility class for calculations affected by whether the result
179 // view is horizontally mirrored. The drawing functions can be written as if
180 // all drawing occurs left-to-right, and then use this class to get the actual
181 // coordinates to begin drawing onscreen.
182 class OmniboxResultView::MirroringContext
{
184 MirroringContext() : center_(0), right_(0) {}
186 // Tells the mirroring context to use the provided range as the physical
187 // bounds of the drawing region. When coordinate mirroring is needed, the
188 // mirror point will be the center of this range.
189 void Initialize(int x
, int width
) {
190 center_
= x
+ width
/ 2;
194 // Given a logical range within the drawing region, returns the coordinate of
195 // the possibly-mirrored "left" side. (This functions exactly like
196 // View::MirroredLeftPointForRect().)
197 int mirrored_left_coord(int left
, int right
) const {
198 return base::i18n::IsRTL() ? (center_
+ (center_
- right
)) : left
;
201 // Given a logical coordinate within the drawing region, returns the remaining
203 int remaining_width(int x
) const {
211 DISALLOW_COPY_AND_ASSIGN(MirroringContext
);
214 OmniboxResultView::OmniboxResultView(OmniboxPopupContentsView
* model
,
216 LocationBarView
* location_bar_view
,
217 const gfx::FontList
& font_list
)
219 model_index_(model_index
),
220 location_bar_view_(location_bar_view
),
221 font_list_(font_list
),
223 std::max(font_list
.GetHeight(),
224 font_list
.DeriveWithStyle(gfx::Font::BOLD
).GetHeight())),
225 mirroring_context_(new MirroringContext()),
226 keyword_icon_(new views::ImageView()),
227 animation_(new gfx::SlideAnimation(this)) {
228 CHECK_GE(model_index
, 0);
229 if (default_icon_size_
== 0) {
231 location_bar_view_
->GetThemeProvider()->GetImageSkiaNamed(
232 AutocompleteMatch::TypeToIcon(
233 AutocompleteMatchType::URL_WHAT_YOU_TYPED
))->width();
235 keyword_icon_
->set_owned_by_client();
236 keyword_icon_
->EnableCanvasFlippingForRTLUI(true);
237 keyword_icon_
->SetImage(GetKeywordIcon());
238 keyword_icon_
->SizeToPreferredSize();
241 OmniboxResultView::~OmniboxResultView() {
244 SkColor
OmniboxResultView::GetColor(
245 ResultViewState state
,
246 ColorKind kind
) const {
247 for (size_t i
= 0; i
< arraysize(kTranslationTable
); ++i
) {
248 if (kTranslationTable
[i
].state
== state
&&
249 kTranslationTable
[i
].kind
== kind
) {
250 return GetNativeTheme()->GetSystemColor(kTranslationTable
[i
].id
);
258 void OmniboxResultView::SetMatch(const AutocompleteMatch
& match
) {
260 match_
.PossiblySwapContentsAndDescriptionForDisplay();
263 answer_image_
= gfx::ImageSkia();
265 AutocompleteMatch
* associated_keyword_match
= match_
.associated_keyword
.get();
266 if (associated_keyword_match
) {
267 keyword_icon_
->SetImage(GetKeywordIcon());
268 if (!keyword_icon_
->parent())
269 AddChildView(keyword_icon_
.get());
270 } else if (keyword_icon_
->parent()) {
271 RemoveChildView(keyword_icon_
.get());
278 void OmniboxResultView::ShowKeyword(bool show_keyword
) {
285 void OmniboxResultView::Invalidate() {
286 keyword_icon_
->SetImage(GetKeywordIcon());
287 // While the text in the RenderTexts may not have changed, the styling
288 // (color/bold) may need to change. So we reset them to cause them to be
289 // recomputed in OnPaint().
294 gfx::Size
OmniboxResultView::GetPreferredSize() const {
296 return gfx::Size(0, GetContentLineHeight());
297 // An answer implies a match and a description in a large font.
298 return gfx::Size(0, GetContentLineHeight() + GetAnswerLineHeight());
301 ////////////////////////////////////////////////////////////////////////////////
302 // OmniboxResultView, protected:
304 OmniboxResultView::ResultViewState
OmniboxResultView::GetState() const {
305 if (model_
->IsSelectedIndex(model_index_
))
307 return model_
->IsHoveredIndex(model_index_
) ? HOVERED
: NORMAL
;
310 int OmniboxResultView::GetTextHeight() const {
314 void OmniboxResultView::PaintMatch(const AutocompleteMatch
& match
,
315 gfx::RenderText
* contents
,
316 gfx::RenderText
* description
,
319 int y
= text_bounds_
.y();
321 if (!separator_rendertext_
) {
322 const base::string16
& separator
=
323 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR
);
324 separator_rendertext_
.reset(CreateRenderText(separator
).release());
325 separator_rendertext_
->SetColor(GetColor(GetState(), DIMMED_TEXT
));
326 separator_width_
= separator_rendertext_
->GetContentWidth();
329 contents
->SetDisplayRect(gfx::Rect(gfx::Size(INT_MAX
, 0)));
331 description
->SetDisplayRect(gfx::Rect(gfx::Size(INT_MAX
, 0)));
332 int contents_max_width
, description_max_width
;
333 OmniboxPopupModel::ComputeMatchMaxWidths(
334 contents
->GetContentWidth(),
336 description
? description
->GetContentWidth() : 0,
337 mirroring_context_
->remaining_width(x
),
338 !AutocompleteMatch::IsSearchType(match
.type
),
340 &description_max_width
);
342 int after_contents_x
=
343 DrawRenderText(match
, contents
, true, canvas
, x
, y
, contents_max_width
);
345 if (description_max_width
!= 0) {
347 y
+= GetContentLineHeight();
348 if (!answer_image_
.isNull()) {
349 int answer_icon_size
= GetAnswerLineHeight();
350 canvas
->DrawImageInt(
352 0, 0, answer_image_
.width(), answer_image_
.height(),
353 GetMirroredXInView(x
), y
, answer_icon_size
, answer_icon_size
, true);
354 // See TODO in Layout().
355 x
+= answer_icon_size
+
356 location_bar_view_
->GetThemeProvider()->GetDisplayProperty(
357 ThemeProperties::PROPERTY_ICON_LABEL_VIEW_TRAILING_PADDING
);
360 x
= DrawRenderText(match
, separator_rendertext_
.get(), false, canvas
,
361 after_contents_x
, y
, separator_width_
);
364 DrawRenderText(match
, description
, false, canvas
, x
, y
,
365 description_max_width
);
369 int OmniboxResultView::DrawRenderText(
370 const AutocompleteMatch
& match
,
371 gfx::RenderText
* render_text
,
376 int max_width
) const {
377 DCHECK(!render_text
->text().empty());
379 const int remaining_width
= mirroring_context_
->remaining_width(x
);
380 int right_x
= x
+ max_width
;
382 // Infinite suggestions should appear with the leading ellipses vertically
385 (match
.type
== AutocompleteMatchType::SEARCH_SUGGEST_TAIL
)) {
386 // When the directionality of suggestion doesn't match the UI, we try to
387 // vertically stack the ellipsis by restricting the end edge (right_x).
388 const bool is_ui_rtl
= base::i18n::IsRTL();
389 const bool is_match_contents_rtl
=
390 (render_text
->GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT
);
392 GetDisplayOffset(match
, is_ui_rtl
, is_match_contents_rtl
);
394 scoped_ptr
<gfx::RenderText
> prefix_render_text(
395 CreateRenderText(base::UTF8ToUTF16(
396 match
.GetAdditionalInfo(kACMatchPropertyContentsPrefix
))));
397 const int prefix_width
= prefix_render_text
->GetContentWidth();
400 const int max_match_contents_width
= model_
->max_match_contents_width();
402 if (is_ui_rtl
!= is_match_contents_rtl
) {
403 // RTL infinite suggestions appear near the left edge in LTR UI, while LTR
404 // infinite suggestions appear near the right edge in RTL UI. This is
405 // against the natural horizontal alignment of the text. We reduce the
406 // width of the box for suggestion display, so that the suggestions appear
407 // in correct confines. This reduced width allows us to modify the text
408 // alignment (see below).
409 right_x
= x
+ std::min(remaining_width
- prefix_width
,
410 std::max(offset
, max_match_contents_width
));
412 // We explicitly set the horizontal alignment so that when LTR suggestions
413 // show in RTL UI (or vice versa), their ellipses appear stacked in a
415 render_text
->SetHorizontalAlignment(
416 is_match_contents_rtl
? gfx::ALIGN_RIGHT
: gfx::ALIGN_LEFT
);
418 // If the dropdown is wide enough, place the ellipsis at the position
419 // where the omitted text would have ended. Otherwise reduce the offset of
420 // the ellipsis such that the widest suggestion reaches the end of the
422 const int start_offset
= std::max(prefix_width
,
423 std::min(remaining_width
- max_match_contents_width
, offset
));
424 right_x
= x
+ std::min(remaining_width
, start_offset
+ max_width
);
426 prefix_x
= x
- prefix_width
;
428 prefix_render_text
->SetDirectionalityMode(is_match_contents_rtl
?
429 gfx::DIRECTIONALITY_FORCE_RTL
: gfx::DIRECTIONALITY_FORCE_LTR
);
430 prefix_render_text
->SetHorizontalAlignment(
431 is_match_contents_rtl
? gfx::ALIGN_RIGHT
: gfx::ALIGN_LEFT
);
432 prefix_render_text
->SetDisplayRect(
433 gfx::Rect(mirroring_context_
->mirrored_left_coord(
434 prefix_x
, prefix_x
+ prefix_width
),
435 y
, prefix_width
, GetContentLineHeight()));
436 prefix_render_text
->Draw(canvas
);
439 // Set the display rect to trigger eliding.
440 render_text
->SetDisplayRect(
441 gfx::Rect(mirroring_context_
->mirrored_left_coord(x
, right_x
), y
,
442 right_x
- x
, GetContentLineHeight()));
443 render_text
->Draw(canvas
);
447 scoped_ptr
<gfx::RenderText
> OmniboxResultView::CreateRenderText(
448 const base::string16
& text
) const {
449 scoped_ptr
<gfx::RenderText
> render_text(gfx::RenderText::CreateInstance());
450 render_text
->SetDisplayRect(gfx::Rect(gfx::Size(INT_MAX
, 0)));
451 render_text
->SetCursorEnabled(false);
452 render_text
->SetElideBehavior(gfx::ELIDE_TAIL
);
453 render_text
->SetFontList(font_list_
);
454 render_text
->SetText(text
);
455 return render_text
.Pass();
458 scoped_ptr
<gfx::RenderText
> OmniboxResultView::CreateClassifiedRenderText(
459 const base::string16
& text
,
460 const ACMatchClassifications
& classifications
,
461 bool force_dim
) const {
462 scoped_ptr
<gfx::RenderText
> render_text(CreateRenderText(text
));
463 const size_t text_length
= render_text
->text().length();
464 for (size_t i
= 0; i
< classifications
.size(); ++i
) {
465 const size_t text_start
= classifications
[i
].offset
;
466 if (text_start
>= text_length
)
469 const size_t text_end
= (i
< (classifications
.size() - 1)) ?
470 std::min(classifications
[i
+ 1].offset
, text_length
) :
472 const gfx::Range
current_range(text_start
, text_end
);
474 // Calculate style-related data.
475 if (classifications
[i
].style
& ACMatchClassification::MATCH
)
476 render_text
->ApplyStyle(gfx::BOLD
, true, current_range
);
478 ColorKind color_kind
= TEXT
;
479 if (classifications
[i
].style
& ACMatchClassification::URL
) {
481 // Consider logical string for domain "ABC.com×™/hello" where ABC are
482 // Hebrew (RTL) characters. This string should ideally show as
483 // "CBA.com/hello". If we do not force LTR on URL, it will appear as
485 // With IDN and RTL TLDs, it might be okay to allow RTL rendering of URLs,
486 // but it still has some pitfalls like :
487 // ABC.COM/abc-pqr/xyz/FGH will appear as HGF/abc-pqr/xyz/MOC.CBA which
488 // really confuses the path hierarchy of the URL.
489 // Also, if the URL supports https, the appearance will change into LTR
491 // In conclusion, LTR rendering of URL is probably the safest bet.
492 render_text
->SetDirectionalityMode(gfx::DIRECTIONALITY_FORCE_LTR
);
493 } else if (force_dim
||
494 (classifications
[i
].style
& ACMatchClassification::DIM
)) {
495 color_kind
= DIMMED_TEXT
;
497 render_text
->ApplyColor(GetColor(GetState(), color_kind
), current_range
);
499 return render_text
.Pass();
502 int OmniboxResultView::GetMatchContentsWidth() const {
503 InitContentsRenderTextIfNecessary();
504 contents_rendertext_
->SetDisplayRect(gfx::Rect(gfx::Size(INT_MAX
, 0)));
505 return contents_rendertext_
->GetContentWidth();
508 void OmniboxResultView::SetAnswerImage(const gfx::ImageSkia
& image
) {
509 answer_image_
= image
;
513 // TODO(skanuj): This is probably identical across all OmniboxResultView rows in
514 // the omnibox dropdown. Consider sharing the result.
515 int OmniboxResultView::GetDisplayOffset(
516 const AutocompleteMatch
& match
,
518 bool is_match_contents_rtl
) const {
519 if (match
.type
!= AutocompleteMatchType::SEARCH_SUGGEST_TAIL
)
522 const base::string16
& input_text
=
523 base::UTF8ToUTF16(match
.GetAdditionalInfo(kACMatchPropertyInputText
));
524 int contents_start_index
= 0;
525 base::StringToInt(match
.GetAdditionalInfo(kACMatchPropertyContentsStartIndex
),
526 &contents_start_index
);
528 scoped_ptr
<gfx::RenderText
> input_render_text(CreateRenderText(input_text
));
529 const gfx::Range
& glyph_bounds
=
530 input_render_text
->GetGlyphBounds(contents_start_index
);
531 const int start_padding
= is_match_contents_rtl
?
532 std::max(glyph_bounds
.start(), glyph_bounds
.end()) :
533 std::min(glyph_bounds
.start(), glyph_bounds
.end());
536 (input_render_text
->GetContentWidth() - start_padding
) : start_padding
;
540 int OmniboxResultView::default_icon_size_
= 0;
542 const char* OmniboxResultView::GetClassName() const {
543 return "OmniboxResultView";
546 gfx::ImageSkia
OmniboxResultView::GetIcon() const {
547 const gfx::Image image
= model_
->GetIconIfExtensionMatch(model_index_
);
548 if (!image
.IsEmpty())
549 return image
.AsImageSkia();
551 int icon
= model_
->IsStarredMatch(match_
) ?
552 IDR_OMNIBOX_STAR
: AutocompleteMatch::TypeToIcon(match_
.type
);
553 if (GetState() == SELECTED
&&
554 !ui::MaterialDesignController::IsModeMaterial()) {
556 case IDR_OMNIBOX_CALCULATOR
:
557 icon
= IDR_OMNIBOX_CALCULATOR_SELECTED
;
559 case IDR_OMNIBOX_EXTENSION_APP
:
560 icon
= IDR_OMNIBOX_EXTENSION_APP_SELECTED
;
562 case IDR_OMNIBOX_HTTP
:
563 icon
= IDR_OMNIBOX_HTTP_SELECTED
;
565 case IDR_OMNIBOX_SEARCH
:
566 icon
= IDR_OMNIBOX_SEARCH_SELECTED
;
568 case IDR_OMNIBOX_STAR
:
569 icon
= IDR_OMNIBOX_STAR_SELECTED
;
576 return *(location_bar_view_
->GetThemeProvider()->GetImageSkiaNamed(icon
));
579 const gfx::ImageSkia
* OmniboxResultView::GetKeywordIcon() const {
580 // NOTE: If we ever begin returning icons of varying size, then callers need
581 // to ensure that |keyword_icon_| is resized each time its image is reset.
582 int icon
= IDR_OMNIBOX_TTS
;
583 if (GetState() == SELECTED
&& !ui::MaterialDesignController::IsModeMaterial())
584 icon
= IDR_OMNIBOX_TTS_SELECTED
;
586 return location_bar_view_
->GetThemeProvider()->GetImageSkiaNamed(icon
);
589 bool OmniboxResultView::ShowOnlyKeywordMatch() const {
590 return match_
.associated_keyword
&&
591 (keyword_icon_
->x() <= icon_bounds_
.right());
594 void OmniboxResultView::ResetRenderTexts() const {
595 contents_rendertext_
.reset();
596 description_rendertext_
.reset();
597 separator_rendertext_
.reset();
598 keyword_contents_rendertext_
.reset();
599 keyword_description_rendertext_
.reset();
602 void OmniboxResultView::InitContentsRenderTextIfNecessary() const {
603 if (!contents_rendertext_
) {
604 contents_rendertext_
.reset(
605 CreateClassifiedRenderText(
606 match_
.contents
, match_
.contents_class
, false).release());
610 void OmniboxResultView::Layout() {
611 const gfx::ImageSkia icon
= GetIcon();
612 // TODO(jonross): Currently |location_bar_view_| provides the correct
613 // ThemeProvider, as it is loaded on the BrowserFrame widget. The root widget
614 // for OmniboxResultView is AutocompletePopupWidget, which is not loading the
615 // theme. We should update the omnibox code to also track its own
616 // ThemeProvider in order to reduce dependancy on LocationBarView.
617 ui::ThemeProvider
* theme_provider
= location_bar_view_
->GetThemeProvider();
618 // |theme_provider| can be null when animations are running during shutdown,
619 // after OmniboxResultView has been removed from the tree of Views.
622 const int horizontal_padding
= theme_provider
->GetDisplayProperty(
623 ThemeProperties::PROPERTY_LOCATION_BAR_HORIZONTAL_PADDING
);
624 const int trailing_padding
= theme_provider
->GetDisplayProperty(
625 ThemeProperties::PROPERTY_ICON_LABEL_VIEW_TRAILING_PADDING
);
627 const int start_x
= StartMargin() + horizontal_padding
;
628 const int end_x
= width() - EndMargin() - horizontal_padding
;
630 icon_bounds_
.SetRect(
631 start_x
+ ((icon
.width() == default_icon_size_
) ? 0 : trailing_padding
),
632 (GetContentLineHeight() - icon
.height()) / 2,
633 icon
.width(), icon
.height());
635 const int text_x
= start_x
+ default_icon_size_
+ horizontal_padding
;
636 int text_width
= end_x
- text_x
;
638 if (match_
.associated_keyword
.get()) {
639 const int max_kw_x
= end_x
- keyword_icon_
->width();
640 const int kw_x
= animation_
->CurrentValueBetween(max_kw_x
, start_x
);
641 const int kw_text_x
= kw_x
+ keyword_icon_
->width() + horizontal_padding
;
643 text_width
= kw_x
- text_x
- horizontal_padding
;
644 keyword_text_bounds_
.SetRect(
645 kw_text_x
, 0, std::max(end_x
- kw_text_x
, 0), height());
646 keyword_icon_
->SetPosition(
647 gfx::Point(kw_x
, (height() - keyword_icon_
->height()) / 2));
650 text_bounds_
.SetRect(text_x
, 0, std::max(text_width
, 0), height());
653 void OmniboxResultView::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
654 animation_
->SetSlideDuration((width() - StartMargin() - EndMargin()) / 4);
657 void OmniboxResultView::OnPaint(gfx::Canvas
* canvas
) {
658 const ResultViewState state
= GetState();
660 canvas
->DrawColor(GetColor(state
, BACKGROUND
));
662 // NOTE: While animating the keyword match, both matches may be visible.
664 if (!ShowOnlyKeywordMatch()) {
665 canvas
->DrawImageInt(GetIcon(), GetMirroredXForRect(icon_bounds_
),
667 int x
= GetMirroredXForRect(text_bounds_
);
668 mirroring_context_
->Initialize(x
, text_bounds_
.width());
669 InitContentsRenderTextIfNecessary();
671 if (!description_rendertext_
) {
673 contents_rendertext_
=
674 CreateAnswerLine(match_
.answer
->first_line(), font_list_
);
675 description_rendertext_
= CreateAnswerLine(
676 match_
.answer
->second_line(),
677 ui::ResourceBundle::GetSharedInstance().GetFontList(
678 ui::ResourceBundle::LargeFont
));
679 } else if (!match_
.description
.empty()) {
680 description_rendertext_
= CreateClassifiedRenderText(
681 match_
.description
, match_
.description_class
, true);
684 PaintMatch(match_
, contents_rendertext_
.get(),
685 description_rendertext_
.get(), canvas
, x
);
688 AutocompleteMatch
* keyword_match
= match_
.associated_keyword
.get();
690 int x
= GetMirroredXForRect(keyword_text_bounds_
);
691 mirroring_context_
->Initialize(x
, keyword_text_bounds_
.width());
692 if (!keyword_contents_rendertext_
) {
693 keyword_contents_rendertext_
.reset(
694 CreateClassifiedRenderText(keyword_match
->contents
,
695 keyword_match
->contents_class
,
698 if (!keyword_description_rendertext_
&&
699 !keyword_match
->description
.empty()) {
700 keyword_description_rendertext_
.reset(
701 CreateClassifiedRenderText(keyword_match
->description
,
702 keyword_match
->description_class
,
705 PaintMatch(*keyword_match
, keyword_contents_rendertext_
.get(),
706 keyword_description_rendertext_
.get(), canvas
, x
);
710 void OmniboxResultView::AnimationProgressed(const gfx::Animation
* animation
) {
715 int OmniboxResultView::GetAnswerLineHeight() const {
716 // GetTextStyle(1) is the largest font used and so defines the boundary that
717 // all the other answer styles fit within.
718 return ui::ResourceBundle::GetSharedInstance()
719 .GetFontList(GetTextStyle(1).font
)
723 int OmniboxResultView::GetContentLineHeight() const {
724 ui::ThemeProvider
* theme_provider
= location_bar_view_
->GetThemeProvider();
725 const int min_icon_vertical_padding
= theme_provider
->GetDisplayProperty(
726 ThemeProperties::PROPERTY_OMNIBOX_DROPDOWN_MIN_ICON_VERTICAL_PADDING
);
727 const int min_text_vertical_padding
= theme_provider
->GetDisplayProperty(
728 ThemeProperties::PROPERTY_OMNIBOX_DROPDOWN_MIN_TEXT_VERTICAL_PADDING
);
730 return std::max(default_icon_size_
+ (min_icon_vertical_padding
* 2),
731 GetTextHeight() + (min_text_vertical_padding
* 2));
734 scoped_ptr
<gfx::RenderText
> OmniboxResultView::CreateAnswerLine(
735 const SuggestionAnswer::ImageLine
& line
,
736 gfx::FontList font_list
) {
737 scoped_ptr
<gfx::RenderText
> destination
= CreateRenderText(base::string16());
738 destination
->SetFontList(font_list
);
740 for (const SuggestionAnswer::TextField
& text_field
: line
.text_fields())
741 AppendAnswerText(destination
.get(), text_field
.text(), text_field
.type());
742 const base::char16
space(' ');
743 const auto* text_field
= line
.additional_text();
745 AppendAnswerText(destination
.get(), space
+ text_field
->text(),
748 text_field
= line
.status_text();
750 AppendAnswerText(destination
.get(), space
+ text_field
->text(),
753 return destination
.Pass();
756 void OmniboxResultView::AppendAnswerText(gfx::RenderText
* destination
,
757 const base::string16
& text
,
759 // TODO(dschuyler): make this better. Right now this only supports unnested
760 // bold tags. In the future we'll need to flag unexpected tags while adding
761 // support for b, i, u, sub, and sup. We'll also need to support HTML
762 // entities (< for '<', etc.).
763 const base::string16 begin_tag
= base::ASCIIToUTF16("<b>");
764 const base::string16 end_tag
= base::ASCIIToUTF16("</b>");
767 size_t end
= text
.find(begin_tag
, begin
);
768 if (end
== base::string16::npos
) {
769 AppendAnswerTextHelper(destination
, text
.substr(begin
), text_type
, false);
772 AppendAnswerTextHelper(destination
, text
.substr(begin
, end
- begin
),
774 begin
= end
+ begin_tag
.length();
775 end
= text
.find(end_tag
, begin
);
776 if (end
== base::string16::npos
)
778 AppendAnswerTextHelper(destination
, text
.substr(begin
, end
- begin
),
780 begin
= end
+ end_tag
.length();
784 void OmniboxResultView::AppendAnswerTextHelper(gfx::RenderText
* destination
,
785 const base::string16
& text
,
790 int offset
= destination
->text().length();
791 gfx::Range
range(offset
, offset
+ text
.length());
792 destination
->AppendText(text
);
793 const TextStyle
& text_style
= GetTextStyle(text_type
);
794 // TODO(dschuyler): follow up on the problem of different font sizes within
795 // one RenderText. Maybe with destination->SetFontList(...).
796 destination
->ApplyStyle(gfx::BOLD
, is_bold
, range
);
797 destination
->ApplyColor(
798 GetNativeTheme()->GetSystemColor(text_style
.colors
[GetState()]), range
);
799 destination
->ApplyBaselineStyle(text_style
.baseline
, range
);
802 int OmniboxResultView::StartMargin() const {
803 return ui::MaterialDesignController::IsModeMaterial() ?
804 model_
->start_margin() : 0;
807 int OmniboxResultView::EndMargin() const {
808 return ui::MaterialDesignController::IsModeMaterial() ?
809 model_
->end_margin() : 0;