Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / omnibox / omnibox_result_view.cc
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.
4
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
11
12 #include "chrome/browser/ui/views/omnibox/omnibox_result_view.h"
13
14 #include <algorithm>  // NOLINT
15
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"
32
33 #if defined(OS_WIN)
34 #include "ui/native_theme/native_theme_win.h"
35 #endif
36
37 #if defined(USE_AURA)
38 #include "ui/native_theme/native_theme_aura.h"
39 #endif
40
41 namespace {
42
43 const base::char16 kEllipsis[] = { 0x2026, 0x0 };
44
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;
49
50 }  // namespace
51
52 ////////////////////////////////////////////////////////////////////////////////
53 // OmniboxResultView, public:
54
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) {}
60
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;
66
67   // Styled text classification pieces within this run, in logical order.
68   Classifications classifications;
69 };
70
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) {}
78
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;
85   }
86
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;
92   }
93
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;
98   }
99
100  private:
101   int center_;
102   int right_;
103
104   DISALLOW_COPY_AND_ASSIGN(MirroringContext);
105 };
106
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();
132   }
133   keyword_icon_->set_owned_by_client();
134   keyword_icon_->EnableCanvasFlippingForRTLUI(true);
135   keyword_icon_->SetImage(GetKeywordIcon());
136   keyword_icon_->SizeToPreferredSize();
137 }
138
139 OmniboxResultView::~OmniboxResultView() {
140 }
141
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;
159     }
160     return win_colors[state][kind];
161   }
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;
179   }
180   return colors[state][kind];
181 }
182
183 void OmniboxResultView::SetMatch(const AutocompleteMatch& match) {
184   match_ = match;
185   animation_->Reset();
186
187   if (match.associated_keyword.get()) {
188     keyword_icon_->SetImage(GetKeywordIcon());
189
190     if (!keyword_icon_->parent())
191       AddChildView(keyword_icon_.get());
192   } else if (keyword_icon_->parent()) {
193     RemoveChildView(keyword_icon_.get());
194   }
195
196   Layout();
197 }
198
199 void OmniboxResultView::ShowKeyword(bool show_keyword) {
200   if (show_keyword)
201     animation_->Show();
202   else
203     animation_->Hide();
204 }
205
206 void OmniboxResultView::Invalidate() {
207   keyword_icon_->SetImage(GetKeywordIcon());
208   SchedulePaint();
209 }
210
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)));
215 }
216
217 ////////////////////////////////////////////////////////////////////////////////
218 // OmniboxResultView, protected:
219
220 OmniboxResultView::ResultViewState OmniboxResultView::GetState() const {
221   if (model_->IsSelectedIndex(model_index_))
222     return SELECTED;
223   return model_->IsHoveredIndex(model_index_) ? HOVERED : NORMAL;
224 }
225
226 int OmniboxResultView::GetTextHeight() const {
227   return font_height_;
228 }
229
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());
235
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());
250
251     DrawString(canvas, match.description, match.description_class, true, x,
252                text_bounds_.y());
253   }
254 }
255
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]);
279     }
280
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);
286   }
287 }
288
289 // static
290 bool OmniboxResultView::SortRunsLogically(const RunData& lhs,
291                                           const RunData& rhs) {
292   return lhs.run_start < rhs.run_start;
293 }
294
295 // static
296 bool OmniboxResultView::SortRunsVisually(const RunData& lhs,
297                                          const RunData& rhs) {
298   return lhs.visual_order < rhs.visual_order;
299 }
300
301 // static
302 int OmniboxResultView::default_icon_size_ = 0;
303
304 gfx::ImageSkia OmniboxResultView::GetIcon() const {
305   const gfx::Image image = model_->GetIconIfExtensionMatch(model_index_);
306   if (!image.IsEmpty())
307     return image.AsImageSkia();
308
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;
328     }
329   }
330   return *(location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(icon));
331 }
332
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);
338 }
339
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;
349
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;
358     }
359   }
360
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);
369
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;
375
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);
379
380     // Calculate style-related data.
381     if (classifications[i].style & ACMatchClassification::MATCH)
382       render_text->ApplyStyle(gfx::BOLD, true, current_range);
383
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;
390     }
391     render_text->ApplyColor(GetColor(GetState(), color_kind), current_range);
392   }
393
394   int remaining_width = mirroring_context_->remaining_width(x);
395
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;
400   }
401
402   if (render_text->GetContentWidth() > remaining_width)
403     render_text->SetElideBehavior(gfx::ELIDE_AT_END);
404
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);
411
412   // Need to call GetContentWidth again as the SetDisplayRect may modify it.
413   return x + render_text->GetContentWidth();
414 }
415
416 void OmniboxResultView::Layout() {
417   const gfx::ImageSkia icon = GetIcon();
418
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());
423
424   int text_x = edge_item_padding_ + default_icon_size_ + item_padding_;
425   int text_width = width() - text_x - edge_item_padding_;
426
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_;
434
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));
441   }
442
443   text_bounds_.SetRect(text_x, 0, std::max(text_width, 0), height());
444 }
445
446 void OmniboxResultView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
447   animation_->SetSlideDuration(width() / 4);
448 }
449
450 void OmniboxResultView::OnPaint(gfx::Canvas* canvas) {
451   const ResultViewState state = GetState();
452   if (state != NORMAL)
453     canvas->DrawColor(GetColor(state, BACKGROUND));
454
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());
460
461     // Paint the text.
462     int x = GetMirroredXForRect(text_bounds_);
463     mirroring_context_->Initialize(x, text_bounds_.width());
464     PaintMatch(canvas, match_, x);
465   }
466
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);
472   }
473 }
474
475 void OmniboxResultView::AnimationProgressed(const gfx::Animation* animation) {
476   Layout();
477   SchedulePaint();
478 }