Home | History | Annotate | Download | only in omnibox
      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/text/text_elider.h"
     25 #include "ui/base/theme_provider.h"
     26 #include "ui/gfx/canvas.h"
     27 #include "ui/gfx/color_utils.h"
     28 #include "ui/gfx/image/image.h"
     29 #include "ui/gfx/render_text.h"
     30 #include "ui/native_theme/native_theme.h"
     31 
     32 #if defined(OS_WIN)
     33 #include "ui/native_theme/native_theme_win.h"
     34 #endif
     35 
     36 #if defined(USE_AURA)
     37 #include "ui/native_theme/native_theme_aura.h"
     38 #endif
     39 
     40 namespace {
     41 
     42 const char16 kEllipsis[] = { 0x2026, 0x0 };
     43 
     44 // The minimum distance between the top and bottom of the {icon|text} and the
     45 // top or bottom of the row.
     46 const int kMinimumIconVerticalPadding = 2;
     47 const int kMinimumTextVerticalPadding = 3;
     48 
     49 }  // namespace
     50 
     51 ////////////////////////////////////////////////////////////////////////////////
     52 // OmniboxResultView, public:
     53 
     54 // Precalculated data used to draw a complete visual run within the match.
     55 // This will include all or part of at least one, and possibly several,
     56 // classifications.
     57 struct OmniboxResultView::RunData {
     58   RunData() : run_start(0), visual_order(0), is_rtl(false), pixel_width(0) {}
     59 
     60   size_t run_start;  // Offset within the match text where this run begins.
     61   int visual_order;  // Where this run occurs in visual order.  The earliest
     62   // run drawn is run 0.
     63   bool is_rtl;
     64   int pixel_width;
     65 
     66   // Styled text classification pieces within this run, in logical order.
     67   Classifications classifications;
     68 };
     69 
     70 // This class is a utility class for calculations affected by whether the result
     71 // view is horizontally mirrored.  The drawing functions can be written as if
     72 // all drawing occurs left-to-right, and then use this class to get the actual
     73 // coordinates to begin drawing onscreen.
     74 class OmniboxResultView::MirroringContext {
     75  public:
     76   MirroringContext() : center_(0), right_(0) {}
     77 
     78   // Tells the mirroring context to use the provided range as the physical
     79   // bounds of the drawing region.  When coordinate mirroring is needed, the
     80   // mirror point will be the center of this range.
     81   void Initialize(int x, int width) {
     82     center_ = x + width / 2;
     83     right_ = x + width;
     84   }
     85 
     86   // Given a logical range within the drawing region, returns the coordinate of
     87   // the possibly-mirrored "left" side.  (This functions exactly like
     88   // View::MirroredLeftPointForRect().)
     89   int mirrored_left_coord(int left, int right) const {
     90     return base::i18n::IsRTL() ? (center_ + (center_ - right)) : left;
     91   }
     92 
     93   // Given a logical coordinate within the drawing region, returns the remaining
     94   // width available.
     95   int remaining_width(int x) const {
     96     return right_ - x;
     97   }
     98 
     99  private:
    100   int center_;
    101   int right_;
    102 
    103   DISALLOW_COPY_AND_ASSIGN(MirroringContext);
    104 };
    105 
    106 OmniboxResultView::OmniboxResultView(
    107     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_(std::max(font_list.GetHeight(),
    119                             font_list.DeriveFontList(gfx::BOLD).GetHeight())),
    120       ellipsis_width_(font_list.GetPrimaryFont().GetStringWidth(
    121           string16(kEllipsis))),
    122       mirroring_context_(new MirroringContext()),
    123       keyword_icon_(new views::ImageView()),
    124       animation_(new ui::SlideAnimation(this)) {
    125   CHECK_GE(model_index, 0);
    126   if (default_icon_size_ == 0) {
    127     default_icon_size_ =
    128         location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(
    129             AutocompleteMatch::TypeToIcon(
    130                 AutocompleteMatchType::URL_WHAT_YOU_TYPED))->width();
    131   }
    132   keyword_icon_->set_owned_by_client();
    133   keyword_icon_->EnableCanvasFlippingForRTLUI(true);
    134   keyword_icon_->SetImage(GetKeywordIcon());
    135   keyword_icon_->SizeToPreferredSize();
    136 }
    137 
    138 OmniboxResultView::~OmniboxResultView() {
    139 }
    140 
    141 SkColor OmniboxResultView::GetColor(
    142     ResultViewState state,
    143     ColorKind kind) const {
    144   const ui::NativeTheme* theme = GetNativeTheme();
    145 #if defined(OS_WIN)
    146   if (theme == ui::NativeThemeWin::instance()) {
    147     static bool win_initialized = false;
    148     static SkColor win_colors[NUM_STATES][NUM_KINDS];
    149     if (!win_initialized) {
    150       win_colors[NORMAL][BACKGROUND] = color_utils::GetSysSkColor(COLOR_WINDOW);
    151       win_colors[SELECTED][BACKGROUND] =
    152           color_utils::GetSysSkColor(COLOR_HIGHLIGHT);
    153       win_colors[NORMAL][TEXT] = color_utils::GetSysSkColor(COLOR_WINDOWTEXT);
    154       win_colors[SELECTED][TEXT] =
    155           color_utils::GetSysSkColor(COLOR_HIGHLIGHTTEXT);
    156       CommonInitColors(theme, win_colors);
    157       win_initialized = true;
    158     }
    159     return win_colors[state][kind];
    160   }
    161 #endif
    162   static bool initialized = false;
    163   static SkColor colors[NUM_STATES][NUM_KINDS];
    164   if (!initialized) {
    165     colors[NORMAL][BACKGROUND] = theme->GetSystemColor(
    166         ui::NativeTheme::kColorId_TextfieldDefaultBackground);
    167     colors[NORMAL][TEXT] = theme->GetSystemColor(
    168         ui::NativeTheme::kColorId_TextfieldDefaultColor);
    169     colors[NORMAL][URL] = SkColorSetARGB(0xff, 0x00, 0x99, 0x33);
    170     colors[SELECTED][BACKGROUND] = theme->GetSystemColor(
    171         ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused);
    172     colors[SELECTED][TEXT] = theme->GetSystemColor(
    173         ui::NativeTheme::kColorId_TextfieldSelectionColor);
    174     colors[SELECTED][URL] = SkColorSetARGB(0xff, 0x00, 0x66, 0x22);
    175     colors[HOVERED][URL] = SkColorSetARGB(0xff, 0x00, 0x66, 0x22);
    176     CommonInitColors(theme, colors);
    177     initialized = true;
    178   }
    179   return colors[state][kind];
    180 }
    181 
    182 void OmniboxResultView::SetMatch(const AutocompleteMatch& match) {
    183   match_ = match;
    184   animation_->Reset();
    185 
    186   if (match.associated_keyword.get()) {
    187     keyword_icon_->SetImage(GetKeywordIcon());
    188 
    189     if (!keyword_icon_->parent())
    190       AddChildView(keyword_icon_.get());
    191   } else if (keyword_icon_->parent()) {
    192     RemoveChildView(keyword_icon_.get());
    193   }
    194 
    195   Layout();
    196 }
    197 
    198 void OmniboxResultView::ShowKeyword(bool show_keyword) {
    199   if (show_keyword)
    200     animation_->Show();
    201   else
    202     animation_->Hide();
    203 }
    204 
    205 void OmniboxResultView::Invalidate() {
    206   keyword_icon_->SetImage(GetKeywordIcon());
    207   SchedulePaint();
    208 }
    209 
    210 gfx::Size OmniboxResultView::GetPreferredSize() {
    211   return gfx::Size(0, std::max(
    212       default_icon_size_ + (kMinimumIconVerticalPadding * 2),
    213       GetTextHeight() + (minimum_text_vertical_padding_ * 2)));
    214 }
    215 
    216 ////////////////////////////////////////////////////////////////////////////////
    217 // OmniboxResultView, protected:
    218 
    219 OmniboxResultView::ResultViewState OmniboxResultView::GetState() const {
    220   if (model_->IsSelectedIndex(model_index_))
    221     return SELECTED;
    222   return model_->IsHoveredIndex(model_index_) ? HOVERED : NORMAL;
    223 }
    224 
    225 int OmniboxResultView::GetTextHeight() const {
    226   return font_height_;
    227 }
    228 
    229 void OmniboxResultView::PaintMatch(gfx::Canvas* canvas,
    230                                    const AutocompleteMatch& match,
    231                                    int x) {
    232   x = DrawString(canvas, match.contents, match.contents_class, false, x,
    233                  text_bounds_.y());
    234 
    235   // Paint the description.
    236   // TODO(pkasting): Because we paint in multiple separate pieces, we can wind
    237   // up with no space even for an ellipsis for one or both of these pieces.
    238   // Instead, we should paint the entire match as a single long string.  This
    239   // would also let us use a more properly-localizable string than we get with
    240   // just the IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR.
    241   if (!match.description.empty()) {
    242     string16 separator =
    243         l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR);
    244     ACMatchClassifications classifications;
    245     classifications.push_back(
    246         ACMatchClassification(0, ACMatchClassification::NONE));
    247     x = DrawString(canvas, separator, classifications, true, x,
    248                    text_bounds_.y());
    249 
    250     DrawString(canvas, match.description, match.description_class, true, x,
    251                text_bounds_.y());
    252   }
    253 }
    254 
    255 // static
    256 void OmniboxResultView::CommonInitColors(const ui::NativeTheme* theme,
    257                                          SkColor colors[][NUM_KINDS]) {
    258   colors[HOVERED][BACKGROUND] =
    259       color_utils::AlphaBlend(colors[SELECTED][BACKGROUND],
    260                               colors[NORMAL][BACKGROUND], 64);
    261   colors[HOVERED][TEXT] = colors[NORMAL][TEXT];
    262 #if defined(USE_AURA)
    263   const bool is_aura = theme == ui::NativeThemeAura::instance();
    264 #else
    265   const bool is_aura = false;
    266 #endif
    267   for (int i = 0; i < NUM_STATES; ++i) {
    268     if (is_aura) {
    269       colors[i][TEXT] =
    270           color_utils::AlphaBlend(SK_ColorBLACK, colors[i][BACKGROUND], 0xdd);
    271       colors[i][DIMMED_TEXT] =
    272           color_utils::AlphaBlend(SK_ColorBLACK, colors[i][BACKGROUND], 0xbb);
    273     } else {
    274       colors[i][DIMMED_TEXT] =
    275           color_utils::AlphaBlend(colors[i][TEXT], colors[i][BACKGROUND], 128);
    276       colors[i][URL] = color_utils::GetReadableColor(SkColorSetRGB(0, 128, 0),
    277                                                      colors[i][BACKGROUND]);
    278     }
    279 
    280     // TODO(joi): Programmatically draw the dropdown border using
    281     // this color as well. (Right now it's drawn as black with 25%
    282     // alpha.)
    283     colors[i][DIVIDER] =
    284         color_utils::AlphaBlend(colors[i][TEXT], colors[i][BACKGROUND], 0x34);
    285   }
    286 }
    287 
    288 // static
    289 bool OmniboxResultView::SortRunsLogically(const RunData& lhs,
    290                                           const RunData& rhs) {
    291   return lhs.run_start < rhs.run_start;
    292 }
    293 
    294 // static
    295 bool OmniboxResultView::SortRunsVisually(const RunData& lhs,
    296                                          const RunData& rhs) {
    297   return lhs.visual_order < rhs.visual_order;
    298 }
    299 
    300 // static
    301 int OmniboxResultView::default_icon_size_ = 0;
    302 
    303 gfx::ImageSkia OmniboxResultView::GetIcon() const {
    304   const gfx::Image image = model_->GetIconIfExtensionMatch(model_index_);
    305   if (!image.IsEmpty())
    306     return image.AsImageSkia();
    307 
    308   int icon = match_.starred ?
    309       IDR_OMNIBOX_STAR : AutocompleteMatch::TypeToIcon(match_.type);
    310   if (GetState() == SELECTED) {
    311     switch (icon) {
    312       case IDR_OMNIBOX_EXTENSION_APP:
    313         icon = IDR_OMNIBOX_EXTENSION_APP_SELECTED;
    314         break;
    315       case IDR_OMNIBOX_HTTP:
    316         icon = IDR_OMNIBOX_HTTP_SELECTED;
    317         break;
    318       case IDR_OMNIBOX_SEARCH:
    319         icon = IDR_OMNIBOX_SEARCH_SELECTED;
    320         break;
    321       case IDR_OMNIBOX_STAR:
    322         icon = IDR_OMNIBOX_STAR_SELECTED;
    323         break;
    324       default:
    325         NOTREACHED();
    326         break;
    327     }
    328   }
    329   return *(location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(icon));
    330 }
    331 
    332 const gfx::ImageSkia* OmniboxResultView::GetKeywordIcon() const {
    333   // NOTE: If we ever begin returning icons of varying size, then callers need
    334   // to ensure that |keyword_icon_| is resized each time its image is reset.
    335   return location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(
    336       (GetState() == SELECTED) ? IDR_OMNIBOX_TTS_SELECTED : IDR_OMNIBOX_TTS);
    337 }
    338 
    339 int OmniboxResultView::DrawString(
    340     gfx::Canvas* canvas,
    341     const string16& text,
    342     const ACMatchClassifications& classifications,
    343     bool force_dim,
    344     int x,
    345     int y) {
    346   if (text.empty())
    347     return x;
    348 
    349   // Check whether or not this text is a URL.  URLs are always displayed LTR
    350   // regardless of locale.
    351   bool is_url = true;
    352   for (ACMatchClassifications::const_iterator i(classifications.begin());
    353        i != classifications.end(); ++i) {
    354     if (!(i->style & ACMatchClassification::URL)) {
    355       is_url = false;
    356       break;
    357     }
    358   }
    359 
    360   // Split the text into visual runs.  We do this first so that we don't need to
    361   // worry about whether our eliding might change the visual display in
    362   // unintended ways, e.g. by removing directional markings or by adding an
    363   // ellipsis that's not enclosed in appropriate markings.
    364   base::i18n::BiDiLineIterator bidi_line;
    365   if (!bidi_line.Open(text, base::i18n::IsRTL(), is_url))
    366     return x;
    367   const int num_runs = bidi_line.CountRuns();
    368   ScopedVector<gfx::RenderText> render_texts;
    369   Runs runs;
    370   for (int run = 0; run < num_runs; ++run) {
    371     int run_start_int = 0, run_length_int = 0;
    372     // The index we pass to GetVisualRun corresponds to the position of the run
    373     // in the displayed text. For example, the string "Google in HEBREW" (where
    374     // HEBREW is text in the Hebrew language) has two runs: "Google in " which
    375     // is an LTR run, and "HEBREW" which is an RTL run. In an LTR context, the
    376     // run "Google in " has the index 0 (since it is the leftmost run
    377     // displayed). In an RTL context, the same run has the index 1 because it
    378     // is the rightmost run. This is why the order in which we traverse the
    379     // runs is different depending on the locale direction.
    380     const UBiDiDirection run_direction = bidi_line.GetVisualRun(
    381         (base::i18n::IsRTL() && !is_url) ? (num_runs - run - 1) : run,
    382         &run_start_int, &run_length_int);
    383     DCHECK_GT(run_length_int, 0);
    384     runs.push_back(RunData());
    385     RunData* current_run = &runs.back();
    386     current_run->run_start = run_start_int;
    387     const size_t run_end = current_run->run_start + run_length_int;
    388     current_run->visual_order = run;
    389     current_run->is_rtl = !is_url && (run_direction == UBIDI_RTL);
    390 
    391     // Compute classifications for this run.
    392     for (size_t i = 0; i < classifications.size(); ++i) {
    393       const size_t text_start =
    394           std::max(classifications[i].offset, current_run->run_start);
    395       if (text_start >= run_end)
    396         break;  // We're past the last classification in the run.
    397 
    398       const size_t text_end = (i < (classifications.size() - 1)) ?
    399           std::min(classifications[i + 1].offset, run_end) : run_end;
    400       if (text_end <= current_run->run_start)
    401         continue;  // We haven't reached the first classification in the run.
    402 
    403       render_texts.push_back(gfx::RenderText::CreateInstance());
    404       gfx::RenderText* render_text = render_texts.back();
    405       current_run->classifications.push_back(render_text);
    406       render_text->SetText(text.substr(text_start, text_end - text_start));
    407       render_text->SetFontList(font_list_);
    408 
    409       // Calculate style-related data.
    410       if (classifications[i].style & ACMatchClassification::MATCH)
    411         render_text->SetStyle(gfx::BOLD, true);
    412       const ResultViewState state = GetState();
    413       if (classifications[i].style & ACMatchClassification::URL)
    414         render_text->SetColor(GetColor(state, URL));
    415       else if (classifications[i].style & ACMatchClassification::DIM)
    416         render_text->SetColor(GetColor(state, DIMMED_TEXT));
    417       else
    418         render_text->SetColor(GetColor(state, force_dim ? DIMMED_TEXT : TEXT));
    419 
    420       current_run->pixel_width += render_text->GetStringSize().width();
    421     }
    422     DCHECK(!current_run->classifications.empty());
    423   }
    424   DCHECK(!runs.empty());
    425 
    426   // Sort into logical order so we can elide logically.
    427   std::sort(runs.begin(), runs.end(), &SortRunsLogically);
    428 
    429   // Now determine what to elide, if anything.  Several subtle points:
    430   //   * Because we have the run data, we can get edge cases correct, like
    431   //     whether to place an ellipsis before or after the end of a run when the
    432   //     text needs to be elided at the run boundary.
    433   //   * The "or one before it" comments below refer to cases where an earlier
    434   //     classification fits completely, but leaves too little space for an
    435   //     ellipsis that turns out to be needed later.  These cases are commented
    436   //     more completely in Elide().
    437   int remaining_width = mirroring_context_->remaining_width(x);
    438   for (Runs::iterator i(runs.begin()); i != runs.end(); ++i) {
    439     if (i->pixel_width > remaining_width) {
    440       // This run or one before it needs to be elided.
    441       for (Classifications::iterator j(i->classifications.begin());
    442            j != i->classifications.end(); ++j) {
    443         const int width = (*j)->GetStringSize().width();
    444         if (width > remaining_width) {
    445           // This classification or one before it needs to be elided.  Erase all
    446           // further classifications and runs so Elide() can simply reverse-
    447           // iterate over everything to find the specific classification to
    448           // elide.
    449           i->classifications.erase(++j, i->classifications.end());
    450           runs.erase(++i, runs.end());
    451           Elide(&runs, remaining_width);
    452           break;
    453         }
    454         remaining_width -= width;
    455       }
    456       break;
    457     }
    458     remaining_width -= i->pixel_width;
    459   }
    460 
    461   // Sort back into visual order so we can display the runs correctly.
    462   std::sort(runs.begin(), runs.end(), &SortRunsVisually);
    463 
    464   // Draw the runs.
    465   for (Runs::iterator i(runs.begin()); i != runs.end(); ++i) {
    466     const bool reverse_visible_order = (i->is_rtl != base::i18n::IsRTL());
    467     if (reverse_visible_order)
    468       std::reverse(i->classifications.begin(), i->classifications.end());
    469     for (Classifications::const_iterator j(i->classifications.begin());
    470          j != i->classifications.end(); ++j) {
    471       const gfx::Size size = (*j)->GetStringSize();
    472       // Align the text runs to a common baseline.
    473       const gfx::Rect rect(
    474           mirroring_context_->mirrored_left_coord(x, x + size.width()),
    475           y + font_list_.GetBaseline() - (*j)->GetBaseline(),
    476           size.width(), size.height());
    477       (*j)->SetDisplayRect(rect);
    478       (*j)->Draw(canvas);
    479       x += size.width();
    480     }
    481   }
    482 
    483   return x;
    484 }
    485 
    486 void OmniboxResultView::Elide(Runs* runs, int remaining_width) const {
    487   // The complexity of this function is due to edge cases like the following:
    488   // We have 100 px of available space, an initial classification that takes 86
    489   // px, and a font that has a 15 px wide ellipsis character.  Now if the first
    490   // classification is followed by several very narrow classifications (e.g. 3
    491   // px wide each), we don't know whether we need to elide or not at the time we
    492   // see the first classification -- it depends on how many subsequent
    493   // classifications follow, and some of those may be in the next run (or
    494   // several runs!).  This is why instead we let our caller move forward until
    495   // we know we definitely need to elide, and then in this function we move
    496   // backward again until we find a string that we can successfully do the
    497   // eliding on.
    498   bool first_classification = true;
    499   for (Runs::reverse_iterator i(runs->rbegin()); i != runs->rend(); ++i) {
    500     for (Classifications::reverse_iterator j(i->classifications.rbegin());
    501          j != i->classifications.rend(); ++j) {
    502       if (!first_classification) {
    503         // We also add this classification's width (sans ellipsis) back to the
    504         // available width since we want to consider the available space we'll
    505         // have when we draw this classification.
    506         remaining_width += (*j)->GetStringSize().width();
    507 
    508         // For all but the first classification we consider, we need to append
    509         // an ellipsis, since there isn't enough room to draw it after this
    510         // classification.
    511         (*j)->SetText((*j)->text() + kEllipsis);
    512       }
    513       first_classification = false;
    514 
    515       // Can we fit at least an ellipsis?
    516       gfx::Font font((*j)->GetStyle(gfx::BOLD) ?
    517           (*j)->GetPrimaryFont().DeriveFont(0, gfx::Font::BOLD) :
    518           (*j)->GetPrimaryFont());
    519       string16 elided_text(
    520           ui::ElideText((*j)->text(), font, remaining_width, ui::ELIDE_AT_END));
    521       Classifications::reverse_iterator prior(j + 1);
    522       const bool on_first_classification = (prior == i->classifications.rend());
    523       if (elided_text.empty() && (remaining_width >= ellipsis_width_) &&
    524           on_first_classification) {
    525         // Edge case: This classification is bold, we can't fit a bold ellipsis
    526         // but we can fit a normal one, and this is the first classification in
    527         // the run.  We should display a lone normal ellipsis, because appending
    528         // one to the end of the previous run might put it in the wrong visual
    529         // location (if the previous run is reversed from the normal visual
    530         // order).
    531         // NOTE: If this isn't the first classification in the run, we don't
    532         // need to bother with this; see note below.
    533         elided_text = kEllipsis;
    534       }
    535       if (!elided_text.empty()) {
    536         // Success.  Elide this classification and stop.
    537         (*j)->SetText(elided_text);
    538 
    539         // If we could only fit an ellipsis, then only make it bold if there was
    540         // an immediate prior classification in this run that was also bold, or
    541         // it will look orphaned.
    542         if ((*j)->GetStyle(gfx::BOLD) && (elided_text.length() == 1) &&
    543             (on_first_classification || !(*prior)->GetStyle(gfx::BOLD)))
    544           (*j)->SetStyle(gfx::BOLD, false);
    545 
    546         // Erase any other classifications that come after the elided one.
    547         i->classifications.erase(j.base(), i->classifications.end());
    548         runs->erase(i.base(), runs->end());
    549         return;
    550       }
    551 
    552       // We couldn't fit an ellipsis.  Move back one classification,
    553       // append an ellipsis, and try again.
    554       // NOTE: In the edge case that a bold ellipsis doesn't fit but a
    555       // normal one would, and we reach here, then there is a previous
    556       // classification in this run, and so either:
    557       //   * It's normal, and will be able to draw successfully with the
    558       //     ellipsis we'll append to it, or
    559       //   * It is also bold, in which case we don't want to fall back
    560       //     to a normal ellipsis anyway (see comment above).
    561     }
    562   }
    563 
    564   // We couldn't draw anything.
    565   runs->clear();
    566 }
    567 
    568 void OmniboxResultView::Layout() {
    569   const gfx::ImageSkia icon = GetIcon();
    570 
    571   icon_bounds_.SetRect(edge_item_padding_ +
    572       ((icon.width() == default_icon_size_) ?
    573           0 : LocationBarView::kIconInternalPadding),
    574       (height() - icon.height()) / 2, icon.width(), icon.height());
    575 
    576   int text_x = edge_item_padding_ + default_icon_size_ + item_padding_;
    577   int text_height = GetTextHeight();
    578   int text_width;
    579 
    580   if (match_.associated_keyword.get()) {
    581     const int kw_collapsed_size =
    582         keyword_icon_->width() + edge_item_padding_;
    583     const int max_kw_x = width() - kw_collapsed_size;
    584     const int kw_x =
    585         animation_->CurrentValueBetween(max_kw_x, edge_item_padding_);
    586     const int kw_text_x = kw_x + keyword_icon_->width() + item_padding_;
    587 
    588     text_width = kw_x - text_x - item_padding_;
    589     keyword_text_bounds_.SetRect(kw_text_x, 0,
    590         std::max(width() - kw_text_x - edge_item_padding_, 0), text_height);
    591     keyword_icon_->SetPosition(gfx::Point(kw_x,
    592         (height() - keyword_icon_->height()) / 2));
    593   } else {
    594     text_width = width() - text_x - edge_item_padding_;
    595   }
    596 
    597   text_bounds_.SetRect(text_x, std::max(0, (height() - text_height) / 2),
    598       std::max(text_width, 0), text_height);
    599 }
    600 
    601 void OmniboxResultView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
    602   animation_->SetSlideDuration(width() / 4);
    603 }
    604 
    605 void OmniboxResultView::OnPaint(gfx::Canvas* canvas) {
    606   const ResultViewState state = GetState();
    607   if (state != NORMAL)
    608     canvas->DrawColor(GetColor(state, BACKGROUND));
    609 
    610   if (!match_.associated_keyword.get() ||
    611       keyword_icon_->x() > icon_bounds_.right()) {
    612     // Paint the icon.
    613     canvas->DrawImageInt(GetIcon(), GetMirroredXForRect(icon_bounds_),
    614                          icon_bounds_.y());
    615 
    616     // Paint the text.
    617     int x = GetMirroredXForRect(text_bounds_);
    618     mirroring_context_->Initialize(x, text_bounds_.width());
    619     PaintMatch(canvas, match_, x);
    620   }
    621 
    622   if (match_.associated_keyword.get()) {
    623     // Paint the keyword text.
    624     int x = GetMirroredXForRect(keyword_text_bounds_);
    625     mirroring_context_->Initialize(x, keyword_text_bounds_.width());
    626     PaintMatch(canvas, *match_.associated_keyword.get(), x);
    627   }
    628 }
    629 
    630 void OmniboxResultView::AnimationProgressed(const ui::Animation* animation) {
    631   Layout();
    632   SchedulePaint();
    633 }
    634