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/bitmap_fetcher/bitmap_fetcher_service_factory.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
23 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
24 #include "chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h"
25 #include "chrome/grit/generated_resources.h"
26 #include "components/omnibox/suggestion_answer.h"
27 #include "grit/components_scaled_resources.h"
28 #include "grit/theme_resources.h"
29 #include "third_party/skia/include/core/SkColor.h"
30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/base/theme_provider.h"
33 #include "ui/gfx/canvas.h"
34 #include "ui/gfx/color_utils.h"
35 #include "ui/gfx/image/image.h"
36 #include "ui/gfx/range/range.h"
37 #include "ui/gfx/render_text.h"
38 #include "ui/gfx/text_utils.h"
40 using ui::NativeTheme
;
44 // The minimum distance between the top and bottom of the icon and the
45 // top or bottom of the row.
46 const int kMinimumIconVerticalPadding
= 2;
48 // Calls back to the OmniboxResultView when the requested image is downloaded.
49 // This is a separate class instead of being implemented on OmniboxResultView
50 // because BitmapFetcherService currently takes ownership of this object.
51 // TODO(dschuyler): Make BitmapFetcherService use the more typical non-owning
52 // ObserverList pattern and have OmniboxResultView implement the Observer call
54 class AnswerImageObserver
: public BitmapFetcherService::Observer
{
56 explicit AnswerImageObserver(
57 const base::WeakPtr
<OmniboxResultView
>& view
)
60 void OnImageChanged(BitmapFetcherService::RequestId request_id
,
61 const SkBitmap
& image
) override
;
64 const base::WeakPtr
<OmniboxResultView
> view_
;
65 DISALLOW_COPY_AND_ASSIGN(AnswerImageObserver
);
68 void AnswerImageObserver::OnImageChanged(
69 BitmapFetcherService::RequestId request_id
,
70 const SkBitmap
& image
) {
71 DCHECK(!image
.empty());
73 view_
->SetAnswerImage(gfx::ImageSkia::CreateFrom1xBitmap(image
));
76 // A mapping from OmniboxResultView's ResultViewState/ColorKind types to
77 // NativeTheme colors.
78 struct TranslationTable
{
79 ui::NativeTheme::ColorId id
;
80 OmniboxResultView::ResultViewState state
;
81 OmniboxResultView::ColorKind kind
;
82 } static const kTranslationTable
[] = {
83 { NativeTheme::kColorId_ResultsTableNormalBackground
,
84 OmniboxResultView::NORMAL
, OmniboxResultView::BACKGROUND
},
85 { NativeTheme::kColorId_ResultsTableHoveredBackground
,
86 OmniboxResultView::HOVERED
, OmniboxResultView::BACKGROUND
},
87 { NativeTheme::kColorId_ResultsTableSelectedBackground
,
88 OmniboxResultView::SELECTED
, OmniboxResultView::BACKGROUND
},
89 { NativeTheme::kColorId_ResultsTableNormalText
,
90 OmniboxResultView::NORMAL
, OmniboxResultView::TEXT
},
91 { NativeTheme::kColorId_ResultsTableHoveredText
,
92 OmniboxResultView::HOVERED
, OmniboxResultView::TEXT
},
93 { NativeTheme::kColorId_ResultsTableSelectedText
,
94 OmniboxResultView::SELECTED
, OmniboxResultView::TEXT
},
95 { NativeTheme::kColorId_ResultsTableNormalDimmedText
,
96 OmniboxResultView::NORMAL
, OmniboxResultView::DIMMED_TEXT
},
97 { NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
98 OmniboxResultView::HOVERED
, OmniboxResultView::DIMMED_TEXT
},
99 { NativeTheme::kColorId_ResultsTableSelectedDimmedText
,
100 OmniboxResultView::SELECTED
, OmniboxResultView::DIMMED_TEXT
},
101 { NativeTheme::kColorId_ResultsTableNormalUrl
,
102 OmniboxResultView::NORMAL
, OmniboxResultView::URL
},
103 { NativeTheme::kColorId_ResultsTableHoveredUrl
,
104 OmniboxResultView::HOVERED
, OmniboxResultView::URL
},
105 { NativeTheme::kColorId_ResultsTableSelectedUrl
,
106 OmniboxResultView::SELECTED
, OmniboxResultView::URL
},
107 { NativeTheme::kColorId_ResultsTableNormalDivider
,
108 OmniboxResultView::NORMAL
, OmniboxResultView::DIVIDER
},
109 { NativeTheme::kColorId_ResultsTableHoveredDivider
,
110 OmniboxResultView::HOVERED
, OmniboxResultView::DIVIDER
},
111 { NativeTheme::kColorId_ResultsTableSelectedDivider
,
112 OmniboxResultView::SELECTED
, OmniboxResultView::DIVIDER
},
116 ui::ResourceBundle::FontStyle font
;
117 ui::NativeTheme::ColorId colors
[OmniboxResultView::NUM_STATES
];
118 gfx::BaselineStyle baseline
;
119 } const kTextStyles
[] = {
121 {ui::ResourceBundle::LargeFont
,
122 {NativeTheme::kColorId_ResultsTableNormalText
,
123 NativeTheme::kColorId_ResultsTableHoveredText
,
124 NativeTheme::kColorId_ResultsTableSelectedText
},
125 gfx::NORMAL_BASELINE
},
127 {ui::ResourceBundle::LargeFont
,
128 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
129 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
130 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
131 gfx::NORMAL_BASELINE
},
132 // 3 TOP_ALIGNED_TEXT
133 {ui::ResourceBundle::LargeFont
,
134 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
135 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
136 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
138 // 4 DESCRIPTION_TEXT
139 {ui::ResourceBundle::BaseFont
,
140 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
141 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
142 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
143 gfx::NORMAL_BASELINE
},
144 // 5 DESCRIPTION_TEXT_NEGATIVE
145 {ui::ResourceBundle::LargeFont
,
146 {NativeTheme::kColorId_ResultsTableNegativeText
,
147 NativeTheme::kColorId_ResultsTableNegativeHoveredText
,
148 NativeTheme::kColorId_ResultsTableNegativeSelectedText
},
150 // 6 DESCRIPTION_TEXT_POSITIVE
151 {ui::ResourceBundle::LargeFont
,
152 {NativeTheme::kColorId_ResultsTablePositiveText
,
153 NativeTheme::kColorId_ResultsTablePositiveHoveredText
,
154 NativeTheme::kColorId_ResultsTablePositiveSelectedText
},
157 {ui::ResourceBundle::SmallFont
,
158 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
159 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
160 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
161 gfx::NORMAL_BASELINE
},
163 {ui::ResourceBundle::BaseFont
,
164 {NativeTheme::kColorId_ResultsTableNormalText
,
165 NativeTheme::kColorId_ResultsTableHoveredText
,
166 NativeTheme::kColorId_ResultsTableSelectedText
},
167 gfx::NORMAL_BASELINE
},
168 // 9 SUGGESTION_TEXT_POSITIVE
169 {ui::ResourceBundle::BaseFont
,
170 {NativeTheme::kColorId_ResultsTablePositiveText
,
171 NativeTheme::kColorId_ResultsTablePositiveHoveredText
,
172 NativeTheme::kColorId_ResultsTablePositiveSelectedText
},
173 gfx::NORMAL_BASELINE
},
174 // 10 SUGGESTION_TEXT_NEGATIVE
175 {ui::ResourceBundle::BaseFont
,
176 {NativeTheme::kColorId_ResultsTableNegativeText
,
177 NativeTheme::kColorId_ResultsTableNegativeHoveredText
,
178 NativeTheme::kColorId_ResultsTableNegativeSelectedText
},
179 gfx::NORMAL_BASELINE
},
180 // 11 SUGGESTION_LINK_COLOR
181 {ui::ResourceBundle::BaseFont
,
182 {NativeTheme::kColorId_ResultsTableNormalUrl
,
183 NativeTheme::kColorId_ResultsTableHoveredUrl
,
184 NativeTheme::kColorId_ResultsTableSelectedUrl
},
185 gfx::NORMAL_BASELINE
},
187 {ui::ResourceBundle::LargeFont
,
188 {NativeTheme::kColorId_ResultsTableNormalDimmedText
,
189 NativeTheme::kColorId_ResultsTableHoveredDimmedText
,
190 NativeTheme::kColorId_ResultsTableSelectedDimmedText
},
192 // 13 PERSONALIZED_SUGGESTION_TEXT
193 {ui::ResourceBundle::BaseFont
,
194 {NativeTheme::kColorId_ResultsTableNormalText
,
195 NativeTheme::kColorId_ResultsTableHoveredText
,
196 NativeTheme::kColorId_ResultsTableSelectedText
},
197 gfx::NORMAL_BASELINE
},
200 const TextStyle
& GetTextStyle(int type
) {
201 if (type
< 1 || static_cast<size_t>(type
) > arraysize(kTextStyles
))
203 // Subtract one because the types are one based (not zero based).
204 return kTextStyles
[type
- 1];
209 ////////////////////////////////////////////////////////////////////////////////
210 // OmniboxResultView, public:
212 // This class is a utility class for calculations affected by whether the result
213 // view is horizontally mirrored. The drawing functions can be written as if
214 // all drawing occurs left-to-right, and then use this class to get the actual
215 // coordinates to begin drawing onscreen.
216 class OmniboxResultView::MirroringContext
{
218 MirroringContext() : center_(0), right_(0) {}
220 // Tells the mirroring context to use the provided range as the physical
221 // bounds of the drawing region. When coordinate mirroring is needed, the
222 // mirror point will be the center of this range.
223 void Initialize(int x
, int width
) {
224 center_
= x
+ width
/ 2;
228 // Given a logical range within the drawing region, returns the coordinate of
229 // the possibly-mirrored "left" side. (This functions exactly like
230 // View::MirroredLeftPointForRect().)
231 int mirrored_left_coord(int left
, int right
) const {
232 return base::i18n::IsRTL() ? (center_
+ (center_
- right
)) : left
;
235 // Given a logical coordinate within the drawing region, returns the remaining
237 int remaining_width(int x
) const {
245 DISALLOW_COPY_AND_ASSIGN(MirroringContext
);
248 OmniboxResultView::OmniboxResultView(OmniboxPopupContentsView
* model
,
250 LocationBarView
* location_bar_view
,
251 const gfx::FontList
& font_list
)
252 : edge_item_padding_(LocationBarView::kItemPadding
),
253 item_padding_(LocationBarView::kItemPadding
),
255 model_index_(model_index
),
256 location_bar_view_(location_bar_view
),
257 image_service_(BitmapFetcherServiceFactory::GetForBrowserContext(
258 location_bar_view_
->profile())),
259 font_list_(font_list
),
261 std::max(font_list
.GetHeight(),
262 font_list
.DeriveWithStyle(gfx::Font::BOLD
).GetHeight())),
263 mirroring_context_(new MirroringContext()),
264 keyword_icon_(new views::ImageView()),
265 animation_(new gfx::SlideAnimation(this)),
266 request_id_(BitmapFetcherService::REQUEST_ID_INVALID
),
267 weak_ptr_factory_(this) {
268 CHECK_GE(model_index
, 0);
269 if (default_icon_size_
== 0) {
271 location_bar_view_
->GetThemeProvider()->GetImageSkiaNamed(
272 AutocompleteMatch::TypeToIcon(
273 AutocompleteMatchType::URL_WHAT_YOU_TYPED
))->width();
275 keyword_icon_
->set_owned_by_client();
276 keyword_icon_
->EnableCanvasFlippingForRTLUI(true);
277 keyword_icon_
->SetImage(GetKeywordIcon());
278 keyword_icon_
->SizeToPreferredSize();
281 OmniboxResultView::~OmniboxResultView() {
283 image_service_
->CancelRequest(request_id_
);
286 SkColor
OmniboxResultView::GetColor(
287 ResultViewState state
,
288 ColorKind kind
) const {
289 for (size_t i
= 0; i
< arraysize(kTranslationTable
); ++i
) {
290 if (kTranslationTable
[i
].state
== state
&&
291 kTranslationTable
[i
].kind
== kind
) {
292 return GetNativeTheme()->GetSystemColor(kTranslationTable
[i
].id
);
300 void OmniboxResultView::SetMatch(const AutocompleteMatch
& match
) {
305 answer_image_
= gfx::ImageSkia();
306 if (image_service_
) {
307 image_service_
->CancelRequest(request_id_
);
309 request_id_
= image_service_
->RequestImage(
310 match_
.answer
->second_line().image_url(),
311 new AnswerImageObserver(weak_ptr_factory_
.GetWeakPtr()));
315 AutocompleteMatch
* associated_keyword_match
= match_
.associated_keyword
.get();
316 if (associated_keyword_match
) {
317 keyword_icon_
->SetImage(GetKeywordIcon());
318 if (!keyword_icon_
->parent())
319 AddChildView(keyword_icon_
.get());
320 } else if (keyword_icon_
->parent()) {
321 RemoveChildView(keyword_icon_
.get());
327 void OmniboxResultView::ShowKeyword(bool show_keyword
) {
334 void OmniboxResultView::Invalidate() {
335 keyword_icon_
->SetImage(GetKeywordIcon());
336 // While the text in the RenderTexts may not have changed, the styling
337 // (color/bold) may need to change. So we reset them to cause them to be
338 // recomputed in OnPaint().
343 gfx::Size
OmniboxResultView::GetPreferredSize() const {
345 return gfx::Size(0, GetContentLineHeight());
346 // An answer implies a match and a description in a large font.
347 return gfx::Size(0, GetContentLineHeight() + GetAnswerLineHeight());
350 ////////////////////////////////////////////////////////////////////////////////
351 // OmniboxResultView, protected:
353 OmniboxResultView::ResultViewState
OmniboxResultView::GetState() const {
354 if (model_
->IsSelectedIndex(model_index_
))
356 return model_
->IsHoveredIndex(model_index_
) ? HOVERED
: NORMAL
;
359 int OmniboxResultView::GetTextHeight() const {
363 void OmniboxResultView::PaintMatch(const AutocompleteMatch
& match
,
364 gfx::RenderText
* contents
,
365 gfx::RenderText
* description
,
368 int y
= text_bounds_
.y();
370 if (!separator_rendertext_
) {
371 const base::string16
& separator
=
372 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR
);
373 separator_rendertext_
.reset(CreateRenderText(separator
).release());
374 separator_rendertext_
->SetColor(GetColor(GetState(), DIMMED_TEXT
));
375 separator_width_
= separator_rendertext_
->GetContentWidth();
378 int contents_max_width
, description_max_width
;
379 OmniboxPopupModel::ComputeMatchMaxWidths(
380 contents
->GetContentWidth(),
382 description
? description
->GetContentWidth() : 0,
383 mirroring_context_
->remaining_width(x
),
384 !AutocompleteMatch::IsSearchType(match
.type
),
386 &description_max_width
);
388 int after_contents_x
=
389 DrawRenderText(match
, contents
, true, canvas
, x
, y
, contents_max_width
);
391 if (description_max_width
!= 0) {
393 y
+= GetContentLineHeight();
394 if (!answer_image_
.isNull()) {
395 int answer_icon_size
= GetAnswerLineHeight();
396 canvas
->DrawImageInt(
398 0, 0, answer_image_
.width(), answer_image_
.height(),
399 GetMirroredXInView(x
), y
, answer_icon_size
, answer_icon_size
, true);
400 x
+= answer_icon_size
+ LocationBarView::kIconInternalPadding
;
403 x
= DrawRenderText(match
, separator_rendertext_
.get(), false, canvas
,
404 after_contents_x
, y
, separator_width_
);
407 DrawRenderText(match
, description
, false, canvas
, x
, y
,
408 description_max_width
);
412 int OmniboxResultView::DrawRenderText(
413 const AutocompleteMatch
& match
,
414 gfx::RenderText
* render_text
,
419 int max_width
) const {
420 DCHECK(!render_text
->text().empty());
422 const int remaining_width
= mirroring_context_
->remaining_width(x
);
423 int right_x
= x
+ max_width
;
425 // Infinite suggestions should appear with the leading ellipses vertically
428 (match
.type
== AutocompleteMatchType::SEARCH_SUGGEST_TAIL
)) {
429 // When the directionality of suggestion doesn't match the UI, we try to
430 // vertically stack the ellipsis by restricting the end edge (right_x).
431 const bool is_ui_rtl
= base::i18n::IsRTL();
432 const bool is_match_contents_rtl
=
433 (render_text
->GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT
);
435 GetDisplayOffset(match
, is_ui_rtl
, is_match_contents_rtl
);
437 scoped_ptr
<gfx::RenderText
> prefix_render_text(
438 CreateRenderText(base::UTF8ToUTF16(
439 match
.GetAdditionalInfo(kACMatchPropertyContentsPrefix
))));
440 const int prefix_width
= prefix_render_text
->GetContentWidth();
443 const int max_match_contents_width
= model_
->max_match_contents_width();
445 if (is_ui_rtl
!= is_match_contents_rtl
) {
446 // RTL infinite suggestions appear near the left edge in LTR UI, while LTR
447 // infinite suggestions appear near the right edge in RTL UI. This is
448 // against the natural horizontal alignment of the text. We reduce the
449 // width of the box for suggestion display, so that the suggestions appear
450 // in correct confines. This reduced width allows us to modify the text
451 // alignment (see below).
452 right_x
= x
+ std::min(remaining_width
- prefix_width
,
453 std::max(offset
, max_match_contents_width
));
455 // We explicitly set the horizontal alignment so that when LTR suggestions
456 // show in RTL UI (or vice versa), their ellipses appear stacked in a
458 render_text
->SetHorizontalAlignment(
459 is_match_contents_rtl
? gfx::ALIGN_RIGHT
: gfx::ALIGN_LEFT
);
461 // If the dropdown is wide enough, place the ellipsis at the position
462 // where the omitted text would have ended. Otherwise reduce the offset of
463 // the ellipsis such that the widest suggestion reaches the end of the
465 const int start_offset
= std::max(prefix_width
,
466 std::min(remaining_width
- max_match_contents_width
, offset
));
467 right_x
= x
+ std::min(remaining_width
, start_offset
+ max_width
);
469 prefix_x
= x
- prefix_width
;
471 prefix_render_text
->SetDirectionalityMode(is_match_contents_rtl
?
472 gfx::DIRECTIONALITY_FORCE_RTL
: gfx::DIRECTIONALITY_FORCE_LTR
);
473 prefix_render_text
->SetHorizontalAlignment(
474 is_match_contents_rtl
? gfx::ALIGN_RIGHT
: gfx::ALIGN_LEFT
);
475 prefix_render_text
->SetDisplayRect(
476 gfx::Rect(mirroring_context_
->mirrored_left_coord(
477 prefix_x
, prefix_x
+ prefix_width
),
478 y
, prefix_width
, GetContentLineHeight()));
479 prefix_render_text
->Draw(canvas
);
482 // Set the display rect to trigger eliding.
483 render_text
->SetDisplayRect(
484 gfx::Rect(mirroring_context_
->mirrored_left_coord(x
, right_x
), y
,
485 right_x
- x
, GetContentLineHeight()));
486 render_text
->Draw(canvas
);
490 scoped_ptr
<gfx::RenderText
> OmniboxResultView::CreateRenderText(
491 const base::string16
& text
) const {
492 scoped_ptr
<gfx::RenderText
> render_text(gfx::RenderText::CreateInstance());
493 render_text
->SetDisplayRect(gfx::Rect(gfx::Size(INT_MAX
, 0)));
494 render_text
->SetCursorEnabled(false);
495 render_text
->SetElideBehavior(gfx::ELIDE_TAIL
);
496 render_text
->SetFontList(font_list_
);
497 render_text
->SetText(text
);
498 return render_text
.Pass();
501 scoped_ptr
<gfx::RenderText
> OmniboxResultView::CreateClassifiedRenderText(
502 const base::string16
& text
,
503 const ACMatchClassifications
& classifications
,
504 bool force_dim
) const {
505 scoped_ptr
<gfx::RenderText
> render_text(CreateRenderText(text
));
506 const size_t text_length
= render_text
->text().length();
507 for (size_t i
= 0; i
< classifications
.size(); ++i
) {
508 const size_t text_start
= classifications
[i
].offset
;
509 if (text_start
>= text_length
)
512 const size_t text_end
= (i
< (classifications
.size() - 1)) ?
513 std::min(classifications
[i
+ 1].offset
, text_length
) :
515 const gfx::Range
current_range(text_start
, text_end
);
517 // Calculate style-related data.
518 if (classifications
[i
].style
& ACMatchClassification::MATCH
)
519 render_text
->ApplyStyle(gfx::BOLD
, true, current_range
);
521 ColorKind color_kind
= TEXT
;
522 if (classifications
[i
].style
& ACMatchClassification::URL
) {
524 // Consider logical string for domain "ABC.com×™/hello" where ABC are
525 // Hebrew (RTL) characters. This string should ideally show as
526 // "CBA.com/hello". If we do not force LTR on URL, it will appear as
528 // With IDN and RTL TLDs, it might be okay to allow RTL rendering of URLs,
529 // but it still has some pitfalls like :
530 // ABC.COM/abc-pqr/xyz/FGH will appear as HGF/abc-pqr/xyz/MOC.CBA which
531 // really confuses the path hierarchy of the URL.
532 // Also, if the URL supports https, the appearance will change into LTR
534 // In conclusion, LTR rendering of URL is probably the safest bet.
535 render_text
->SetDirectionalityMode(gfx::DIRECTIONALITY_FORCE_LTR
);
536 } else if (force_dim
||
537 (classifications
[i
].style
& ACMatchClassification::DIM
)) {
538 color_kind
= DIMMED_TEXT
;
540 render_text
->ApplyColor(GetColor(GetState(), color_kind
), current_range
);
542 return render_text
.Pass();
545 int OmniboxResultView::GetMatchContentsWidth() const {
546 InitContentsRenderTextIfNecessary();
547 return contents_rendertext_
->GetContentWidth();
550 void OmniboxResultView::SetAnswerImage(const gfx::ImageSkia
& image
) {
551 answer_image_
= image
;
555 // TODO(skanuj): This is probably identical across all OmniboxResultView rows in
556 // the omnibox dropdown. Consider sharing the result.
557 int OmniboxResultView::GetDisplayOffset(
558 const AutocompleteMatch
& match
,
560 bool is_match_contents_rtl
) const {
561 if (match
.type
!= AutocompleteMatchType::SEARCH_SUGGEST_TAIL
)
564 const base::string16
& input_text
=
565 base::UTF8ToUTF16(match
.GetAdditionalInfo(kACMatchPropertyInputText
));
566 int contents_start_index
= 0;
567 base::StringToInt(match
.GetAdditionalInfo(kACMatchPropertyContentsStartIndex
),
568 &contents_start_index
);
570 scoped_ptr
<gfx::RenderText
> input_render_text(CreateRenderText(input_text
));
571 const gfx::Range
& glyph_bounds
=
572 input_render_text
->GetGlyphBounds(contents_start_index
);
573 const int start_padding
= is_match_contents_rtl
?
574 std::max(glyph_bounds
.start(), glyph_bounds
.end()) :
575 std::min(glyph_bounds
.start(), glyph_bounds
.end());
578 (input_render_text
->GetContentWidth() - start_padding
) : start_padding
;
582 int OmniboxResultView::default_icon_size_
= 0;
584 const char* OmniboxResultView::GetClassName() const {
585 return "OmniboxResultView";
588 gfx::ImageSkia
OmniboxResultView::GetIcon() const {
589 const gfx::Image image
= model_
->GetIconIfExtensionMatch(model_index_
);
590 if (!image
.IsEmpty())
591 return image
.AsImageSkia();
593 int icon
= model_
->IsStarredMatch(match_
) ?
594 IDR_OMNIBOX_STAR
: AutocompleteMatch::TypeToIcon(match_
.type
);
595 if (GetState() == SELECTED
) {
597 case IDR_OMNIBOX_CALCULATOR
:
598 icon
= IDR_OMNIBOX_CALCULATOR_SELECTED
;
600 case IDR_OMNIBOX_EXTENSION_APP
:
601 icon
= IDR_OMNIBOX_EXTENSION_APP_SELECTED
;
603 case IDR_OMNIBOX_HTTP
:
604 icon
= IDR_OMNIBOX_HTTP_SELECTED
;
606 case IDR_OMNIBOX_SEARCH
:
607 icon
= IDR_OMNIBOX_SEARCH_SELECTED
;
609 case IDR_OMNIBOX_STAR
:
610 icon
= IDR_OMNIBOX_STAR_SELECTED
;
617 return *(location_bar_view_
->GetThemeProvider()->GetImageSkiaNamed(icon
));
620 const gfx::ImageSkia
* OmniboxResultView::GetKeywordIcon() const {
621 // NOTE: If we ever begin returning icons of varying size, then callers need
622 // to ensure that |keyword_icon_| is resized each time its image is reset.
623 return location_bar_view_
->GetThemeProvider()->GetImageSkiaNamed(
624 (GetState() == SELECTED
) ? IDR_OMNIBOX_TTS_SELECTED
: IDR_OMNIBOX_TTS
);
627 bool OmniboxResultView::ShowOnlyKeywordMatch() const {
628 return match_
.associated_keyword
&&
629 (keyword_icon_
->x() <= icon_bounds_
.right());
632 void OmniboxResultView::ResetRenderTexts() const {
633 contents_rendertext_
.reset();
634 description_rendertext_
.reset();
635 separator_rendertext_
.reset();
636 keyword_contents_rendertext_
.reset();
637 keyword_description_rendertext_
.reset();
640 void OmniboxResultView::InitContentsRenderTextIfNecessary() const {
641 if (!contents_rendertext_
) {
642 contents_rendertext_
.reset(
643 CreateClassifiedRenderText(
644 match_
.contents
, match_
.contents_class
, false).release());
648 void OmniboxResultView::Layout() {
649 const gfx::ImageSkia icon
= GetIcon();
651 icon_bounds_
.SetRect(
652 edge_item_padding_
+ ((icon
.width() == default_icon_size_
)
654 : LocationBarView::kIconInternalPadding
),
655 (GetContentLineHeight() - icon
.height()) / 2, icon
.width(),
658 int text_x
= edge_item_padding_
+ default_icon_size_
+ item_padding_
;
659 int text_width
= width() - text_x
- edge_item_padding_
;
661 if (match_
.associated_keyword
.get()) {
662 const int kw_collapsed_size
=
663 keyword_icon_
->width() + edge_item_padding_
;
664 const int max_kw_x
= width() - kw_collapsed_size
;
666 animation_
->CurrentValueBetween(max_kw_x
, edge_item_padding_
);
667 const int kw_text_x
= kw_x
+ keyword_icon_
->width() + item_padding_
;
669 text_width
= kw_x
- text_x
- item_padding_
;
670 keyword_text_bounds_
.SetRect(
672 std::max(width() - kw_text_x
- edge_item_padding_
, 0), height());
673 keyword_icon_
->SetPosition(
674 gfx::Point(kw_x
, (height() - keyword_icon_
->height()) / 2));
677 text_bounds_
.SetRect(text_x
, 0, std::max(text_width
, 0), height());
680 void OmniboxResultView::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
681 animation_
->SetSlideDuration(width() / 4);
684 void OmniboxResultView::OnPaint(gfx::Canvas
* canvas
) {
685 const ResultViewState state
= GetState();
687 canvas
->DrawColor(GetColor(state
, BACKGROUND
));
689 // NOTE: While animating the keyword match, both matches may be visible.
691 if (!ShowOnlyKeywordMatch()) {
692 canvas
->DrawImageInt(GetIcon(), GetMirroredXForRect(icon_bounds_
),
694 int x
= GetMirroredXForRect(text_bounds_
);
695 mirroring_context_
->Initialize(x
, text_bounds_
.width());
696 InitContentsRenderTextIfNecessary();
698 if (!description_rendertext_
) {
701 description_rendertext_
= CreateRenderText(text
);
702 for (const SuggestionAnswer::TextField
& text_field
:
703 match_
.answer
->second_line().text_fields())
704 AppendAnswerText(text_field
.text(), text_field
.type());
705 const base::char16
space(' ');
706 const auto* text_field
= match_
.answer
->second_line().additional_text();
708 AppendAnswerText(space
+ text_field
->text(), text_field
->type());
709 text_field
= match_
.answer
->second_line().status_text();
711 AppendAnswerText(space
+ text_field
->text(), text_field
->type());
712 } else if (!match_
.description
.empty()) {
713 description_rendertext_
= CreateClassifiedRenderText(
714 match_
.description
, match_
.description_class
, true);
717 PaintMatch(match_
, contents_rendertext_
.get(),
718 description_rendertext_
.get(), canvas
, x
);
721 AutocompleteMatch
* keyword_match
= match_
.associated_keyword
.get();
723 int x
= GetMirroredXForRect(keyword_text_bounds_
);
724 mirroring_context_
->Initialize(x
, keyword_text_bounds_
.width());
725 if (!keyword_contents_rendertext_
) {
726 keyword_contents_rendertext_
.reset(
727 CreateClassifiedRenderText(keyword_match
->contents
,
728 keyword_match
->contents_class
,
731 if (!keyword_description_rendertext_
&&
732 !keyword_match
->description
.empty()) {
733 keyword_description_rendertext_
.reset(
734 CreateClassifiedRenderText(keyword_match
->description
,
735 keyword_match
->description_class
,
738 PaintMatch(*keyword_match
, keyword_contents_rendertext_
.get(),
739 keyword_description_rendertext_
.get(), canvas
, x
);
743 void OmniboxResultView::AnimationProgressed(const gfx::Animation
* animation
) {
748 int OmniboxResultView::GetAnswerLineHeight() const {
749 // GetTextStyle(1) is the largest font used and so defines the boundary that
750 // all the other answer styles fit within.
751 return ui::ResourceBundle::GetSharedInstance()
752 .GetFontList(GetTextStyle(1).font
)
756 int OmniboxResultView::GetContentLineHeight() const {
757 return std::max(default_icon_size_
+ (kMinimumIconVerticalPadding
* 2),
758 GetTextHeight() + (kMinimumTextVerticalPadding
* 2));
761 void OmniboxResultView::AppendAnswerText(const base::string16
& text
,
763 int offset
= description_rendertext_
->text().length();
764 gfx::Range
range(offset
, offset
+ text
.length());
765 description_rendertext_
->AppendText(text
);
766 const TextStyle
& text_style
= GetTextStyle(text_type
);
767 // TODO(dschuyler): follow up on the problem of different font sizes within
769 description_rendertext_
->SetFontList(
770 ui::ResourceBundle::GetSharedInstance().GetFontList(text_style
.font
));
771 description_rendertext_
->ApplyColor(
772 GetNativeTheme()->GetSystemColor(text_style
.colors
[GetState()]), range
);
773 description_rendertext_
->ApplyBaselineStyle(text_style
.baseline
, range
);