NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / ui / views / omnibox / omnibox_result_view.cc
blob9c830ddbef58a1499f27df54c33229ba6f089fca
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 "chrome/browser/ui/omnibox/omnibox_popup_model.h"
19 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
20 #include "chrome/browser/ui/views/omnibox/omnibox_result_view_model.h"
21 #include "grit/generated_resources.h"
22 #include "grit/theme_resources.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/theme_provider.h"
25 #include "ui/gfx/canvas.h"
26 #include "ui/gfx/color_utils.h"
27 #include "ui/gfx/image/image.h"
28 #include "ui/gfx/render_text.h"
29 #include "ui/gfx/text_elider.h"
30 #include "ui/gfx/text_utils.h"
31 #include "ui/native_theme/native_theme.h"
33 #if defined(OS_WIN)
34 #include "ui/native_theme/native_theme_win.h"
35 #endif
37 #if defined(USE_AURA)
38 #include "ui/native_theme/native_theme_aura.h"
39 #endif
41 namespace {
43 const base::char16 kEllipsis[] = { 0x2026, 0x0 };
45 // The minimum distance between the top and bottom of the {icon|text} and the
46 // top or bottom of the row.
47 const int kMinimumIconVerticalPadding = 2;
48 const int kMinimumTextVerticalPadding = 3;
50 } // namespace
52 ////////////////////////////////////////////////////////////////////////////////
53 // OmniboxResultView, public:
55 // Precalculated data used to draw a complete visual run within the match.
56 // This will include all or part of at least one, and possibly several,
57 // classifications.
58 struct OmniboxResultView::RunData {
59 RunData() : run_start(0), visual_order(0), is_rtl(false), pixel_width(0) {}
61 size_t run_start; // Offset within the match text where this run begins.
62 int visual_order; // Where this run occurs in visual order. The earliest
63 // run drawn is run 0.
64 bool is_rtl;
65 int pixel_width;
67 // Styled text classification pieces within this run, in logical order.
68 Classifications classifications;
71 // This class is a utility class for calculations affected by whether the result
72 // view is horizontally mirrored. The drawing functions can be written as if
73 // all drawing occurs left-to-right, and then use this class to get the actual
74 // coordinates to begin drawing onscreen.
75 class OmniboxResultView::MirroringContext {
76 public:
77 MirroringContext() : center_(0), right_(0) {}
79 // Tells the mirroring context to use the provided range as the physical
80 // bounds of the drawing region. When coordinate mirroring is needed, the
81 // mirror point will be the center of this range.
82 void Initialize(int x, int width) {
83 center_ = x + width / 2;
84 right_ = x + width;
87 // Given a logical range within the drawing region, returns the coordinate of
88 // the possibly-mirrored "left" side. (This functions exactly like
89 // View::MirroredLeftPointForRect().)
90 int mirrored_left_coord(int left, int right) const {
91 return base::i18n::IsRTL() ? (center_ + (center_ - right)) : left;
94 // Given a logical coordinate within the drawing region, returns the remaining
95 // width available.
96 int remaining_width(int x) const {
97 return right_ - x;
100 private:
101 int center_;
102 int right_;
104 DISALLOW_COPY_AND_ASSIGN(MirroringContext);
107 OmniboxResultView::OmniboxResultView(OmniboxResultViewModel* model,
108 int model_index,
109 LocationBarView* location_bar_view,
110 const gfx::FontList& font_list)
111 : edge_item_padding_(LocationBarView::GetItemPadding()),
112 item_padding_(LocationBarView::GetItemPadding()),
113 minimum_text_vertical_padding_(kMinimumTextVerticalPadding),
114 model_(model),
115 model_index_(model_index),
116 location_bar_view_(location_bar_view),
117 font_list_(font_list),
118 font_height_(
119 std::max(font_list.GetHeight(),
120 font_list.DeriveWithStyle(gfx::Font::BOLD).GetHeight())),
121 ellipsis_width_(gfx::GetStringWidth(base::string16(kEllipsis),
122 font_list)),
123 mirroring_context_(new MirroringContext()),
124 keyword_icon_(new views::ImageView()),
125 animation_(new gfx::SlideAnimation(this)) {
126 CHECK_GE(model_index, 0);
127 if (default_icon_size_ == 0) {
128 default_icon_size_ =
129 location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(
130 AutocompleteMatch::TypeToIcon(
131 AutocompleteMatchType::URL_WHAT_YOU_TYPED))->width();
133 keyword_icon_->set_owned_by_client();
134 keyword_icon_->EnableCanvasFlippingForRTLUI(true);
135 keyword_icon_->SetImage(GetKeywordIcon());
136 keyword_icon_->SizeToPreferredSize();
139 OmniboxResultView::~OmniboxResultView() {
142 SkColor OmniboxResultView::GetColor(
143 ResultViewState state,
144 ColorKind kind) const {
145 const ui::NativeTheme* theme = GetNativeTheme();
146 #if defined(OS_WIN)
147 if (theme == ui::NativeThemeWin::instance()) {
148 static bool win_initialized = false;
149 static SkColor win_colors[NUM_STATES][NUM_KINDS];
150 if (!win_initialized) {
151 win_colors[NORMAL][BACKGROUND] = color_utils::GetSysSkColor(COLOR_WINDOW);
152 win_colors[SELECTED][BACKGROUND] =
153 color_utils::GetSysSkColor(COLOR_HIGHLIGHT);
154 win_colors[NORMAL][TEXT] = color_utils::GetSysSkColor(COLOR_WINDOWTEXT);
155 win_colors[SELECTED][TEXT] =
156 color_utils::GetSysSkColor(COLOR_HIGHLIGHTTEXT);
157 CommonInitColors(theme, win_colors);
158 win_initialized = true;
160 return win_colors[state][kind];
162 #endif
163 static bool initialized = false;
164 static SkColor colors[NUM_STATES][NUM_KINDS];
165 if (!initialized) {
166 colors[NORMAL][BACKGROUND] = theme->GetSystemColor(
167 ui::NativeTheme::kColorId_TextfieldDefaultBackground);
168 colors[NORMAL][TEXT] = theme->GetSystemColor(
169 ui::NativeTheme::kColorId_TextfieldDefaultColor);
170 colors[NORMAL][URL] = SkColorSetARGB(0xff, 0x00, 0x99, 0x33);
171 colors[SELECTED][BACKGROUND] = theme->GetSystemColor(
172 ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused);
173 colors[SELECTED][TEXT] = theme->GetSystemColor(
174 ui::NativeTheme::kColorId_TextfieldSelectionColor);
175 colors[SELECTED][URL] = SkColorSetARGB(0xff, 0x00, 0x66, 0x22);
176 colors[HOVERED][URL] = SkColorSetARGB(0xff, 0x00, 0x66, 0x22);
177 CommonInitColors(theme, colors);
178 initialized = true;
180 return colors[state][kind];
183 void OmniboxResultView::SetMatch(const AutocompleteMatch& match) {
184 match_ = match;
185 animation_->Reset();
187 if (match.associated_keyword.get()) {
188 keyword_icon_->SetImage(GetKeywordIcon());
190 if (!keyword_icon_->parent())
191 AddChildView(keyword_icon_.get());
192 } else if (keyword_icon_->parent()) {
193 RemoveChildView(keyword_icon_.get());
196 Layout();
199 void OmniboxResultView::ShowKeyword(bool show_keyword) {
200 if (show_keyword)
201 animation_->Show();
202 else
203 animation_->Hide();
206 void OmniboxResultView::Invalidate() {
207 keyword_icon_->SetImage(GetKeywordIcon());
208 SchedulePaint();
211 gfx::Size OmniboxResultView::GetPreferredSize() {
212 return gfx::Size(0, std::max(
213 default_icon_size_ + (kMinimumIconVerticalPadding * 2),
214 GetTextHeight() + (minimum_text_vertical_padding_ * 2)));
217 ////////////////////////////////////////////////////////////////////////////////
218 // OmniboxResultView, protected:
220 OmniboxResultView::ResultViewState OmniboxResultView::GetState() const {
221 if (model_->IsSelectedIndex(model_index_))
222 return SELECTED;
223 return model_->IsHoveredIndex(model_index_) ? HOVERED : NORMAL;
226 int OmniboxResultView::GetTextHeight() const {
227 return font_height_;
230 void OmniboxResultView::PaintMatch(gfx::Canvas* canvas,
231 const AutocompleteMatch& match,
232 int x) {
233 x = DrawString(canvas, match.contents, match.contents_class, false, x,
234 text_bounds_.y());
236 // Paint the description.
237 // TODO(pkasting): Because we paint in multiple separate pieces, we can wind
238 // up with no space even for an ellipsis for one or both of these pieces.
239 // Instead, we should paint the entire match as a single long string. This
240 // would also let us use a more properly-localizable string than we get with
241 // just the IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR.
242 if (!match.description.empty()) {
243 base::string16 separator =
244 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR);
245 ACMatchClassifications classifications;
246 classifications.push_back(
247 ACMatchClassification(0, ACMatchClassification::NONE));
248 x = DrawString(canvas, separator, classifications, true, x,
249 text_bounds_.y());
251 DrawString(canvas, match.description, match.description_class, true, x,
252 text_bounds_.y());
256 // static
257 void OmniboxResultView::CommonInitColors(const ui::NativeTheme* theme,
258 SkColor colors[][NUM_KINDS]) {
259 colors[HOVERED][BACKGROUND] =
260 color_utils::AlphaBlend(colors[SELECTED][BACKGROUND],
261 colors[NORMAL][BACKGROUND], 64);
262 colors[HOVERED][TEXT] = colors[NORMAL][TEXT];
263 #if defined(USE_AURA)
264 const bool is_aura = theme == ui::NativeThemeAura::instance();
265 #else
266 const bool is_aura = false;
267 #endif
268 for (int i = 0; i < NUM_STATES; ++i) {
269 if (is_aura) {
270 colors[i][TEXT] =
271 color_utils::AlphaBlend(SK_ColorBLACK, colors[i][BACKGROUND], 0xdd);
272 colors[i][DIMMED_TEXT] =
273 color_utils::AlphaBlend(SK_ColorBLACK, colors[i][BACKGROUND], 0xbb);
274 } else {
275 colors[i][DIMMED_TEXT] =
276 color_utils::AlphaBlend(colors[i][TEXT], colors[i][BACKGROUND], 128);
277 colors[i][URL] = color_utils::GetReadableColor(SkColorSetRGB(0, 128, 0),
278 colors[i][BACKGROUND]);
281 // TODO(joi): Programmatically draw the dropdown border using
282 // this color as well. (Right now it's drawn as black with 25%
283 // alpha.)
284 colors[i][DIVIDER] =
285 color_utils::AlphaBlend(colors[i][TEXT], colors[i][BACKGROUND], 0x34);
289 // static
290 bool OmniboxResultView::SortRunsLogically(const RunData& lhs,
291 const RunData& rhs) {
292 return lhs.run_start < rhs.run_start;
295 // static
296 bool OmniboxResultView::SortRunsVisually(const RunData& lhs,
297 const RunData& rhs) {
298 return lhs.visual_order < rhs.visual_order;
301 // static
302 int OmniboxResultView::default_icon_size_ = 0;
304 gfx::ImageSkia OmniboxResultView::GetIcon() const {
305 const gfx::Image image = model_->GetIconIfExtensionMatch(model_index_);
306 if (!image.IsEmpty())
307 return image.AsImageSkia();
309 int icon = match_.starred ?
310 IDR_OMNIBOX_STAR : AutocompleteMatch::TypeToIcon(match_.type);
311 if (GetState() == SELECTED) {
312 switch (icon) {
313 case IDR_OMNIBOX_EXTENSION_APP:
314 icon = IDR_OMNIBOX_EXTENSION_APP_SELECTED;
315 break;
316 case IDR_OMNIBOX_HTTP:
317 icon = IDR_OMNIBOX_HTTP_SELECTED;
318 break;
319 case IDR_OMNIBOX_SEARCH:
320 icon = IDR_OMNIBOX_SEARCH_SELECTED;
321 break;
322 case IDR_OMNIBOX_STAR:
323 icon = IDR_OMNIBOX_STAR_SELECTED;
324 break;
325 default:
326 NOTREACHED();
327 break;
330 return *(location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(icon));
333 const gfx::ImageSkia* OmniboxResultView::GetKeywordIcon() const {
334 // NOTE: If we ever begin returning icons of varying size, then callers need
335 // to ensure that |keyword_icon_| is resized each time its image is reset.
336 return location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(
337 (GetState() == SELECTED) ? IDR_OMNIBOX_TTS_SELECTED : IDR_OMNIBOX_TTS);
340 int OmniboxResultView::DrawString(
341 gfx::Canvas* canvas,
342 const base::string16& text,
343 const ACMatchClassifications& classifications,
344 bool force_dim,
345 int x,
346 int y) {
347 if (text.empty())
348 return x;
350 // Check whether or not this text is a URL. URLs are always displayed LTR
351 // regardless of locale.
352 bool is_url = true;
353 for (ACMatchClassifications::const_iterator i(classifications.begin());
354 i != classifications.end(); ++i) {
355 if (!(i->style & ACMatchClassification::URL)) {
356 is_url = false;
357 break;
361 scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateInstance());
362 const size_t text_length = text.length();
363 render_text->SetText(text);
364 render_text->SetFontList(font_list_);
365 render_text->SetMultiline(false);
366 render_text->SetCursorEnabled(false);
367 if (is_url)
368 render_text->SetDirectionalityMode(gfx::DIRECTIONALITY_FORCE_LTR);
370 // Apply classifications.
371 for (size_t i = 0; i < classifications.size(); ++i) {
372 const size_t text_start = classifications[i].offset;
373 if (text_start >= text_length)
374 break;
376 const size_t text_end = (i < (classifications.size() - 1)) ?
377 std::min(classifications[i + 1].offset, text_length) : text_length;
378 const gfx::Range current_range(text_start, text_end);
380 // Calculate style-related data.
381 if (classifications[i].style & ACMatchClassification::MATCH)
382 render_text->ApplyStyle(gfx::BOLD, true, current_range);
384 ColorKind color_kind = TEXT;
385 if (classifications[i].style & ACMatchClassification::URL) {
386 color_kind = URL;
387 } else if (force_dim ||
388 (classifications[i].style & ACMatchClassification::DIM)) {
389 color_kind = DIMMED_TEXT;
391 render_text->ApplyColor(GetColor(GetState(), color_kind), current_range);
394 int remaining_width = mirroring_context_->remaining_width(x);
396 // No need to try anything if we can't even show a solitary character.
397 if ((text_length == 1) &&
398 (remaining_width < render_text->GetContentWidth())) {
399 return x;
402 if (render_text->GetContentWidth() > remaining_width)
403 render_text->SetElideBehavior(gfx::ELIDE_AT_END);
405 // Set the display rect to trigger eliding.
406 render_text->SetDisplayRect(gfx::Rect(
407 mirroring_context_->mirrored_left_coord(x, x + remaining_width), y,
408 remaining_width, height()));
409 render_text->set_clip_to_display_rect(true);
410 render_text->Draw(canvas);
412 // Need to call GetContentWidth again as the SetDisplayRect may modify it.
413 return x + render_text->GetContentWidth();
416 void OmniboxResultView::Layout() {
417 const gfx::ImageSkia icon = GetIcon();
419 icon_bounds_.SetRect(edge_item_padding_ +
420 ((icon.width() == default_icon_size_) ?
421 0 : LocationBarView::kIconInternalPadding),
422 (height() - icon.height()) / 2, icon.width(), icon.height());
424 int text_x = edge_item_padding_ + default_icon_size_ + item_padding_;
425 int text_width = width() - text_x - edge_item_padding_;
427 if (match_.associated_keyword.get()) {
428 const int kw_collapsed_size =
429 keyword_icon_->width() + edge_item_padding_;
430 const int max_kw_x = width() - kw_collapsed_size;
431 const int kw_x =
432 animation_->CurrentValueBetween(max_kw_x, edge_item_padding_);
433 const int kw_text_x = kw_x + keyword_icon_->width() + item_padding_;
435 text_width = kw_x - text_x - item_padding_;
436 keyword_text_bounds_.SetRect(
437 kw_text_x, 0,
438 std::max(width() - kw_text_x - edge_item_padding_, 0), height());
439 keyword_icon_->SetPosition(
440 gfx::Point(kw_x, (height() - keyword_icon_->height()) / 2));
443 text_bounds_.SetRect(text_x, 0, std::max(text_width, 0), height());
446 void OmniboxResultView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
447 animation_->SetSlideDuration(width() / 4);
450 void OmniboxResultView::OnPaint(gfx::Canvas* canvas) {
451 const ResultViewState state = GetState();
452 if (state != NORMAL)
453 canvas->DrawColor(GetColor(state, BACKGROUND));
455 if (!match_.associated_keyword.get() ||
456 keyword_icon_->x() > icon_bounds_.right()) {
457 // Paint the icon.
458 canvas->DrawImageInt(GetIcon(), GetMirroredXForRect(icon_bounds_),
459 icon_bounds_.y());
461 // Paint the text.
462 int x = GetMirroredXForRect(text_bounds_);
463 mirroring_context_->Initialize(x, text_bounds_.width());
464 PaintMatch(canvas, match_, x);
467 if (match_.associated_keyword.get()) {
468 // Paint the keyword text.
469 int x = GetMirroredXForRect(keyword_text_bounds_);
470 mirroring_context_->Initialize(x, keyword_text_bounds_.width());
471 PaintMatch(canvas, *match_.associated_keyword.get(), x);
475 void OmniboxResultView::AnimationProgressed(const gfx::Animation* animation) {
476 Layout();
477 SchedulePaint();