| Index: ui/views/controls/styled_label.cc
 | 
| diff --git a/ui/views/controls/styled_label.cc b/ui/views/controls/styled_label.cc
 | 
| index d0fda3de315db4386b7c033f6a440112ae119226..b0e46a45ec3016f85b3f58f6f6358b8129c27027 100644
 | 
| --- a/ui/views/controls/styled_label.cc
 | 
| +++ b/ui/views/controls/styled_label.cc
 | 
| @@ -23,33 +23,82 @@ int CalculateLineHeight() {
 | 
|    return label.GetPreferredSize().height();
 | 
|  }
 | 
|  
 | 
| +scoped_ptr<View> CreateLabelRange(const string16& text,
 | 
| +                                  const StyledLabel::RangeStyleInfo& style_info,
 | 
| +                                  views::LinkListener* link_listener) {
 | 
| +  scoped_ptr<Label> result;
 | 
| +
 | 
| +  if (style_info.is_link) {
 | 
| +    Link* link = new Link(text);
 | 
| +    link->set_listener(link_listener);
 | 
| +    link->SetUnderline((style_info.font_style & gfx::Font::UNDERLINE) != 0);
 | 
| +    result.reset(link);
 | 
| +  } else {
 | 
| +    Label* label = new Label(text);
 | 
| +    // Give the label a focus border so that its preferred size matches
 | 
| +    // links' preferred sizes
 | 
| +    label->SetHasFocusBorder(true);
 | 
| +
 | 
| +    result.reset(label);
 | 
| +  }
 | 
| +
 | 
| +  if (!style_info.tooltip.empty())
 | 
| +    result->SetTooltipText(style_info.tooltip);
 | 
| +  if (style_info.font_style != gfx::Font::NORMAL)
 | 
| +    result->SetFont(result->font().DeriveFont(0, style_info.font_style));
 | 
| +
 | 
| +  return scoped_ptr<View>(result.release());
 | 
| +}
 | 
| +
 | 
|  }  // namespace
 | 
|  
 | 
| -bool StyledLabel::LinkRange::operator<(
 | 
| -    const StyledLabel::LinkRange& other) const {
 | 
| +
 | 
| +StyledLabel::RangeStyleInfo::RangeStyleInfo()
 | 
| +    : font_style(gfx::Font::NORMAL),
 | 
| +      disable_line_wrapping(false),
 | 
| +      is_link(false) {
 | 
| +}
 | 
| +
 | 
| +StyledLabel::RangeStyleInfo::~RangeStyleInfo() {}
 | 
| +
 | 
| +// static
 | 
| +StyledLabel::RangeStyleInfo StyledLabel::RangeStyleInfo::CreateForLink() {
 | 
| +  RangeStyleInfo result;
 | 
| +  result.disable_line_wrapping = true;
 | 
| +  result.is_link = true;
 | 
| +  result.font_style = gfx::Font::UNDERLINE;
 | 
| +  return result;
 | 
| +}
 | 
| +
 | 
| +bool StyledLabel::StyleRange::operator<(
 | 
| +    const StyledLabel::StyleRange& other) const {
 | 
|    // Intentionally reversed so the priority queue is sorted by smallest first.
 | 
|    return range.start() > other.range.start();
 | 
|  }
 | 
|  
 | 
|  StyledLabel::StyledLabel(const string16& text, StyledLabelListener* listener)
 | 
| -    : text_(text),
 | 
| -      listener_(listener) {}
 | 
| +    : listener_(listener) {
 | 
| +  TrimWhitespace(text, TRIM_TRAILING, &text_);
 | 
| +}
 | 
|  
 | 
|  StyledLabel::~StyledLabel() {}
 | 
|  
 | 
|  void StyledLabel::SetText(const string16& text) {
 | 
|    text_ = text;
 | 
|    calculated_size_ = gfx::Size();
 | 
| -  link_ranges_ = std::priority_queue<LinkRange>();
 | 
| +  style_ranges_ = std::priority_queue<StyleRange>();
 | 
|    RemoveAllChildViews(true);
 | 
|    PreferredSizeChanged();
 | 
|  }
 | 
|  
 | 
| -void StyledLabel::AddLink(const ui::Range& range) {
 | 
| +void StyledLabel::AddStyleRange(const ui::Range& range,
 | 
| +                                const RangeStyleInfo& style_info) {
 | 
|    DCHECK(!range.is_reversed());
 | 
|    DCHECK(!range.is_empty());
 | 
|    DCHECK(ui::Range(0, text_.size()).Contains(range));
 | 
| -  link_ranges_.push(LinkRange(range));
 | 
| +
 | 
| +  style_ranges_.push(StyleRange(range, style_info));
 | 
| +
 | 
|    calculated_size_ = gfx::Size();
 | 
|    PreferredSizeChanged();
 | 
|  }
 | 
| @@ -94,34 +143,53 @@ int StyledLabel::CalculateAndDoLayout(int width, bool dry_run) {
 | 
|    int x = 0;
 | 
|  
 | 
|    string16 remaining_string = text_;
 | 
| -  std::priority_queue<LinkRange> link_ranges = link_ranges_;
 | 
| +  std::priority_queue<StyleRange> style_ranges = style_ranges_;
 | 
|  
 | 
|    // Iterate over the text, creating a bunch of labels and links and laying them
 | 
|    // out in the appropriate positions.
 | 
|    while (!remaining_string.empty()) {
 | 
| -    // Don't put whitespace at beginning of a line.
 | 
| -    if (x == 0)
 | 
| +    // Don't put whitespace at beginning of a line with an exception for the
 | 
| +    // first line (so the text's leading whitespace is respected).
 | 
| +    if (x == 0 && line > 0)
 | 
|        TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string);
 | 
|  
 | 
|      ui::Range range(ui::Range::InvalidRange());
 | 
| -    if (!link_ranges.empty())
 | 
| -      range = link_ranges.top().range;
 | 
| +    if (!style_ranges.empty())
 | 
| +      range = style_ranges.top().range;
 | 
| +
 | 
| +    const size_t position = text_.size() - remaining_string.size();
 | 
|  
 | 
|      const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height);
 | 
|      std::vector<string16> substrings;
 | 
| +    gfx::Font text_font;
 | 
| +    // If the start of the remaining text is inside a styled range, the font
 | 
| +    // style may differ from the base font. The font specified by the range
 | 
| +    // should be used when eliding text.
 | 
| +    if (position >= range.start()) {
 | 
| +      text_font =
 | 
| +          text_font.DeriveFont(0, style_ranges.top().style_info.font_style);
 | 
| +    }
 | 
|      ui::ElideRectangleText(remaining_string,
 | 
| -                           gfx::Font(),
 | 
| +                           text_font,
 | 
|                             chunk_bounds.width(),
 | 
|                             chunk_bounds.height(),
 | 
|                             ui::IGNORE_LONG_WORDS,
 | 
|                             &substrings);
 | 
|  
 | 
| +    DCHECK(!substrings.empty());
 | 
|      string16 chunk = substrings[0];
 | 
|      if (chunk.empty()) {
 | 
| -      // Nothing fit on this line. Start a new line. If x is 0, there's no room
 | 
| -      // for anything. Just abort.
 | 
| -      if (x == 0)
 | 
| +      // Nothing fits on this line. Start a new line.
 | 
| +      // If x is 0, first line may have leading whitespace that doesn't fit in a
 | 
| +      // single line, so try trimming those. Otherwise there is no room for
 | 
| +      // anything; abort.
 | 
| +      if (x == 0) {
 | 
| +        if (line == 0) {
 | 
| +          TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string);
 | 
| +          continue;
 | 
| +        }
 | 
|          break;
 | 
| +      }
 | 
|  
 | 
|        x = 0;
 | 
|        line++;
 | 
| @@ -129,33 +197,32 @@ int StyledLabel::CalculateAndDoLayout(int width, bool dry_run) {
 | 
|      }
 | 
|  
 | 
|      scoped_ptr<View> view;
 | 
| -    const size_t position = text_.size() - remaining_string.size();
 | 
|      if (position >= range.start()) {
 | 
| -      // This chunk is a link.
 | 
| -      if (chunk.size() < range.length() && x != 0) {
 | 
| -        // Don't wrap links. Try to fit them entirely on one line.
 | 
| +      const RangeStyleInfo& style_info = style_ranges.top().style_info;
 | 
| +
 | 
| +      if (style_info.disable_line_wrapping && chunk.size() < range.length() &&
 | 
| +          position == range.start() && x != 0) {
 | 
| +        // If the chunk should not be wrapped, try to fit it entirely on the
 | 
| +        // next line.
 | 
|          x = 0;
 | 
|          line++;
 | 
|          continue;
 | 
|        }
 | 
|  
 | 
| -      chunk = chunk.substr(0, range.length());
 | 
| -      Link* link = new Link(chunk);
 | 
| -      link->set_listener(this);
 | 
| -      if (!dry_run)
 | 
| -        link_targets_[link] = range;
 | 
| -      view.reset(link);
 | 
| -      link_ranges.pop();
 | 
| +      chunk = chunk.substr(0, std::min(chunk.size(), range.end() - position));
 | 
| +
 | 
| +      view = CreateLabelRange(chunk, style_info, this);
 | 
| +
 | 
| +      if (style_info.is_link && !dry_run)
 | 
| +        link_targets_[view.get()] = range;
 | 
| +
 | 
| +      if (position + chunk.size() >= range.end())
 | 
| +        style_ranges.pop();
 | 
|      } else {
 | 
|        // This chunk is normal text.
 | 
|        if (position + chunk.size() > range.start())
 | 
|          chunk = chunk.substr(0, range.start() - position);
 | 
| -
 | 
| -      Label* label = new Label(chunk);
 | 
| -      // Give the label a focus border so that its preferred size matches
 | 
| -      // links' preferred sizes.
 | 
| -      label->SetHasFocusBorder(true);
 | 
| -      view.reset(label);
 | 
| +      view = CreateLabelRange(chunk, RangeStyleInfo(), this);
 | 
|      }
 | 
|  
 | 
|      // Lay out the views to overlap by 1 pixel to compensate for their border
 | 
| 
 |