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/memory/scoped_vector.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_util.h"
20 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
21 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
22 #include "chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h"
23 #include "chrome/grit/generated_resources.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/resource_bundle.h"
29 #include "ui/base/theme_provider.h"
30 #include "ui/gfx/canvas.h"
31 #include "ui/gfx/color_utils.h"
32 #include "ui/gfx/image/image.h"
33 #include "ui/gfx/range/range.h"
34 #include "ui/gfx/render_text.h"
35 #include "ui/gfx/text_utils.h"
37 using ui::NativeTheme
;
41 // The minimum distance between the top and bottom of the icon and the
42 // top or bottom of the row.
43 const int kMinimumIconVerticalPadding
= 2;
45 // A mapping from OmniboxResultView's ResultViewState/ColorKind types to
46 // NativeTheme colors.
47 struct TranslationTable
{
48 ui::NativeTheme::ColorId id
;
49 OmniboxResultView::ResultViewState state
;
50 OmniboxResultView::ColorKind kind
;
51 } static const kTranslationTable
[] = {
52 { NativeTheme::kColorId_ResultsTableNormalBackground
,
53 OmniboxResultView::NORMAL
, OmniboxResultView::BACKGROUND
},
54 { NativeTheme::kColorId_ResultsTableHoveredBackground
,
55 OmniboxResultView::HOVERED
, OmniboxResultView::BACKGROUND
},
56 { NativeTheme::kColorId_ResultsTableSelectedBackground
,
57 OmniboxResultView::SELECTED
, OmniboxResultView::BACKGROUND
},
58 { NativeTheme::kColorId_ResultsTableNormalText
,
59 OmniboxResultView::NORMAL
, OmniboxResultView::TEXT
},
60 { NativeTheme::kColorId_ResultsTableHoveredText
,
61 OmniboxResultView::HOVERED
, OmniboxResultView::TEXT
},
62 { NativeTheme::kColorId_ResultsTableSelectedText
,
63 OmniboxResultView::SELECTED
, OmniboxResultView::TEXT
},
64 { NativeTheme::kColorId_ResultsTableNormalDimmedText
,
65 OmniboxResultView::NORMAL
, OmniboxResultView::DIMMED_TEXT
},
66 { NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
67 OmniboxResultView::HOVERED
, OmniboxResultView::DIMMED_TEXT
},
68 { NativeTheme::kColorId_ResultsTableSelectedDimmedText
,
69 OmniboxResultView::SELECTED
, OmniboxResultView::DIMMED_TEXT
},
70 { NativeTheme::kColorId_ResultsTableNormalUrl
,
71 OmniboxResultView::NORMAL
, OmniboxResultView::URL
},
72 { NativeTheme::kColorId_ResultsTableHoveredUrl
,
73 OmniboxResultView::HOVERED
, OmniboxResultView::URL
},
74 { NativeTheme::kColorId_ResultsTableSelectedUrl
,
75 OmniboxResultView::SELECTED
, OmniboxResultView::URL
},
76 { NativeTheme::kColorId_ResultsTableNormalDivider
,
77 OmniboxResultView::NORMAL
, OmniboxResultView::DIVIDER
},
78 { NativeTheme::kColorId_ResultsTableHoveredDivider
,
79 OmniboxResultView::HOVERED
, OmniboxResultView::DIVIDER
},
80 { NativeTheme::kColorId_ResultsTableSelectedDivider
,
81 OmniboxResultView::SELECTED
, OmniboxResultView::DIVIDER
},
85 ui::ResourceBundle::FontStyle font
;
86 ui::NativeTheme::ColorId colors
[OmniboxResultView::NUM_STATES
];
87 gfx::BaselineStyle baseline
;
88 } const kTextStyles
[] = {
90 {ui::ResourceBundle::LargeFont
,
91 {NativeTheme::kColorId_ResultsTableNormalText
,
92 NativeTheme::kColorId_ResultsTableHoveredText
,
93 NativeTheme::kColorId_ResultsTableSelectedText
},
94 gfx::NORMAL_BASELINE
},
96 {ui::ResourceBundle::LargeFont
,
97 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
98 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
99 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
100 gfx::NORMAL_BASELINE
},
101 // 3 TOP_ALIGNED_TEXT
102 {ui::ResourceBundle::LargeFont
,
103 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
104 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
105 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
107 // 4 DESCRIPTION_TEXT
108 {ui::ResourceBundle::BaseFont
,
109 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
110 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
111 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
112 gfx::NORMAL_BASELINE
},
113 // 5 DESCRIPTION_TEXT_NEGATIVE
114 {ui::ResourceBundle::LargeFont
,
115 {NativeTheme::kColorId_ResultsTableNegativeText
,
116 NativeTheme::kColorId_ResultsTableNegativeHoveredText
,
117 NativeTheme::kColorId_ResultsTableNegativeSelectedText
},
119 // 6 DESCRIPTION_TEXT_POSITIVE
120 {ui::ResourceBundle::LargeFont
,
121 {NativeTheme::kColorId_ResultsTablePositiveText
,
122 NativeTheme::kColorId_ResultsTablePositiveHoveredText
,
123 NativeTheme::kColorId_ResultsTablePositiveSelectedText
},
126 {ui::ResourceBundle::BaseFont
,
127 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
128 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
129 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
132 {ui::ResourceBundle::BaseFont
,
133 {NativeTheme::kColorId_ResultsTableNormalText
,
134 NativeTheme::kColorId_ResultsTableHoveredText
,
135 NativeTheme::kColorId_ResultsTableSelectedText
},
136 gfx::NORMAL_BASELINE
},
137 // 9 SUGGESTION_TEXT_POSITIVE
138 {ui::ResourceBundle::BaseFont
,
139 {NativeTheme::kColorId_ResultsTablePositiveText
,
140 NativeTheme::kColorId_ResultsTablePositiveHoveredText
,
141 NativeTheme::kColorId_ResultsTablePositiveSelectedText
},
142 gfx::NORMAL_BASELINE
},
143 // 10 SUGGESTION_TEXT_NEGATIVE
144 {ui::ResourceBundle::BaseFont
,
145 {NativeTheme::kColorId_ResultsTableNegativeText
,
146 NativeTheme::kColorId_ResultsTableNegativeHoveredText
,
147 NativeTheme::kColorId_ResultsTableNegativeSelectedText
},
148 gfx::NORMAL_BASELINE
},
149 // 11 SUGGESTION_LINK_COLOR
150 {ui::ResourceBundle::BaseFont
,
151 {NativeTheme::kColorId_ResultsTableNormalUrl
,
152 NativeTheme::kColorId_ResultsTableHoveredUrl
,
153 NativeTheme::kColorId_ResultsTableSelectedUrl
},
154 gfx::NORMAL_BASELINE
},
156 {ui::ResourceBundle::LargeFont
,
157 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
158 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
159 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
161 // 13 PERSONALIZED_SUGGESTION_TEXT
162 {ui::ResourceBundle::BaseFont
,
163 {NativeTheme::kColorId_ResultsTableNormalText
,
164 NativeTheme::kColorId_ResultsTableHoveredText
,
165 NativeTheme::kColorId_ResultsTableSelectedText
},
166 gfx::NORMAL_BASELINE
},
169 const TextStyle
& GetTextStyle(int type
) {
170 if (type
< 1 || static_cast<size_t>(type
) > arraysize(kTextStyles
))
172 // Subtract one because the types are one based (not zero based).
173 return kTextStyles
[type
- 1];
178 ////////////////////////////////////////////////////////////////////////////////
179 // OmniboxResultView, public:
181 // This class is a utility class for calculations affected by whether the result
182 // view is horizontally mirrored. The drawing functions can be written as if
183 // all drawing occurs left-to-right, and then use this class to get the actual
184 // coordinates to begin drawing onscreen.
185 class OmniboxResultView::MirroringContext
{
187 MirroringContext() : center_(0), right_(0) {}
189 // Tells the mirroring context to use the provided range as the physical
190 // bounds of the drawing region. When coordinate mirroring is needed, the
191 // mirror point will be the center of this range.
192 void Initialize(int x
, int width
) {
193 center_
= x
+ width
/ 2;
197 // Given a logical range within the drawing region, returns the coordinate of
198 // the possibly-mirrored "left" side. (This functions exactly like
199 // View::MirroredLeftPointForRect().)
200 int mirrored_left_coord(int left
, int right
) const {
201 return base::i18n::IsRTL() ? (center_
+ (center_
- right
)) : left
;
204 // Given a logical coordinate within the drawing region, returns the remaining
206 int remaining_width(int x
) const {
214 DISALLOW_COPY_AND_ASSIGN(MirroringContext
);
217 OmniboxResultView::OmniboxResultView(OmniboxPopupContentsView
* model
,
219 LocationBarView
* location_bar_view
,
220 const gfx::FontList
& font_list
)
221 : edge_item_padding_(LocationBarView::kItemPadding
),
222 item_padding_(LocationBarView::kItemPadding
),
224 model_index_(model_index
),
225 location_bar_view_(location_bar_view
),
226 font_list_(font_list
),
228 std::max(font_list
.GetHeight(),
229 font_list
.DeriveWithStyle(gfx::Font::BOLD
).GetHeight())),
230 mirroring_context_(new MirroringContext()),
231 keyword_icon_(new views::ImageView()),
232 animation_(new gfx::SlideAnimation(this)) {
233 CHECK_GE(model_index
, 0);
234 if (default_icon_size_
== 0) {
236 location_bar_view_
->GetThemeProvider()->GetImageSkiaNamed(
237 AutocompleteMatch::TypeToIcon(
238 AutocompleteMatchType::URL_WHAT_YOU_TYPED
))->width();
240 keyword_icon_
->set_owned_by_client();
241 keyword_icon_
->EnableCanvasFlippingForRTLUI(true);
242 keyword_icon_
->SetImage(GetKeywordIcon());
243 keyword_icon_
->SizeToPreferredSize();
246 OmniboxResultView::~OmniboxResultView() {
249 SkColor
OmniboxResultView::GetColor(
250 ResultViewState state
,
251 ColorKind kind
) const {
252 for (size_t i
= 0; i
< arraysize(kTranslationTable
); ++i
) {
253 if (kTranslationTable
[i
].state
== state
&&
254 kTranslationTable
[i
].kind
== kind
) {
255 return GetNativeTheme()->GetSystemColor(kTranslationTable
[i
].id
);
263 void OmniboxResultView::SetMatch(const AutocompleteMatch
& match
) {
265 match_
.PossiblySwapContentsAndDescriptionForDisplay();
268 answer_image_
= gfx::ImageSkia();
270 AutocompleteMatch
* associated_keyword_match
= match_
.associated_keyword
.get();
271 if (associated_keyword_match
) {
272 keyword_icon_
->SetImage(GetKeywordIcon());
273 if (!keyword_icon_
->parent())
274 AddChildView(keyword_icon_
.get());
275 } else if (keyword_icon_
->parent()) {
276 RemoveChildView(keyword_icon_
.get());
282 void OmniboxResultView::ShowKeyword(bool show_keyword
) {
289 void OmniboxResultView::Invalidate() {
290 keyword_icon_
->SetImage(GetKeywordIcon());
291 // While the text in the RenderTexts may not have changed, the styling
292 // (color/bold) may need to change. So we reset them to cause them to be
293 // recomputed in OnPaint().
298 gfx::Size
OmniboxResultView::GetPreferredSize() const {
300 return gfx::Size(0, GetContentLineHeight());
301 // An answer implies a match and a description in a large font.
302 return gfx::Size(0, GetContentLineHeight() + GetAnswerLineHeight());
305 ////////////////////////////////////////////////////////////////////////////////
306 // OmniboxResultView, protected:
308 OmniboxResultView::ResultViewState
OmniboxResultView::GetState() const {
309 if (model_
->IsSelectedIndex(model_index_
))
311 return model_
->IsHoveredIndex(model_index_
) ? HOVERED
: NORMAL
;
314 int OmniboxResultView::GetTextHeight() const {
318 void OmniboxResultView::PaintMatch(const AutocompleteMatch
& match
,
319 gfx::RenderText
* contents
,
320 gfx::RenderText
* description
,
323 int y
= text_bounds_
.y();
325 if (!separator_rendertext_
) {
326 const base::string16
& separator
=
327 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR
);
328 separator_rendertext_
.reset(CreateRenderText(separator
).release());
329 separator_rendertext_
->SetColor(GetColor(GetState(), DIMMED_TEXT
));
330 separator_width_
= separator_rendertext_
->GetContentWidth();
333 contents
->SetDisplayRect(gfx::Rect(gfx::Size(INT_MAX
, 0)));
335 description
->SetDisplayRect(gfx::Rect(gfx::Size(INT_MAX
, 0)));
336 int contents_max_width
, description_max_width
;
337 OmniboxPopupModel::ComputeMatchMaxWidths(
338 contents
->GetContentWidth(),
340 description
? description
->GetContentWidth() : 0,
341 mirroring_context_
->remaining_width(x
),
342 !AutocompleteMatch::IsSearchType(match
.type
),
344 &description_max_width
);
346 int after_contents_x
=
347 DrawRenderText(match
, contents
, true, canvas
, x
, y
, contents_max_width
);
349 if (description_max_width
!= 0) {
351 y
+= GetContentLineHeight();
352 if (!answer_image_
.isNull()) {
353 int answer_icon_size
= GetAnswerLineHeight();
354 canvas
->DrawImageInt(
356 0, 0, answer_image_
.width(), answer_image_
.height(),
357 GetMirroredXInView(x
), y
, answer_icon_size
, answer_icon_size
, true);
358 x
+= answer_icon_size
+ LocationBarView::kIconInternalPadding
;
361 x
= DrawRenderText(match
, separator_rendertext_
.get(), false, canvas
,
362 after_contents_x
, y
, separator_width_
);
365 DrawRenderText(match
, description
, false, canvas
, x
, y
,
366 description_max_width
);
370 int OmniboxResultView::DrawRenderText(
371 const AutocompleteMatch
& match
,
372 gfx::RenderText
* render_text
,
377 int max_width
) const {
378 DCHECK(!render_text
->text().empty());
380 const int remaining_width
= mirroring_context_
->remaining_width(x
);
381 int right_x
= x
+ max_width
;
383 // Infinite suggestions should appear with the leading ellipses vertically
386 (match
.type
== AutocompleteMatchType::SEARCH_SUGGEST_TAIL
)) {
387 // When the directionality of suggestion doesn't match the UI, we try to
388 // vertically stack the ellipsis by restricting the end edge (right_x).
389 const bool is_ui_rtl
= base::i18n::IsRTL();
390 const bool is_match_contents_rtl
=
391 (render_text
->GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT
);
393 GetDisplayOffset(match
, is_ui_rtl
, is_match_contents_rtl
);
395 scoped_ptr
<gfx::RenderText
> prefix_render_text(
396 CreateRenderText(base::UTF8ToUTF16(
397 match
.GetAdditionalInfo(kACMatchPropertyContentsPrefix
))));
398 const int prefix_width
= prefix_render_text
->GetContentWidth();
401 const int max_match_contents_width
= model_
->max_match_contents_width();
403 if (is_ui_rtl
!= is_match_contents_rtl
) {
404 // RTL infinite suggestions appear near the left edge in LTR UI, while LTR
405 // infinite suggestions appear near the right edge in RTL UI. This is
406 // against the natural horizontal alignment of the text. We reduce the
407 // width of the box for suggestion display, so that the suggestions appear
408 // in correct confines. This reduced width allows us to modify the text
409 // alignment (see below).
410 right_x
= x
+ std::min(remaining_width
- prefix_width
,
411 std::max(offset
, max_match_contents_width
));
413 // We explicitly set the horizontal alignment so that when LTR suggestions
414 // show in RTL UI (or vice versa), their ellipses appear stacked in a
416 render_text
->SetHorizontalAlignment(
417 is_match_contents_rtl
? gfx::ALIGN_RIGHT
: gfx::ALIGN_LEFT
);
419 // If the dropdown is wide enough, place the ellipsis at the position
420 // where the omitted text would have ended. Otherwise reduce the offset of
421 // the ellipsis such that the widest suggestion reaches the end of the
423 const int start_offset
= std::max(prefix_width
,
424 std::min(remaining_width
- max_match_contents_width
, offset
));
425 right_x
= x
+ std::min(remaining_width
, start_offset
+ max_width
);
427 prefix_x
= x
- prefix_width
;
429 prefix_render_text
->SetDirectionalityMode(is_match_contents_rtl
?
430 gfx::DIRECTIONALITY_FORCE_RTL
: gfx::DIRECTIONALITY_FORCE_LTR
);
431 prefix_render_text
->SetHorizontalAlignment(
432 is_match_contents_rtl
? gfx::ALIGN_RIGHT
: gfx::ALIGN_LEFT
);
433 prefix_render_text
->SetDisplayRect(
434 gfx::Rect(mirroring_context_
->mirrored_left_coord(
435 prefix_x
, prefix_x
+ prefix_width
),
436 y
, prefix_width
, GetContentLineHeight()));
437 prefix_render_text
->Draw(canvas
);
440 // Set the display rect to trigger eliding.
441 render_text
->SetDisplayRect(
442 gfx::Rect(mirroring_context_
->mirrored_left_coord(x
, right_x
), y
,
443 right_x
- x
, GetContentLineHeight()));
444 render_text
->Draw(canvas
);
448 scoped_ptr
<gfx::RenderText
> OmniboxResultView::CreateRenderText(
449 const base::string16
& text
) const {
450 scoped_ptr
<gfx::RenderText
> render_text(gfx::RenderText::CreateInstance());
451 render_text
->SetDisplayRect(gfx::Rect(gfx::Size(INT_MAX
, 0)));
452 render_text
->SetCursorEnabled(false);
453 render_text
->SetElideBehavior(gfx::ELIDE_TAIL
);
454 render_text
->SetFontList(font_list_
);
455 render_text
->SetText(text
);
456 return render_text
.Pass();
459 scoped_ptr
<gfx::RenderText
> OmniboxResultView::CreateClassifiedRenderText(
460 const base::string16
& text
,
461 const ACMatchClassifications
& classifications
,
462 bool force_dim
) const {
463 scoped_ptr
<gfx::RenderText
> render_text(CreateRenderText(text
));
464 const size_t text_length
= render_text
->text().length();
465 for (size_t i
= 0; i
< classifications
.size(); ++i
) {
466 const size_t text_start
= classifications
[i
].offset
;
467 if (text_start
>= text_length
)
470 const size_t text_end
= (i
< (classifications
.size() - 1)) ?
471 std::min(classifications
[i
+ 1].offset
, text_length
) :
473 const gfx::Range
current_range(text_start
, text_end
);
475 // Calculate style-related data.
476 if (classifications
[i
].style
& ACMatchClassification::MATCH
)
477 render_text
->ApplyStyle(gfx::BOLD
, true, current_range
);
479 ColorKind color_kind
= TEXT
;
480 if (classifications
[i
].style
& ACMatchClassification::URL
) {
482 // Consider logical string for domain "ABC.com×™/hello" where ABC are
483 // Hebrew (RTL) characters. This string should ideally show as
484 // "CBA.com/hello". If we do not force LTR on URL, it will appear as
486 // With IDN and RTL TLDs, it might be okay to allow RTL rendering of URLs,
487 // but it still has some pitfalls like :
488 // ABC.COM/abc-pqr/xyz/FGH will appear as HGF/abc-pqr/xyz/MOC.CBA which
489 // really confuses the path hierarchy of the URL.
490 // Also, if the URL supports https, the appearance will change into LTR
492 // In conclusion, LTR rendering of URL is probably the safest bet.
493 render_text
->SetDirectionalityMode(gfx::DIRECTIONALITY_FORCE_LTR
);
494 } else if (force_dim
||
495 (classifications
[i
].style
& ACMatchClassification::DIM
)) {
496 color_kind
= DIMMED_TEXT
;
498 render_text
->ApplyColor(GetColor(GetState(), color_kind
), current_range
);
500 return render_text
.Pass();
503 int OmniboxResultView::GetMatchContentsWidth() const {
504 InitContentsRenderTextIfNecessary();
505 contents_rendertext_
->SetDisplayRect(gfx::Rect(gfx::Size(INT_MAX
, 0)));
506 return contents_rendertext_
->GetContentWidth();
509 void OmniboxResultView::SetAnswerImage(const gfx::ImageSkia
& image
) {
510 answer_image_
= image
;
514 // TODO(skanuj): This is probably identical across all OmniboxResultView rows in
515 // the omnibox dropdown. Consider sharing the result.
516 int OmniboxResultView::GetDisplayOffset(
517 const AutocompleteMatch
& match
,
519 bool is_match_contents_rtl
) const {
520 if (match
.type
!= AutocompleteMatchType::SEARCH_SUGGEST_TAIL
)
523 const base::string16
& input_text
=
524 base::UTF8ToUTF16(match
.GetAdditionalInfo(kACMatchPropertyInputText
));
525 int contents_start_index
= 0;
526 base::StringToInt(match
.GetAdditionalInfo(kACMatchPropertyContentsStartIndex
),
527 &contents_start_index
);
529 scoped_ptr
<gfx::RenderText
> input_render_text(CreateRenderText(input_text
));
530 const gfx::Range
& glyph_bounds
=
531 input_render_text
->GetGlyphBounds(contents_start_index
);
532 const int start_padding
= is_match_contents_rtl
?
533 std::max(glyph_bounds
.start(), glyph_bounds
.end()) :
534 std::min(glyph_bounds
.start(), glyph_bounds
.end());
537 (input_render_text
->GetContentWidth() - start_padding
) : start_padding
;
541 int OmniboxResultView::default_icon_size_
= 0;
543 const char* OmniboxResultView::GetClassName() const {
544 return "OmniboxResultView";
547 gfx::ImageSkia
OmniboxResultView::GetIcon() const {
548 const gfx::Image image
= model_
->GetIconIfExtensionMatch(model_index_
);
549 if (!image
.IsEmpty())
550 return image
.AsImageSkia();
552 int icon
= model_
->IsStarredMatch(match_
) ?
553 IDR_OMNIBOX_STAR
: AutocompleteMatch::TypeToIcon(match_
.type
);
554 if (GetState() == SELECTED
) {
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 return location_bar_view_
->GetThemeProvider()->GetImageSkiaNamed(
583 (GetState() == SELECTED
) ? IDR_OMNIBOX_TTS_SELECTED
: IDR_OMNIBOX_TTS
);
586 bool OmniboxResultView::ShowOnlyKeywordMatch() const {
587 return match_
.associated_keyword
&&
588 (keyword_icon_
->x() <= icon_bounds_
.right());
591 void OmniboxResultView::ResetRenderTexts() const {
592 contents_rendertext_
.reset();
593 description_rendertext_
.reset();
594 separator_rendertext_
.reset();
595 keyword_contents_rendertext_
.reset();
596 keyword_description_rendertext_
.reset();
599 void OmniboxResultView::InitContentsRenderTextIfNecessary() const {
600 if (!contents_rendertext_
) {
601 contents_rendertext_
.reset(
602 CreateClassifiedRenderText(
603 match_
.contents
, match_
.contents_class
, false).release());
607 void OmniboxResultView::Layout() {
608 const gfx::ImageSkia icon
= GetIcon();
610 icon_bounds_
.SetRect(
611 edge_item_padding_
+ ((icon
.width() == default_icon_size_
)
613 : LocationBarView::kIconInternalPadding
),
614 (GetContentLineHeight() - icon
.height()) / 2, icon
.width(),
617 int text_x
= edge_item_padding_
+ default_icon_size_
+ item_padding_
;
618 int text_width
= width() - text_x
- edge_item_padding_
;
620 if (match_
.associated_keyword
.get()) {
621 const int kw_collapsed_size
=
622 keyword_icon_
->width() + edge_item_padding_
;
623 const int max_kw_x
= width() - kw_collapsed_size
;
625 animation_
->CurrentValueBetween(max_kw_x
, edge_item_padding_
);
626 const int kw_text_x
= kw_x
+ keyword_icon_
->width() + item_padding_
;
628 text_width
= kw_x
- text_x
- item_padding_
;
629 keyword_text_bounds_
.SetRect(
631 std::max(width() - kw_text_x
- edge_item_padding_
, 0), height());
632 keyword_icon_
->SetPosition(
633 gfx::Point(kw_x
, (height() - keyword_icon_
->height()) / 2));
636 text_bounds_
.SetRect(text_x
, 0, std::max(text_width
, 0), height());
639 void OmniboxResultView::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
640 animation_
->SetSlideDuration(width() / 4);
643 void OmniboxResultView::OnPaint(gfx::Canvas
* canvas
) {
644 const ResultViewState state
= GetState();
646 canvas
->DrawColor(GetColor(state
, BACKGROUND
));
648 // NOTE: While animating the keyword match, both matches may be visible.
650 if (!ShowOnlyKeywordMatch()) {
651 canvas
->DrawImageInt(GetIcon(), GetMirroredXForRect(icon_bounds_
),
653 int x
= GetMirroredXForRect(text_bounds_
);
654 mirroring_context_
->Initialize(x
, text_bounds_
.width());
655 InitContentsRenderTextIfNecessary();
657 if (!description_rendertext_
) {
659 contents_rendertext_
=
660 CreateAnswerLine(match_
.answer
->first_line(), font_list_
);
661 description_rendertext_
= CreateAnswerLine(
662 match_
.answer
->second_line(),
663 ui::ResourceBundle::GetSharedInstance().GetFontList(
664 ui::ResourceBundle::LargeFont
));
665 } else if (!match_
.description
.empty()) {
666 description_rendertext_
= CreateClassifiedRenderText(
667 match_
.description
, match_
.description_class
, true);
670 PaintMatch(match_
, contents_rendertext_
.get(),
671 description_rendertext_
.get(), canvas
, x
);
674 AutocompleteMatch
* keyword_match
= match_
.associated_keyword
.get();
676 int x
= GetMirroredXForRect(keyword_text_bounds_
);
677 mirroring_context_
->Initialize(x
, keyword_text_bounds_
.width());
678 if (!keyword_contents_rendertext_
) {
679 keyword_contents_rendertext_
.reset(
680 CreateClassifiedRenderText(keyword_match
->contents
,
681 keyword_match
->contents_class
,
684 if (!keyword_description_rendertext_
&&
685 !keyword_match
->description
.empty()) {
686 keyword_description_rendertext_
.reset(
687 CreateClassifiedRenderText(keyword_match
->description
,
688 keyword_match
->description_class
,
691 PaintMatch(*keyword_match
, keyword_contents_rendertext_
.get(),
692 keyword_description_rendertext_
.get(), canvas
, x
);
696 void OmniboxResultView::AnimationProgressed(const gfx::Animation
* animation
) {
701 int OmniboxResultView::GetAnswerLineHeight() const {
702 // GetTextStyle(1) is the largest font used and so defines the boundary that
703 // all the other answer styles fit within.
704 return ui::ResourceBundle::GetSharedInstance()
705 .GetFontList(GetTextStyle(1).font
)
709 int OmniboxResultView::GetContentLineHeight() const {
710 return std::max(default_icon_size_
+ (kMinimumIconVerticalPadding
* 2),
711 GetTextHeight() + (kMinimumTextVerticalPadding
* 2));
714 scoped_ptr
<gfx::RenderText
> OmniboxResultView::CreateAnswerLine(
715 const SuggestionAnswer::ImageLine
& line
,
716 gfx::FontList font_list
) {
717 scoped_ptr
<gfx::RenderText
> destination
= CreateRenderText(base::string16());
718 destination
->SetFontList(font_list
);
720 for (const SuggestionAnswer::TextField
& text_field
: line
.text_fields())
721 AppendAnswerText(destination
.get(), text_field
.text(), text_field
.type());
722 const base::char16
space(' ');
723 const auto* text_field
= line
.additional_text();
725 AppendAnswerText(destination
.get(), space
+ text_field
->text(),
728 text_field
= line
.status_text();
730 AppendAnswerText(destination
.get(), space
+ text_field
->text(),
733 return destination
.Pass();
736 void OmniboxResultView::AppendAnswerText(gfx::RenderText
* destination
,
737 const base::string16
& text
,
739 // TODO(dschuyler): make this better. Right now this only supports unnested
740 // bold tags. In the future we'll need to flag unexpected tags while adding
741 // support for b, i, u, sub, and sup. We'll also need to support HTML
742 // entities (< for '<', etc.).
743 const base::string16 begin_tag
= base::ASCIIToUTF16("<b>");
744 const base::string16 end_tag
= base::ASCIIToUTF16("</b>");
747 size_t end
= text
.find(begin_tag
, begin
);
748 if (end
== base::string16::npos
) {
749 AppendAnswerTextHelper(destination
, text
.substr(begin
), text_type
, false);
752 AppendAnswerTextHelper(destination
, text
.substr(begin
, end
- begin
),
754 begin
= end
+ begin_tag
.length();
755 end
= text
.find(end_tag
, begin
);
756 if (end
== base::string16::npos
)
758 AppendAnswerTextHelper(destination
, text
.substr(begin
, end
- begin
),
760 begin
= end
+ end_tag
.length();
764 void OmniboxResultView::AppendAnswerTextHelper(gfx::RenderText
* destination
,
765 const base::string16
& text
,
770 int offset
= destination
->text().length();
771 gfx::Range
range(offset
, offset
+ text
.length());
772 destination
->AppendText(text
);
773 const TextStyle
& text_style
= GetTextStyle(text_type
);
774 // TODO(dschuyler): follow up on the problem of different font sizes within
775 // one RenderText. Maybe with destination->SetFontList(...).
776 destination
->ApplyStyle(gfx::BOLD
, is_bold
, range
);
777 destination
->ApplyColor(
778 GetNativeTheme()->GetSystemColor(text_style
.colors
[GetState()]), range
);
779 destination
->ApplyBaselineStyle(text_style
.baseline
, range
);