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/ui/views/layout_constants.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 GetLayoutConstant(ICON_LABEL_VIEW_TRAILING_PADDING
);
359 x
= DrawRenderText(match
, separator_rendertext_
.get(), false, canvas
,
360 after_contents_x
, y
, separator_width_
);
363 DrawRenderText(match
, description
, false, canvas
, x
, y
,
364 description_max_width
);
368 int OmniboxResultView::DrawRenderText(
369 const AutocompleteMatch
& match
,
370 gfx::RenderText
* render_text
,
375 int max_width
) const {
376 DCHECK(!render_text
->text().empty());
378 const int remaining_width
= mirroring_context_
->remaining_width(x
);
379 int right_x
= x
+ max_width
;
381 // Infinite suggestions should appear with the leading ellipses vertically
384 (match
.type
== AutocompleteMatchType::SEARCH_SUGGEST_TAIL
)) {
385 // When the directionality of suggestion doesn't match the UI, we try to
386 // vertically stack the ellipsis by restricting the end edge (right_x).
387 const bool is_ui_rtl
= base::i18n::IsRTL();
388 const bool is_match_contents_rtl
=
389 (render_text
->GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT
);
391 GetDisplayOffset(match
, is_ui_rtl
, is_match_contents_rtl
);
393 scoped_ptr
<gfx::RenderText
> prefix_render_text(
394 CreateRenderText(base::UTF8ToUTF16(
395 match
.GetAdditionalInfo(kACMatchPropertyContentsPrefix
))));
396 const int prefix_width
= prefix_render_text
->GetContentWidth();
399 const int max_match_contents_width
= model_
->max_match_contents_width();
401 if (is_ui_rtl
!= is_match_contents_rtl
) {
402 // RTL infinite suggestions appear near the left edge in LTR UI, while LTR
403 // infinite suggestions appear near the right edge in RTL UI. This is
404 // against the natural horizontal alignment of the text. We reduce the
405 // width of the box for suggestion display, so that the suggestions appear
406 // in correct confines. This reduced width allows us to modify the text
407 // alignment (see below).
408 right_x
= x
+ std::min(remaining_width
- prefix_width
,
409 std::max(offset
, max_match_contents_width
));
411 // We explicitly set the horizontal alignment so that when LTR suggestions
412 // show in RTL UI (or vice versa), their ellipses appear stacked in a
414 render_text
->SetHorizontalAlignment(
415 is_match_contents_rtl
? gfx::ALIGN_RIGHT
: gfx::ALIGN_LEFT
);
417 // If the dropdown is wide enough, place the ellipsis at the position
418 // where the omitted text would have ended. Otherwise reduce the offset of
419 // the ellipsis such that the widest suggestion reaches the end of the
421 const int start_offset
= std::max(prefix_width
,
422 std::min(remaining_width
- max_match_contents_width
, offset
));
423 right_x
= x
+ std::min(remaining_width
, start_offset
+ max_width
);
425 prefix_x
= x
- prefix_width
;
427 prefix_render_text
->SetDirectionalityMode(is_match_contents_rtl
?
428 gfx::DIRECTIONALITY_FORCE_RTL
: gfx::DIRECTIONALITY_FORCE_LTR
);
429 prefix_render_text
->SetHorizontalAlignment(
430 is_match_contents_rtl
? gfx::ALIGN_RIGHT
: gfx::ALIGN_LEFT
);
431 prefix_render_text
->SetDisplayRect(
432 gfx::Rect(mirroring_context_
->mirrored_left_coord(
433 prefix_x
, prefix_x
+ prefix_width
),
434 y
, prefix_width
, GetContentLineHeight()));
435 prefix_render_text
->Draw(canvas
);
438 // Set the display rect to trigger eliding.
439 render_text
->SetDisplayRect(
440 gfx::Rect(mirroring_context_
->mirrored_left_coord(x
, right_x
), y
,
441 right_x
- x
, GetContentLineHeight()));
442 render_text
->Draw(canvas
);
446 scoped_ptr
<gfx::RenderText
> OmniboxResultView::CreateRenderText(
447 const base::string16
& text
) const {
448 scoped_ptr
<gfx::RenderText
> render_text(gfx::RenderText::CreateInstance());
449 render_text
->SetDisplayRect(gfx::Rect(gfx::Size(INT_MAX
, 0)));
450 render_text
->SetCursorEnabled(false);
451 render_text
->SetElideBehavior(gfx::ELIDE_TAIL
);
452 render_text
->SetFontList(font_list_
);
453 render_text
->SetText(text
);
454 return render_text
.Pass();
457 scoped_ptr
<gfx::RenderText
> OmniboxResultView::CreateClassifiedRenderText(
458 const base::string16
& text
,
459 const ACMatchClassifications
& classifications
,
460 bool force_dim
) const {
461 scoped_ptr
<gfx::RenderText
> render_text(CreateRenderText(text
));
462 const size_t text_length
= render_text
->text().length();
463 for (size_t i
= 0; i
< classifications
.size(); ++i
) {
464 const size_t text_start
= classifications
[i
].offset
;
465 if (text_start
>= text_length
)
468 const size_t text_end
= (i
< (classifications
.size() - 1)) ?
469 std::min(classifications
[i
+ 1].offset
, text_length
) :
471 const gfx::Range
current_range(text_start
, text_end
);
473 // Calculate style-related data.
474 if (classifications
[i
].style
& ACMatchClassification::MATCH
)
475 render_text
->ApplyStyle(gfx::BOLD
, true, current_range
);
477 ColorKind color_kind
= TEXT
;
478 if (classifications
[i
].style
& ACMatchClassification::URL
) {
480 // Consider logical string for domain "ABC.com×™/hello" where ABC are
481 // Hebrew (RTL) characters. This string should ideally show as
482 // "CBA.com/hello". If we do not force LTR on URL, it will appear as
484 // With IDN and RTL TLDs, it might be okay to allow RTL rendering of URLs,
485 // but it still has some pitfalls like :
486 // ABC.COM/abc-pqr/xyz/FGH will appear as HGF/abc-pqr/xyz/MOC.CBA which
487 // really confuses the path hierarchy of the URL.
488 // Also, if the URL supports https, the appearance will change into LTR
490 // In conclusion, LTR rendering of URL is probably the safest bet.
491 render_text
->SetDirectionalityMode(gfx::DIRECTIONALITY_FORCE_LTR
);
492 } else if (force_dim
||
493 (classifications
[i
].style
& ACMatchClassification::DIM
)) {
494 color_kind
= DIMMED_TEXT
;
496 render_text
->ApplyColor(GetColor(GetState(), color_kind
), current_range
);
498 return render_text
.Pass();
501 int OmniboxResultView::GetMatchContentsWidth() const {
502 InitContentsRenderTextIfNecessary();
503 contents_rendertext_
->SetDisplayRect(gfx::Rect(gfx::Size(INT_MAX
, 0)));
504 return contents_rendertext_
->GetContentWidth();
507 void OmniboxResultView::SetAnswerImage(const gfx::ImageSkia
& image
) {
508 answer_image_
= image
;
512 // TODO(skanuj): This is probably identical across all OmniboxResultView rows in
513 // the omnibox dropdown. Consider sharing the result.
514 int OmniboxResultView::GetDisplayOffset(
515 const AutocompleteMatch
& match
,
517 bool is_match_contents_rtl
) const {
518 if (match
.type
!= AutocompleteMatchType::SEARCH_SUGGEST_TAIL
)
521 const base::string16
& input_text
=
522 base::UTF8ToUTF16(match
.GetAdditionalInfo(kACMatchPropertyInputText
));
523 int contents_start_index
= 0;
524 base::StringToInt(match
.GetAdditionalInfo(kACMatchPropertyContentsStartIndex
),
525 &contents_start_index
);
527 scoped_ptr
<gfx::RenderText
> input_render_text(CreateRenderText(input_text
));
528 const gfx::Range
& glyph_bounds
=
529 input_render_text
->GetGlyphBounds(contents_start_index
);
530 const int start_padding
= is_match_contents_rtl
?
531 std::max(glyph_bounds
.start(), glyph_bounds
.end()) :
532 std::min(glyph_bounds
.start(), glyph_bounds
.end());
535 (input_render_text
->GetContentWidth() - start_padding
) : start_padding
;
539 int OmniboxResultView::default_icon_size_
= 0;
541 const char* OmniboxResultView::GetClassName() const {
542 return "OmniboxResultView";
545 gfx::ImageSkia
OmniboxResultView::GetIcon() const {
546 const gfx::Image image
= model_
->GetIconIfExtensionMatch(model_index_
);
547 if (!image
.IsEmpty())
548 return image
.AsImageSkia();
550 int icon
= model_
->IsStarredMatch(match_
) ?
551 IDR_OMNIBOX_STAR
: AutocompleteMatch::TypeToIcon(match_
.type
);
552 if (GetState() == SELECTED
&&
553 !ui::MaterialDesignController::IsModeMaterial()) {
555 case IDR_OMNIBOX_CALCULATOR
:
556 icon
= IDR_OMNIBOX_CALCULATOR_SELECTED
;
558 case IDR_OMNIBOX_EXTENSION_APP
:
559 icon
= IDR_OMNIBOX_EXTENSION_APP_SELECTED
;
561 case IDR_OMNIBOX_HTTP
:
562 icon
= IDR_OMNIBOX_HTTP_SELECTED
;
564 case IDR_OMNIBOX_SEARCH
:
565 icon
= IDR_OMNIBOX_SEARCH_SELECTED
;
567 case IDR_OMNIBOX_STAR
:
568 icon
= IDR_OMNIBOX_STAR_SELECTED
;
575 return *(location_bar_view_
->GetThemeProvider()->GetImageSkiaNamed(icon
));
578 const gfx::ImageSkia
* OmniboxResultView::GetKeywordIcon() const {
579 // NOTE: If we ever begin returning icons of varying size, then callers need
580 // to ensure that |keyword_icon_| is resized each time its image is reset.
581 int icon
= IDR_OMNIBOX_TTS
;
582 if (GetState() == SELECTED
&& !ui::MaterialDesignController::IsModeMaterial())
583 icon
= IDR_OMNIBOX_TTS_SELECTED
;
585 return location_bar_view_
->GetThemeProvider()->GetImageSkiaNamed(icon
);
588 bool OmniboxResultView::ShowOnlyKeywordMatch() const {
589 return match_
.associated_keyword
&&
590 (keyword_icon_
->x() <= icon_bounds_
.right());
593 void OmniboxResultView::ResetRenderTexts() const {
594 contents_rendertext_
.reset();
595 description_rendertext_
.reset();
596 separator_rendertext_
.reset();
597 keyword_contents_rendertext_
.reset();
598 keyword_description_rendertext_
.reset();
601 void OmniboxResultView::InitContentsRenderTextIfNecessary() const {
602 if (!contents_rendertext_
) {
603 contents_rendertext_
.reset(
604 CreateClassifiedRenderText(
605 match_
.contents
, match_
.contents_class
, false).release());
609 void OmniboxResultView::Layout() {
610 const int horizontal_padding
=
611 GetLayoutConstant(LOCATION_BAR_HORIZONTAL_PADDING
);
612 const int start_x
= StartMargin() + horizontal_padding
;
613 const int end_x
= width() - EndMargin() - horizontal_padding
;
615 const gfx::ImageSkia icon
= GetIcon();
616 icon_bounds_
.SetRect(
617 start_x
+ ((icon
.width() == default_icon_size_
) ?
618 0 : GetLayoutConstant(ICON_LABEL_VIEW_TRAILING_PADDING
)),
619 (GetContentLineHeight() - icon
.height()) / 2,
620 icon
.width(), icon
.height());
622 const int text_x
= start_x
+ default_icon_size_
+ horizontal_padding
;
623 int text_width
= end_x
- text_x
;
625 if (match_
.associated_keyword
.get()) {
626 const int max_kw_x
= end_x
- keyword_icon_
->width();
627 const int kw_x
= animation_
->CurrentValueBetween(max_kw_x
, start_x
);
628 const int kw_text_x
= kw_x
+ keyword_icon_
->width() + horizontal_padding
;
630 text_width
= kw_x
- text_x
- horizontal_padding
;
631 keyword_text_bounds_
.SetRect(
632 kw_text_x
, 0, std::max(end_x
- kw_text_x
, 0), height());
633 keyword_icon_
->SetPosition(
634 gfx::Point(kw_x
, (height() - keyword_icon_
->height()) / 2));
637 text_bounds_
.SetRect(text_x
, 0, std::max(text_width
, 0), height());
640 void OmniboxResultView::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
641 animation_
->SetSlideDuration((width() - StartMargin() - EndMargin()) / 4);
644 void OmniboxResultView::OnPaint(gfx::Canvas
* canvas
) {
645 const ResultViewState state
= GetState();
647 canvas
->DrawColor(GetColor(state
, BACKGROUND
));
649 // NOTE: While animating the keyword match, both matches may be visible.
651 if (!ShowOnlyKeywordMatch()) {
652 canvas
->DrawImageInt(GetIcon(), GetMirroredXForRect(icon_bounds_
),
654 int x
= GetMirroredXForRect(text_bounds_
);
655 mirroring_context_
->Initialize(x
, text_bounds_
.width());
656 InitContentsRenderTextIfNecessary();
658 if (!description_rendertext_
) {
660 contents_rendertext_
=
661 CreateAnswerLine(match_
.answer
->first_line(), font_list_
);
662 description_rendertext_
= CreateAnswerLine(
663 match_
.answer
->second_line(),
664 ui::ResourceBundle::GetSharedInstance().GetFontList(
665 ui::ResourceBundle::LargeFont
));
666 } else if (!match_
.description
.empty()) {
667 description_rendertext_
= CreateClassifiedRenderText(
668 match_
.description
, match_
.description_class
, true);
671 PaintMatch(match_
, contents_rendertext_
.get(),
672 description_rendertext_
.get(), canvas
, x
);
675 AutocompleteMatch
* keyword_match
= match_
.associated_keyword
.get();
677 int x
= GetMirroredXForRect(keyword_text_bounds_
);
678 mirroring_context_
->Initialize(x
, keyword_text_bounds_
.width());
679 if (!keyword_contents_rendertext_
) {
680 keyword_contents_rendertext_
.reset(
681 CreateClassifiedRenderText(keyword_match
->contents
,
682 keyword_match
->contents_class
,
685 if (!keyword_description_rendertext_
&&
686 !keyword_match
->description
.empty()) {
687 keyword_description_rendertext_
.reset(
688 CreateClassifiedRenderText(keyword_match
->description
,
689 keyword_match
->description_class
,
692 PaintMatch(*keyword_match
, keyword_contents_rendertext_
.get(),
693 keyword_description_rendertext_
.get(), canvas
, x
);
697 void OmniboxResultView::AnimationProgressed(const gfx::Animation
* animation
) {
702 int OmniboxResultView::GetAnswerLineHeight() const {
703 // GetTextStyle(1) is the largest font used and so defines the boundary that
704 // all the other answer styles fit within.
705 return ui::ResourceBundle::GetSharedInstance()
706 .GetFontList(GetTextStyle(1).font
)
710 int OmniboxResultView::GetContentLineHeight() const {
712 default_icon_size_
+ GetLayoutInsets(OMNIBOX_DROPDOWN_ICON
).height(),
713 GetTextHeight() + GetLayoutInsets(OMNIBOX_DROPDOWN_TEXT
).height());
716 scoped_ptr
<gfx::RenderText
> OmniboxResultView::CreateAnswerLine(
717 const SuggestionAnswer::ImageLine
& line
,
718 gfx::FontList font_list
) {
719 scoped_ptr
<gfx::RenderText
> destination
= CreateRenderText(base::string16());
720 destination
->SetFontList(font_list
);
722 for (const SuggestionAnswer::TextField
& text_field
: line
.text_fields())
723 AppendAnswerText(destination
.get(), text_field
.text(), text_field
.type());
724 const base::char16
space(' ');
725 const auto* text_field
= line
.additional_text();
727 AppendAnswerText(destination
.get(), space
+ text_field
->text(),
730 text_field
= line
.status_text();
732 AppendAnswerText(destination
.get(), space
+ text_field
->text(),
735 return destination
.Pass();
738 void OmniboxResultView::AppendAnswerText(gfx::RenderText
* destination
,
739 const base::string16
& text
,
741 // TODO(dschuyler): make this better. Right now this only supports unnested
742 // bold tags. In the future we'll need to flag unexpected tags while adding
743 // support for b, i, u, sub, and sup. We'll also need to support HTML
744 // entities (< for '<', etc.).
745 const base::string16 begin_tag
= base::ASCIIToUTF16("<b>");
746 const base::string16 end_tag
= base::ASCIIToUTF16("</b>");
749 size_t end
= text
.find(begin_tag
, begin
);
750 if (end
== base::string16::npos
) {
751 AppendAnswerTextHelper(destination
, text
.substr(begin
), text_type
, false);
754 AppendAnswerTextHelper(destination
, text
.substr(begin
, end
- begin
),
756 begin
= end
+ begin_tag
.length();
757 end
= text
.find(end_tag
, begin
);
758 if (end
== base::string16::npos
)
760 AppendAnswerTextHelper(destination
, text
.substr(begin
, end
- begin
),
762 begin
= end
+ end_tag
.length();
766 void OmniboxResultView::AppendAnswerTextHelper(gfx::RenderText
* destination
,
767 const base::string16
& text
,
772 int offset
= destination
->text().length();
773 gfx::Range
range(offset
, offset
+ text
.length());
774 destination
->AppendText(text
);
775 const TextStyle
& text_style
= GetTextStyle(text_type
);
776 // TODO(dschuyler): follow up on the problem of different font sizes within
777 // one RenderText. Maybe with destination->SetFontList(...).
778 destination
->ApplyStyle(gfx::BOLD
, is_bold
, range
);
779 destination
->ApplyColor(
780 GetNativeTheme()->GetSystemColor(text_style
.colors
[GetState()]), range
);
781 destination
->ApplyBaselineStyle(text_style
.baseline
, range
);
784 int OmniboxResultView::StartMargin() const {
785 return ui::MaterialDesignController::IsModeMaterial() ?
786 model_
->start_margin() : 0;
789 int OmniboxResultView::EndMargin() const {
790 return ui::MaterialDesignController::IsModeMaterial() ?
791 model_
->end_margin() : 0;