Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / ui / views / controls / styled_label.cc
blob700e62a392115963ae77adb8abb4a2fb60aa0205
1 // Copyright 2013 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 #include "ui/views/controls/styled_label.h"
7 #include <vector>
9 #include "base/strings/string_util.h"
10 #include "ui/gfx/font_list.h"
11 #include "ui/gfx/text_elider.h"
12 #include "ui/native_theme/native_theme.h"
13 #include "ui/views/controls/label.h"
14 #include "ui/views/controls/link.h"
15 #include "ui/views/controls/styled_label_listener.h"
17 namespace views {
20 // Helpers --------------------------------------------------------------------
22 namespace {
24 // Calculates the height of a line of text. Currently returns the height of
25 // a label.
26 int CalculateLineHeight(const gfx::FontList& font_list) {
27 Label label;
28 label.SetFontList(font_list);
29 return label.GetPreferredSize().height();
32 scoped_ptr<Label> CreateLabelRange(
33 const base::string16& text,
34 const gfx::FontList& font_list,
35 const StyledLabel::RangeStyleInfo& style_info,
36 views::LinkListener* link_listener) {
37 scoped_ptr<Label> result;
39 if (style_info.is_link) {
40 Link* link = new Link(text);
41 link->set_listener(link_listener);
42 link->SetUnderline((style_info.font_style & gfx::Font::UNDERLINE) != 0);
43 result.reset(link);
44 } else {
45 result.reset(new Label(text));
48 result->SetEnabledColor(style_info.color);
49 result->SetFontList(font_list);
51 if (!style_info.tooltip.empty())
52 result->SetTooltipText(style_info.tooltip);
53 if (style_info.font_style != gfx::Font::NORMAL) {
54 result->SetFontList(
55 result->font_list().DeriveWithStyle(style_info.font_style));
58 return result.Pass();
61 } // namespace
64 // StyledLabel::RangeStyleInfo ------------------------------------------------
66 StyledLabel::RangeStyleInfo::RangeStyleInfo()
67 : font_style(gfx::Font::NORMAL),
68 color(ui::NativeTheme::instance()->GetSystemColor(
69 ui::NativeTheme::kColorId_LabelEnabledColor)),
70 disable_line_wrapping(false),
71 is_link(false) {}
73 StyledLabel::RangeStyleInfo::~RangeStyleInfo() {}
75 // static
76 StyledLabel::RangeStyleInfo StyledLabel::RangeStyleInfo::CreateForLink() {
77 RangeStyleInfo result;
78 result.disable_line_wrapping = true;
79 result.is_link = true;
80 result.color = Link::GetDefaultEnabledColor();
81 return result;
85 // StyledLabel::StyleRange ----------------------------------------------------
87 bool StyledLabel::StyleRange::operator<(
88 const StyledLabel::StyleRange& other) const {
89 return range.start() < other.range.start();
93 // StyledLabel ----------------------------------------------------------------
95 StyledLabel::StyledLabel(const base::string16& text,
96 StyledLabelListener* listener)
97 : specified_line_height_(0),
98 listener_(listener),
99 displayed_on_background_color_set_(false),
100 auto_color_readability_enabled_(true) {
101 base::TrimWhitespace(text, base::TRIM_TRAILING, &text_);
104 StyledLabel::~StyledLabel() {}
106 void StyledLabel::SetText(const base::string16& text) {
107 text_ = text;
108 style_ranges_.clear();
109 RemoveAllChildViews(true);
110 PreferredSizeChanged();
113 void StyledLabel::SetBaseFontList(const gfx::FontList& font_list) {
114 font_list_ = font_list;
115 PreferredSizeChanged();
118 void StyledLabel::AddStyleRange(const gfx::Range& range,
119 const RangeStyleInfo& style_info) {
120 DCHECK(!range.is_reversed());
121 DCHECK(!range.is_empty());
122 DCHECK(gfx::Range(0, text_.size()).Contains(range));
124 // Insert the new range in sorted order.
125 StyleRanges new_range;
126 new_range.push_front(StyleRange(range, style_info));
127 style_ranges_.merge(new_range);
129 PreferredSizeChanged();
132 void StyledLabel::SetDefaultStyle(const RangeStyleInfo& style_info) {
133 default_style_info_ = style_info;
134 PreferredSizeChanged();
137 void StyledLabel::SetLineHeight(int line_height) {
138 specified_line_height_ = line_height;
139 PreferredSizeChanged();
142 void StyledLabel::SetDisplayedOnBackgroundColor(SkColor color) {
143 displayed_on_background_color_ = color;
144 displayed_on_background_color_set_ = true;
147 gfx::Insets StyledLabel::GetInsets() const {
148 gfx::Insets insets = View::GetInsets();
150 // We need a focus border iff we contain a link that will have a focus border.
151 // That in turn will be true only if the link is non-empty.
152 for (StyleRanges::const_iterator i(style_ranges_.begin());
153 i != style_ranges_.end(); ++i) {
154 if (i->style_info.is_link && !i->range.is_empty()) {
155 const gfx::Insets focus_border_padding(
156 Label::kFocusBorderPadding, Label::kFocusBorderPadding,
157 Label::kFocusBorderPadding, Label::kFocusBorderPadding);
158 insets += focus_border_padding;
159 break;
163 return insets;
166 int StyledLabel::GetHeightForWidth(int w) const {
167 if (w != calculated_size_.width()) {
168 // TODO(erg): Munge the const-ness of the style label. CalculateAndDoLayout
169 // doesn't actually make any changes to member variables when |dry_run| is
170 // set to true. In general, the mutating and non-mutating parts shouldn't
171 // be in the same codepath.
172 calculated_size_ =
173 const_cast<StyledLabel*>(this)->CalculateAndDoLayout(w, true);
175 return calculated_size_.height();
178 void StyledLabel::Layout() {
179 calculated_size_ = CalculateAndDoLayout(GetLocalBounds().width(), false);
182 void StyledLabel::PreferredSizeChanged() {
183 calculated_size_ = gfx::Size();
184 View::PreferredSizeChanged();
187 void StyledLabel::LinkClicked(Link* source, int event_flags) {
188 if (listener_)
189 listener_->StyledLabelLinkClicked(link_targets_[source], event_flags);
192 gfx::Size StyledLabel::CalculateAndDoLayout(int width, bool dry_run) {
193 if (!dry_run) {
194 RemoveAllChildViews(true);
195 link_targets_.clear();
198 width -= GetInsets().width();
199 if (width <= 0 || text_.empty())
200 return gfx::Size();
202 const int line_height = specified_line_height_ > 0 ? specified_line_height_
203 : CalculateLineHeight(font_list_);
204 // The index of the line we're on.
205 int line = 0;
206 // The x position (in pixels) of the line we're on, relative to content
207 // bounds.
208 int x = 0;
210 base::string16 remaining_string = text_;
211 StyleRanges::const_iterator current_range = style_ranges_.begin();
213 // Iterate over the text, creating a bunch of labels and links and laying them
214 // out in the appropriate positions.
215 while (!remaining_string.empty()) {
216 // Don't put whitespace at beginning of a line with an exception for the
217 // first line (so the text's leading whitespace is respected).
218 if (x == 0 && line > 0) {
219 base::TrimWhitespace(remaining_string, base::TRIM_LEADING,
220 &remaining_string);
223 gfx::Range range(gfx::Range::InvalidRange());
224 if (current_range != style_ranges_.end())
225 range = current_range->range;
227 const size_t position = text_.size() - remaining_string.size();
229 const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height);
230 std::vector<base::string16> substrings;
231 gfx::FontList text_font_list = font_list_;
232 // If the start of the remaining text is inside a styled range, the font
233 // style may differ from the base font. The font specified by the range
234 // should be used when eliding text.
235 if (position >= range.start()) {
236 text_font_list = text_font_list.DeriveWithStyle(
237 current_range->style_info.font_style);
239 gfx::ElideRectangleText(remaining_string,
240 text_font_list,
241 chunk_bounds.width(),
242 chunk_bounds.height(),
243 gfx::IGNORE_LONG_WORDS,
244 &substrings);
246 DCHECK(!substrings.empty());
247 base::string16 chunk = substrings[0];
248 if (chunk.empty()) {
249 // Nothing fits on this line. Start a new line.
250 // If x is 0, first line may have leading whitespace that doesn't fit in a
251 // single line, so try trimming those. Otherwise there is no room for
252 // anything; abort.
253 if (x == 0) {
254 if (line == 0) {
255 base::TrimWhitespace(remaining_string, base::TRIM_LEADING,
256 &remaining_string);
257 continue;
259 break;
262 x = 0;
263 line++;
264 continue;
267 scoped_ptr<Label> label;
268 if (position >= range.start()) {
269 const RangeStyleInfo& style_info = current_range->style_info;
271 if (style_info.disable_line_wrapping && chunk.size() < range.length() &&
272 position == range.start() && x != 0) {
273 // If the chunk should not be wrapped, try to fit it entirely on the
274 // next line.
275 x = 0;
276 line++;
277 continue;
280 chunk = chunk.substr(0, std::min(chunk.size(), range.end() - position));
282 label = CreateLabelRange(chunk, font_list_, style_info, this);
284 if (style_info.is_link && !dry_run)
285 link_targets_[label.get()] = range;
287 if (position + chunk.size() >= range.end())
288 ++current_range;
289 } else {
290 // This chunk is normal text.
291 if (position + chunk.size() > range.start())
292 chunk = chunk.substr(0, range.start() - position);
293 label = CreateLabelRange(chunk, font_list_, default_style_info_, this);
296 if (displayed_on_background_color_set_)
297 label->SetBackgroundColor(displayed_on_background_color_);
298 label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled_);
300 // Calculate the size of the optional focus border, and overlap by that
301 // amount. Otherwise, "<a>link</a>," will render as "link ,".
302 gfx::Insets focus_border_insets(label->GetInsets());
303 focus_border_insets += -label->View::GetInsets();
304 const gfx::Size view_size = label->GetPreferredSize();
305 if (!dry_run) {
306 label->SetBoundsRect(gfx::Rect(
307 gfx::Point(GetInsets().left() + x - focus_border_insets.left(),
308 GetInsets().top() + line * line_height -
309 focus_border_insets.top()),
310 view_size));
311 AddChildView(label.release());
313 x += view_size.width() - focus_border_insets.width();
315 remaining_string = remaining_string.substr(chunk.size());
318 // The user-specified line height only applies to interline spacing, so the
319 // final line's height is unaffected.
320 int total_height = line * line_height +
321 CalculateLineHeight(font_list_) + GetInsets().height();
322 return gfx::Size(width, total_height);
325 } // namespace views