Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / ui / views / controls / styled_label.cc
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.
4
5 #include "ui/views/controls/styled_label.h"
6
7 #include <vector>
8
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"
16
17 namespace views {
18
19
20 // Helpers --------------------------------------------------------------------
21
22 namespace {
23
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();
30 }
31
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;
38
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));
46   }
47
48   result->SetEnabledColor(style_info.color);
49   result->SetFontList(font_list);
50
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));
56   }
57
58   return result.Pass();
59 }
60
61 }  // namespace
62
63
64 // StyledLabel::RangeStyleInfo ------------------------------------------------
65
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) {}
72
73 StyledLabel::RangeStyleInfo::~RangeStyleInfo() {}
74
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;
82 }
83
84
85 // StyledLabel::StyleRange ----------------------------------------------------
86
87 bool StyledLabel::StyleRange::operator<(
88     const StyledLabel::StyleRange& other) const {
89   return range.start() < other.range.start();
90 }
91
92
93 // StyledLabel ----------------------------------------------------------------
94
95 StyledLabel::StyledLabel(const base::string16& text,
96                          StyledLabelListener* listener)
97     : listener_(listener),
98       displayed_on_background_color_set_(false),
99       auto_color_readability_enabled_(true) {
100   TrimWhitespace(text, TRIM_TRAILING, &text_);
101 }
102
103 StyledLabel::~StyledLabel() {}
104
105 void StyledLabel::SetText(const base::string16& text) {
106   text_ = text;
107   style_ranges_.clear();
108   RemoveAllChildViews(true);
109   PreferredSizeChanged();
110 }
111
112 void StyledLabel::SetBaseFontList(const gfx::FontList& font_list) {
113   font_list_ = font_list;
114   PreferredSizeChanged();
115 }
116
117 void StyledLabel::AddStyleRange(const gfx::Range& range,
118                                 const RangeStyleInfo& style_info) {
119   DCHECK(!range.is_reversed());
120   DCHECK(!range.is_empty());
121   DCHECK(gfx::Range(0, text_.size()).Contains(range));
122
123   // Insert the new range in sorted order.
124   StyleRanges new_range;
125   new_range.push_front(StyleRange(range, style_info));
126   style_ranges_.merge(new_range);
127
128   PreferredSizeChanged();
129 }
130
131 void StyledLabel::SetDefaultStyle(const RangeStyleInfo& style_info) {
132   default_style_info_ = style_info;
133   PreferredSizeChanged();
134 }
135
136 void StyledLabel::SetDisplayedOnBackgroundColor(SkColor color) {
137   displayed_on_background_color_ = color;
138   displayed_on_background_color_set_ = true;
139 }
140
141 gfx::Insets StyledLabel::GetInsets() const {
142   gfx::Insets insets = View::GetInsets();
143
144   // We need a focus border iff we contain a link that will have a focus border.
145   // That in turn will be true only if the link is non-empty.
146   for (StyleRanges::const_iterator i(style_ranges_.begin());
147         i != style_ranges_.end(); ++i) {
148     if (i->style_info.is_link && !i->range.is_empty()) {
149       const gfx::Insets focus_border_padding(
150           Label::kFocusBorderPadding, Label::kFocusBorderPadding,
151           Label::kFocusBorderPadding, Label::kFocusBorderPadding);
152       insets += focus_border_padding;
153       break;
154     }
155   }
156
157   return insets;
158 }
159
160 int StyledLabel::GetHeightForWidth(int w) {
161   if (w != calculated_size_.width())
162     calculated_size_ = CalculateAndDoLayout(w, true);
163   return calculated_size_.height();
164 }
165
166 void StyledLabel::Layout() {
167   calculated_size_ = CalculateAndDoLayout(GetLocalBounds().width(), false);
168 }
169
170 void StyledLabel::PreferredSizeChanged() {
171   calculated_size_ = gfx::Size();
172   View::PreferredSizeChanged();
173 }
174
175 void StyledLabel::LinkClicked(Link* source, int event_flags) {
176   if (listener_)
177     listener_->StyledLabelLinkClicked(link_targets_[source], event_flags);
178 }
179
180 gfx::Size StyledLabel::CalculateAndDoLayout(int width, bool dry_run) {
181   if (!dry_run) {
182     RemoveAllChildViews(true);
183     link_targets_.clear();
184   }
185
186   width -= GetInsets().width();
187   if (width <= 0 || text_.empty())
188     return gfx::Size();
189
190   const int line_height = CalculateLineHeight(font_list_);
191   // The index of the line we're on.
192   int line = 0;
193   // The x position (in pixels) of the line we're on, relative to content
194   // bounds.
195   int x = 0;
196
197   base::string16 remaining_string = text_;
198   StyleRanges::const_iterator current_range = style_ranges_.begin();
199
200   // Iterate over the text, creating a bunch of labels and links and laying them
201   // out in the appropriate positions.
202   while (!remaining_string.empty()) {
203     // Don't put whitespace at beginning of a line with an exception for the
204     // first line (so the text's leading whitespace is respected).
205     if (x == 0 && line > 0)
206       TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string);
207
208     gfx::Range range(gfx::Range::InvalidRange());
209     if (current_range != style_ranges_.end())
210       range = current_range->range;
211
212     const size_t position = text_.size() - remaining_string.size();
213
214     const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height);
215     std::vector<base::string16> substrings;
216     gfx::FontList text_font_list = font_list_;
217     // If the start of the remaining text is inside a styled range, the font
218     // style may differ from the base font. The font specified by the range
219     // should be used when eliding text.
220     if (position >= range.start()) {
221       text_font_list = text_font_list.DeriveWithStyle(
222           current_range->style_info.font_style);
223     }
224     gfx::ElideRectangleText(remaining_string,
225                             text_font_list,
226                             chunk_bounds.width(),
227                             chunk_bounds.height(),
228                             gfx::IGNORE_LONG_WORDS,
229                             &substrings);
230
231     DCHECK(!substrings.empty());
232     base::string16 chunk = substrings[0];
233     if (chunk.empty()) {
234       // Nothing fits on this line. Start a new line.
235       // If x is 0, first line may have leading whitespace that doesn't fit in a
236       // single line, so try trimming those. Otherwise there is no room for
237       // anything; abort.
238       if (x == 0) {
239         if (line == 0) {
240           TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string);
241           continue;
242         }
243         break;
244       }
245
246       x = 0;
247       line++;
248       continue;
249     }
250
251     scoped_ptr<Label> label;
252     if (position >= range.start()) {
253       const RangeStyleInfo& style_info = current_range->style_info;
254
255       if (style_info.disable_line_wrapping && chunk.size() < range.length() &&
256           position == range.start() && x != 0) {
257         // If the chunk should not be wrapped, try to fit it entirely on the
258         // next line.
259         x = 0;
260         line++;
261         continue;
262       }
263
264       chunk = chunk.substr(0, std::min(chunk.size(), range.end() - position));
265
266       label = CreateLabelRange(chunk, font_list_, style_info, this);
267
268       if (style_info.is_link && !dry_run)
269         link_targets_[label.get()] = range;
270
271       if (position + chunk.size() >= range.end())
272         ++current_range;
273     } else {
274       // This chunk is normal text.
275       if (position + chunk.size() > range.start())
276         chunk = chunk.substr(0, range.start() - position);
277       label = CreateLabelRange(chunk, font_list_, default_style_info_, this);
278     }
279
280     if (displayed_on_background_color_set_)
281       label->SetBackgroundColor(displayed_on_background_color_);
282     label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled_);
283
284     // Calculate the size of the optional focus border, and overlap by that
285     // amount. Otherwise, "<a>link</a>," will render as "link ,".
286     gfx::Insets focus_border_insets(label->GetInsets());
287     focus_border_insets += -label->View::GetInsets();
288     const gfx::Size view_size = label->GetPreferredSize();
289     DCHECK_EQ(line_height, view_size.height() - focus_border_insets.height());
290     if (!dry_run) {
291       label->SetBoundsRect(gfx::Rect(
292           gfx::Point(GetInsets().left() + x - focus_border_insets.left(),
293                      GetInsets().top() + line * line_height -
294                          focus_border_insets.top()),
295           view_size));
296       AddChildView(label.release());
297     }
298     x += view_size.width() - focus_border_insets.width();
299
300     remaining_string = remaining_string.substr(chunk.size());
301   }
302
303   return gfx::Size(width, (line + 1) * line_height + GetInsets().height());
304 }
305
306 }  // namespace views