Make castv2 performance test work.
[chromium-blink-merge.git] / ui / views / controls / styled_label.cc
blob6c9ff2c522a8c53aa16b9d31bcee099bd908db7c
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 <limits>
8 #include <vector>
10 #include "base/strings/string_util.h"
11 #include "ui/gfx/font_list.h"
12 #include "ui/gfx/text_elider.h"
13 #include "ui/native_theme/native_theme.h"
14 #include "ui/views/controls/label.h"
15 #include "ui/views/controls/link.h"
16 #include "ui/views/controls/styled_label_listener.h"
18 namespace views {
21 // Helpers --------------------------------------------------------------------
23 namespace {
25 // Calculates the height of a line of text. Currently returns the height of
26 // a label.
27 int CalculateLineHeight(const gfx::FontList& font_list) {
28 Label label;
29 label.SetFontList(font_list);
30 return label.GetPreferredSize().height();
33 scoped_ptr<Label> CreateLabelRange(
34 const base::string16& text,
35 const gfx::FontList& font_list,
36 const StyledLabel::RangeStyleInfo& style_info,
37 views::LinkListener* link_listener) {
38 scoped_ptr<Label> result;
40 if (style_info.is_link) {
41 Link* link = new Link(text);
42 link->set_listener(link_listener);
43 link->SetUnderline((style_info.font_style & gfx::Font::UNDERLINE) != 0);
44 result.reset(link);
45 } else {
46 result.reset(new Label(text));
49 result->SetEnabledColor(style_info.color);
50 result->SetFontList(font_list);
52 if (!style_info.tooltip.empty())
53 result->SetTooltipText(style_info.tooltip);
54 if (style_info.font_style != gfx::Font::NORMAL) {
55 result->SetFontList(
56 result->font_list().DeriveWithStyle(style_info.font_style));
59 return result.Pass();
62 } // namespace
65 // StyledLabel::RangeStyleInfo ------------------------------------------------
67 StyledLabel::RangeStyleInfo::RangeStyleInfo()
68 : font_style(gfx::Font::NORMAL),
69 color(ui::NativeTheme::instance()->GetSystemColor(
70 ui::NativeTheme::kColorId_LabelEnabledColor)),
71 disable_line_wrapping(false),
72 is_link(false) {}
74 StyledLabel::RangeStyleInfo::~RangeStyleInfo() {}
76 // static
77 StyledLabel::RangeStyleInfo StyledLabel::RangeStyleInfo::CreateForLink() {
78 RangeStyleInfo result;
79 result.disable_line_wrapping = true;
80 result.is_link = true;
81 result.color = Link::GetDefaultEnabledColor();
82 return result;
86 // StyledLabel::StyleRange ----------------------------------------------------
88 bool StyledLabel::StyleRange::operator<(
89 const StyledLabel::StyleRange& other) const {
90 return range.start() < other.range.start();
94 // StyledLabel ----------------------------------------------------------------
96 // static
97 const char StyledLabel::kViewClassName[] = "StyledLabel";
99 StyledLabel::StyledLabel(const base::string16& text,
100 StyledLabelListener* listener)
101 : specified_line_height_(0),
102 listener_(listener),
103 width_at_last_size_calculation_(0),
104 width_at_last_layout_(0),
105 displayed_on_background_color_(SkColorSetRGB(0xFF, 0xFF, 0xFF)),
106 displayed_on_background_color_set_(false),
107 auto_color_readability_enabled_(true) {
108 base::TrimWhitespace(text, base::TRIM_TRAILING, &text_);
111 StyledLabel::~StyledLabel() {}
113 void StyledLabel::SetText(const base::string16& text) {
114 text_ = text;
115 style_ranges_.clear();
116 RemoveAllChildViews(true);
117 PreferredSizeChanged();
120 void StyledLabel::SetBaseFontList(const gfx::FontList& font_list) {
121 font_list_ = font_list;
122 PreferredSizeChanged();
125 void StyledLabel::AddStyleRange(const gfx::Range& range,
126 const RangeStyleInfo& style_info) {
127 DCHECK(!range.is_reversed());
128 DCHECK(!range.is_empty());
129 DCHECK(gfx::Range(0, text_.size()).Contains(range));
131 // Insert the new range in sorted order.
132 StyleRanges new_range;
133 new_range.push_front(StyleRange(range, style_info));
134 style_ranges_.merge(new_range);
136 PreferredSizeChanged();
139 void StyledLabel::SetDefaultStyle(const RangeStyleInfo& style_info) {
140 default_style_info_ = style_info;
141 PreferredSizeChanged();
144 void StyledLabel::SetLineHeight(int line_height) {
145 specified_line_height_ = line_height;
146 PreferredSizeChanged();
149 void StyledLabel::SetDisplayedOnBackgroundColor(SkColor color) {
150 if (displayed_on_background_color_ == color &&
151 displayed_on_background_color_set_)
152 return;
154 displayed_on_background_color_ = color;
155 displayed_on_background_color_set_ = true;
157 for (int i = 0, count = child_count(); i < count; ++i) {
158 DCHECK((child_at(i)->GetClassName() == Label::kViewClassName) ||
159 (child_at(i)->GetClassName() == Link::kViewClassName));
160 static_cast<Label*>(child_at(i))->SetBackgroundColor(color);
164 void StyledLabel::SizeToFit(int max_width) {
165 if (max_width == 0)
166 max_width = std::numeric_limits<int>::max();
168 SetSize(CalculateAndDoLayout(max_width, true));
171 const char* StyledLabel::GetClassName() const {
172 return kViewClassName;
175 gfx::Insets StyledLabel::GetInsets() const {
176 gfx::Insets insets = View::GetInsets();
178 // We need a focus border iff we contain a link that will have a focus border.
179 // That in turn will be true only if the link is non-empty.
180 for (StyleRanges::const_iterator i(style_ranges_.begin());
181 i != style_ranges_.end(); ++i) {
182 if (i->style_info.is_link && !i->range.is_empty()) {
183 const gfx::Insets focus_border_padding(
184 Label::kFocusBorderPadding, Label::kFocusBorderPadding,
185 Label::kFocusBorderPadding, Label::kFocusBorderPadding);
186 insets += focus_border_padding;
187 break;
191 return insets;
194 gfx::Size StyledLabel::GetPreferredSize() const {
195 return calculated_size_;
198 int StyledLabel::GetHeightForWidth(int w) const {
199 // TODO(erg): Munge the const-ness of the style label. CalculateAndDoLayout
200 // doesn't actually make any changes to member variables when |dry_run| is
201 // set to true. In general, the mutating and non-mutating parts shouldn't
202 // be in the same codepath.
203 return const_cast<StyledLabel*>(this)->CalculateAndDoLayout(w, true).height();
206 void StyledLabel::Layout() {
207 CalculateAndDoLayout(GetLocalBounds().width(), false);
210 void StyledLabel::PreferredSizeChanged() {
211 calculated_size_ = gfx::Size();
212 width_at_last_size_calculation_ = 0;
213 width_at_last_layout_ = 0;
214 View::PreferredSizeChanged();
217 void StyledLabel::LinkClicked(Link* source, int event_flags) {
218 if (listener_)
219 listener_->StyledLabelLinkClicked(link_targets_[source], event_flags);
222 gfx::Size StyledLabel::CalculateAndDoLayout(int width, bool dry_run) {
223 if (width == width_at_last_size_calculation_ &&
224 (dry_run || width == width_at_last_layout_))
225 return calculated_size_;
227 width_at_last_size_calculation_ = width;
228 if (!dry_run)
229 width_at_last_layout_ = width;
231 width -= GetInsets().width();
233 if (!dry_run) {
234 RemoveAllChildViews(true);
235 link_targets_.clear();
238 if (width <= 0 || text_.empty())
239 return gfx::Size();
241 const int line_height = specified_line_height_ > 0 ? specified_line_height_
242 : CalculateLineHeight(font_list_);
243 // The index of the line we're on.
244 int line = 0;
245 // The x position (in pixels) of the line we're on, relative to content
246 // bounds.
247 int x = 0;
248 // The width that was actually used. Guaranteed to be no larger than |width|.
249 int used_width = 0;
251 base::string16 remaining_string = text_;
252 StyleRanges::const_iterator current_range = style_ranges_.begin();
254 // Iterate over the text, creating a bunch of labels and links and laying them
255 // out in the appropriate positions.
256 while (!remaining_string.empty()) {
257 // Don't put whitespace at beginning of a line with an exception for the
258 // first line (so the text's leading whitespace is respected).
259 if (x == 0 && line > 0) {
260 base::TrimWhitespace(remaining_string, base::TRIM_LEADING,
261 &remaining_string);
264 gfx::Range range(gfx::Range::InvalidRange());
265 if (current_range != style_ranges_.end())
266 range = current_range->range;
268 const size_t position = text_.size() - remaining_string.size();
270 const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height);
271 std::vector<base::string16> substrings;
272 gfx::FontList text_font_list = font_list_;
273 // If the start of the remaining text is inside a styled range, the font
274 // style may differ from the base font. The font specified by the range
275 // should be used when eliding text.
276 if (position >= range.start()) {
277 text_font_list = text_font_list.DeriveWithStyle(
278 current_range->style_info.font_style);
280 gfx::ElideRectangleText(remaining_string,
281 text_font_list,
282 chunk_bounds.width(),
283 chunk_bounds.height(),
284 gfx::IGNORE_LONG_WORDS,
285 &substrings);
287 DCHECK(!substrings.empty());
288 base::string16 chunk = substrings[0];
289 if (chunk.empty()) {
290 // Nothing fits on this line. Start a new line.
291 // If x is 0, first line may have leading whitespace that doesn't fit in a
292 // single line, so try trimming those. Otherwise there is no room for
293 // anything; abort.
294 if (x == 0) {
295 if (line == 0) {
296 base::TrimWhitespace(remaining_string, base::TRIM_LEADING,
297 &remaining_string);
298 continue;
300 break;
303 x = 0;
304 line++;
305 continue;
308 scoped_ptr<Label> label;
309 if (position >= range.start()) {
310 const RangeStyleInfo& style_info = current_range->style_info;
312 if (style_info.disable_line_wrapping && chunk.size() < range.length() &&
313 position == range.start() && x != 0) {
314 // If the chunk should not be wrapped, try to fit it entirely on the
315 // next line.
316 x = 0;
317 line++;
318 continue;
321 chunk = chunk.substr(0, std::min(chunk.size(), range.end() - position));
323 label = CreateLabelRange(chunk, font_list_, style_info, this);
325 if (style_info.is_link && !dry_run)
326 link_targets_[label.get()] = range;
328 if (position + chunk.size() >= range.end())
329 ++current_range;
330 } else {
331 // This chunk is normal text.
332 if (position + chunk.size() > range.start())
333 chunk = chunk.substr(0, range.start() - position);
334 label = CreateLabelRange(chunk, font_list_, default_style_info_, this);
337 if (displayed_on_background_color_set_)
338 label->SetBackgroundColor(displayed_on_background_color_);
339 label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled_);
341 // Calculate the size of the optional focus border, and overlap by that
342 // amount. Otherwise, "<a>link</a>," will render as "link ,".
343 gfx::Insets focus_border_insets(label->GetInsets());
344 focus_border_insets += -label->View::GetInsets();
345 const gfx::Size view_size = label->GetPreferredSize();
346 if (!dry_run) {
347 label->SetBoundsRect(gfx::Rect(
348 gfx::Point(GetInsets().left() + x - focus_border_insets.left(),
349 GetInsets().top() + line * line_height -
350 focus_border_insets.top()),
351 view_size));
352 AddChildView(label.release());
354 x += view_size.width() - focus_border_insets.width();
355 used_width = std::max(used_width, x);
357 remaining_string = remaining_string.substr(chunk.size());
360 DCHECK_LE(used_width, width);
361 // The user-specified line height only applies to interline spacing, so the
362 // final line's height is unaffected.
363 int total_height = line * line_height +
364 CalculateLineHeight(font_list_) + GetInsets().height();
365 calculated_size_ = gfx::Size(used_width + GetInsets().width(), total_height);
366 return calculated_size_;
369 } // namespace views