Misc. minor cleanups/simplifications.
[chromium-blink-merge.git] / chrome / browser / ui / views / omnibox / omnibox_result_view.cc
blob0cbd99341fec87ede2b5fd67eb5af24d62e34b48
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"
7 #if defined(OS_WIN)
8 #include <atlbase.h> // NOLINT
9 #include <atlwin.h> // NOLINT
10 #endif
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;
42 namespace {
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
53 // directly.
54 class AnswerImageObserver : public BitmapFetcherService::Observer {
55 public:
56 explicit AnswerImageObserver(
57 const base::WeakPtr<OmniboxResultView>& view)
58 : view_(view) {}
60 void OnImageChanged(BitmapFetcherService::RequestId request_id,
61 const SkBitmap& image) override;
63 private:
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());
72 if (view_)
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 },
115 struct TextStyle {
116 ui::ResourceBundle::FontStyle font;
117 ui::NativeTheme::ColorId colors[OmniboxResultView::NUM_STATES];
118 gfx::BaselineStyle baseline;
119 } const kTextStyles[] = {
120 // 1 ANSWER_TEXT
121 {ui::ResourceBundle::LargeFont,
122 {NativeTheme::kColorId_ResultsTableNormalText,
123 NativeTheme::kColorId_ResultsTableHoveredText,
124 NativeTheme::kColorId_ResultsTableSelectedText},
125 gfx::NORMAL_BASELINE},
126 // 2 HEADLINE_TEXT
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},
137 gfx::SUPERIOR},
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},
149 gfx::INFERIOR},
150 // 6 DESCRIPTION_TEXT_POSITIVE
151 {ui::ResourceBundle::LargeFont,
152 {NativeTheme::kColorId_ResultsTablePositiveText,
153 NativeTheme::kColorId_ResultsTablePositiveHoveredText,
154 NativeTheme::kColorId_ResultsTablePositiveSelectedText},
155 gfx::INFERIOR},
156 // 7 MORE_INFO_TEXT
157 {ui::ResourceBundle::SmallFont,
158 {NativeTheme::kColorId_ResultsTableNormalDimmedText,
159 NativeTheme::kColorId_ResultsTableHoveredDimmedText,
160 NativeTheme::kColorId_ResultsTableSelectedDimmedText},
161 gfx::NORMAL_BASELINE},
162 // 8 SUGGESTION_TEXT
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},
186 // 12 STATUS_TEXT
187 {ui::ResourceBundle::LargeFont,
188 {NativeTheme::kColorId_ResultsTableNormalDimmedText,
189 NativeTheme::kColorId_ResultsTableHoveredDimmedText,
190 NativeTheme::kColorId_ResultsTableSelectedDimmedText},
191 gfx::INFERIOR},
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))
202 type = 1;
203 // Subtract one because the types are one based (not zero based).
204 return kTextStyles[type - 1];
207 } // namespace
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 {
217 public:
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;
225 right_ = x + width;
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
236 // width available.
237 int remaining_width(int x) const {
238 return right_ - x;
241 private:
242 int center_;
243 int right_;
245 DISALLOW_COPY_AND_ASSIGN(MirroringContext);
248 OmniboxResultView::OmniboxResultView(OmniboxPopupContentsView* model,
249 int model_index,
250 LocationBarView* location_bar_view,
251 const gfx::FontList& font_list)
252 : edge_item_padding_(LocationBarView::kItemPadding),
253 item_padding_(LocationBarView::kItemPadding),
254 model_(model),
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),
260 font_height_(
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) {
270 default_icon_size_ =
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() {
282 if (image_service_)
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);
296 NOTREACHED();
297 return SK_ColorRED;
300 void OmniboxResultView::SetMatch(const AutocompleteMatch& match) {
301 match_ = match;
302 ResetRenderTexts();
303 animation_->Reset();
305 answer_image_ = gfx::ImageSkia();
306 if (image_service_) {
307 image_service_->CancelRequest(request_id_);
308 if (match_.answer) {
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());
324 Layout();
327 void OmniboxResultView::ShowKeyword(bool show_keyword) {
328 if (show_keyword)
329 animation_->Show();
330 else
331 animation_->Hide();
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().
339 ResetRenderTexts();
340 SchedulePaint();
343 gfx::Size OmniboxResultView::GetPreferredSize() const {
344 if (!match_.answer)
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_))
355 return SELECTED;
356 return model_->IsHoveredIndex(model_index_) ? HOVERED : NORMAL;
359 int OmniboxResultView::GetTextHeight() const {
360 return font_height_;
363 void OmniboxResultView::PaintMatch(const AutocompleteMatch& match,
364 gfx::RenderText* contents,
365 gfx::RenderText* description,
366 gfx::Canvas* canvas,
367 int x) const {
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(),
381 separator_width_,
382 description ? description->GetContentWidth() : 0,
383 mirroring_context_->remaining_width(x),
384 !AutocompleteMatch::IsSearchType(match.type),
385 &contents_max_width,
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) {
392 if (match.answer) {
393 y += GetContentLineHeight();
394 if (!answer_image_.isNull()) {
395 int answer_icon_size = GetAnswerLineHeight();
396 canvas->DrawImageInt(
397 answer_image_,
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;
402 } else {
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,
415 bool contents,
416 gfx::Canvas* canvas,
417 int x,
418 int y,
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
426 // stacked.
427 if (contents &&
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);
434 const int offset =
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();
441 int prefix_x = x;
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));
454 prefix_x = right_x;
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
457 // single column.
458 render_text->SetHorizontalAlignment(
459 is_match_contents_rtl ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT);
460 } else {
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
464 // dropdown.
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);
468 x += start_offset;
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);
487 return right_x;
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)
510 break;
512 const size_t text_end = (i < (classifications.size() - 1)) ?
513 std::min(classifications[i + 1].offset, text_length) :
514 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) {
523 color_kind = 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
527 // "com/hello.CBA".
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
533 // directionality.
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;
552 SchedulePaint();
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,
559 bool is_ui_rtl,
560 bool is_match_contents_rtl) const {
561 if (match.type != AutocompleteMatchType::SEARCH_SUGGEST_TAIL)
562 return 0;
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());
577 return is_ui_rtl ?
578 (input_render_text->GetContentWidth() - start_padding) : start_padding;
581 // static
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) {
596 switch (icon) {
597 case IDR_OMNIBOX_CALCULATOR:
598 icon = IDR_OMNIBOX_CALCULATOR_SELECTED;
599 break;
600 case IDR_OMNIBOX_EXTENSION_APP:
601 icon = IDR_OMNIBOX_EXTENSION_APP_SELECTED;
602 break;
603 case IDR_OMNIBOX_HTTP:
604 icon = IDR_OMNIBOX_HTTP_SELECTED;
605 break;
606 case IDR_OMNIBOX_SEARCH:
607 icon = IDR_OMNIBOX_SEARCH_SELECTED;
608 break;
609 case IDR_OMNIBOX_STAR:
610 icon = IDR_OMNIBOX_STAR_SELECTED;
611 break;
612 default:
613 NOTREACHED();
614 break;
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(),
656 icon.height());
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;
665 const int kw_x =
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(
671 kw_text_x, 0,
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();
686 if (state != NORMAL)
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_),
693 icon_bounds_.y());
694 int x = GetMirroredXForRect(text_bounds_);
695 mirroring_context_->Initialize(x, text_bounds_.width());
696 InitContentsRenderTextIfNecessary();
698 if (!description_rendertext_) {
699 if (match_.answer) {
700 base::string16 text;
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();
707 if (text_field)
708 AppendAnswerText(space + text_field->text(), text_field->type());
709 text_field = match_.answer->second_line().status_text();
710 if (text_field)
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();
722 if (keyword_match) {
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,
729 false).release());
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,
736 true).release());
738 PaintMatch(*keyword_match, keyword_contents_rendertext_.get(),
739 keyword_description_rendertext_.get(), canvas, x);
743 void OmniboxResultView::AnimationProgressed(const gfx::Animation* animation) {
744 Layout();
745 SchedulePaint();
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)
753 .GetHeight();
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,
762 int text_type) {
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
768 // one RenderText.
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);