OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ui/views/controls/styled_label.h" | 5 #include "ui/views/controls/styled_label.h" |
6 | 6 |
7 #include <vector> | 7 #include <vector> |
8 | 8 |
9 #include "base/strings/string_util.h" | 9 #include "base/strings/string_util.h" |
10 #include "ui/gfx/text_elider.h" | 10 #include "ui/gfx/text_elider.h" |
11 #include "ui/native_theme/native_theme.h" | 11 #include "ui/native_theme/native_theme.h" |
12 #include "ui/views/controls/label.h" | 12 #include "ui/views/controls/label.h" |
13 #include "ui/views/controls/link.h" | 13 #include "ui/views/controls/link.h" |
14 #include "ui/views/controls/styled_label_listener.h" | 14 #include "ui/views/controls/styled_label_listener.h" |
15 | 15 |
16 namespace views { | 16 namespace views { |
17 | 17 |
| 18 |
| 19 // Helpers -------------------------------------------------------------------- |
| 20 |
18 namespace { | 21 namespace { |
19 | 22 |
20 // Calculates the height of a line of text. Currently returns the height of | 23 // Calculates the height of a line of text. Currently returns the height of |
21 // a label. | 24 // a label. |
22 int CalculateLineHeight() { | 25 int CalculateLineHeight() { |
23 Label label; | 26 Label label; |
24 return label.GetPreferredSize().height(); | 27 return label.GetPreferredSize().height(); |
25 } | 28 } |
26 | 29 |
27 scoped_ptr<Label> CreateLabelRange( | 30 scoped_ptr<Label> CreateLabelRange( |
28 const string16& text, | 31 const string16& text, |
29 const StyledLabel::RangeStyleInfo& style_info, | 32 const StyledLabel::RangeStyleInfo& style_info, |
30 views::LinkListener* link_listener) { | 33 views::LinkListener* link_listener) { |
31 scoped_ptr<Label> result; | 34 scoped_ptr<Label> result; |
32 | 35 |
33 if (style_info.is_link) { | 36 if (style_info.is_link) { |
34 Link* link = new Link(text); | 37 Link* link = new Link(text); |
35 link->set_listener(link_listener); | 38 link->set_listener(link_listener); |
36 link->SetUnderline((style_info.font_style & gfx::Font::UNDERLINE) != 0); | 39 link->SetUnderline((style_info.font_style & gfx::Font::UNDERLINE) != 0); |
37 result.reset(link); | 40 result.reset(link); |
38 } else { | 41 } else { |
39 Label* label = new Label(text); | 42 result.reset(new Label(text)); |
40 // Give the label a focus border so that its preferred size matches | |
41 // links' preferred sizes | |
42 label->SetHasFocusBorder(true); | |
43 | |
44 result.reset(label); | |
45 } | 43 } |
46 | 44 |
47 result->SetEnabledColor(style_info.color); | 45 result->SetEnabledColor(style_info.color); |
48 | 46 |
49 if (!style_info.tooltip.empty()) | 47 if (!style_info.tooltip.empty()) |
50 result->SetTooltipText(style_info.tooltip); | 48 result->SetTooltipText(style_info.tooltip); |
51 if (style_info.font_style != gfx::Font::NORMAL) | 49 if (style_info.font_style != gfx::Font::NORMAL) |
52 result->SetFont(result->font().DeriveFont(0, style_info.font_style)); | 50 result->SetFont(result->font().DeriveFont(0, style_info.font_style)); |
53 | 51 |
54 return scoped_ptr<Label>(result.release()); | 52 return scoped_ptr<Label>(result.release()); |
55 } | 53 } |
56 | 54 |
57 } // namespace | 55 } // namespace |
58 | 56 |
| 57 |
| 58 // StyledLabel::RangeStyleInfo ------------------------------------------------ |
| 59 |
59 StyledLabel::RangeStyleInfo::RangeStyleInfo() | 60 StyledLabel::RangeStyleInfo::RangeStyleInfo() |
60 : font_style(gfx::Font::NORMAL), | 61 : font_style(gfx::Font::NORMAL), |
61 color(ui::NativeTheme::instance()->GetSystemColor( | 62 color(ui::NativeTheme::instance()->GetSystemColor( |
62 ui::NativeTheme::kColorId_LabelEnabledColor)), | 63 ui::NativeTheme::kColorId_LabelEnabledColor)), |
63 disable_line_wrapping(false), | 64 disable_line_wrapping(false), |
64 is_link(false) {} | 65 is_link(false) {} |
65 | 66 |
66 StyledLabel::RangeStyleInfo::~RangeStyleInfo() {} | 67 StyledLabel::RangeStyleInfo::~RangeStyleInfo() {} |
67 | 68 |
68 // static | 69 // static |
69 StyledLabel::RangeStyleInfo StyledLabel::RangeStyleInfo::CreateForLink() { | 70 StyledLabel::RangeStyleInfo StyledLabel::RangeStyleInfo::CreateForLink() { |
70 RangeStyleInfo result; | 71 RangeStyleInfo result; |
71 result.disable_line_wrapping = true; | 72 result.disable_line_wrapping = true; |
72 result.is_link = true; | 73 result.is_link = true; |
73 result.color = Link::GetDefaultEnabledColor(); | 74 result.color = Link::GetDefaultEnabledColor(); |
74 return result; | 75 return result; |
75 } | 76 } |
76 | 77 |
| 78 |
| 79 // StyledLabel::StyleRange ---------------------------------------------------- |
| 80 |
77 bool StyledLabel::StyleRange::operator<( | 81 bool StyledLabel::StyleRange::operator<( |
78 const StyledLabel::StyleRange& other) const { | 82 const StyledLabel::StyleRange& other) const { |
79 // Intentionally reversed so the priority queue is sorted by smallest first. | 83 return range.start() < other.range.start(); |
80 return range.start() > other.range.start(); | |
81 } | 84 } |
82 | 85 |
| 86 |
| 87 // StyledLabel ---------------------------------------------------------------- |
| 88 |
83 StyledLabel::StyledLabel(const string16& text, StyledLabelListener* listener) | 89 StyledLabel::StyledLabel(const string16& text, StyledLabelListener* listener) |
84 : listener_(listener), | 90 : listener_(listener), |
85 displayed_on_background_color_set_(false), | 91 displayed_on_background_color_set_(false), |
86 auto_color_readability_enabled_(true) { | 92 auto_color_readability_enabled_(true) { |
87 TrimWhitespace(text, TRIM_TRAILING, &text_); | 93 TrimWhitespace(text, TRIM_TRAILING, &text_); |
88 } | 94 } |
89 | 95 |
90 StyledLabel::~StyledLabel() {} | 96 StyledLabel::~StyledLabel() {} |
91 | 97 |
92 void StyledLabel::SetText(const string16& text) { | 98 void StyledLabel::SetText(const string16& text) { |
93 text_ = text; | 99 text_ = text; |
94 style_ranges_ = std::priority_queue<StyleRange>(); | 100 style_ranges_.clear(); |
95 RemoveAllChildViews(true); | 101 RemoveAllChildViews(true); |
96 PreferredSizeChanged(); | 102 PreferredSizeChanged(); |
97 } | 103 } |
98 | 104 |
99 void StyledLabel::AddStyleRange(const gfx::Range& range, | 105 void StyledLabel::AddStyleRange(const gfx::Range& range, |
100 const RangeStyleInfo& style_info) { | 106 const RangeStyleInfo& style_info) { |
101 DCHECK(!range.is_reversed()); | 107 DCHECK(!range.is_reversed()); |
102 DCHECK(!range.is_empty()); | 108 DCHECK(!range.is_empty()); |
103 DCHECK(gfx::Range(0, text_.size()).Contains(range)); | 109 DCHECK(gfx::Range(0, text_.size()).Contains(range)); |
104 | 110 |
105 style_ranges_.push(StyleRange(range, style_info)); | 111 // Insert the new range in sorted order. |
| 112 StyleRanges new_range; |
| 113 new_range.push_front(StyleRange(range, style_info)); |
| 114 style_ranges_.merge(new_range); |
106 | 115 |
107 PreferredSizeChanged(); | 116 PreferredSizeChanged(); |
108 } | 117 } |
109 | 118 |
110 void StyledLabel::SetDefaultStyle(const RangeStyleInfo& style_info) { | 119 void StyledLabel::SetDefaultStyle(const RangeStyleInfo& style_info) { |
111 default_style_info_ = style_info; | 120 default_style_info_ = style_info; |
112 PreferredSizeChanged(); | 121 PreferredSizeChanged(); |
113 } | 122 } |
114 | 123 |
115 void StyledLabel::SetDisplayedOnBackgroundColor(SkColor color) { | 124 void StyledLabel::SetDisplayedOnBackgroundColor(SkColor color) { |
116 displayed_on_background_color_ = color; | 125 displayed_on_background_color_ = color; |
117 displayed_on_background_color_set_ = true; | 126 displayed_on_background_color_set_ = true; |
118 } | 127 } |
119 | 128 |
120 gfx::Insets StyledLabel::GetInsets() const { | 129 gfx::Insets StyledLabel::GetInsets() const { |
121 gfx::Insets insets = View::GetInsets(); | 130 gfx::Insets insets = View::GetInsets(); |
122 const gfx::Insets focus_border_padding(1, 1, 1, 1); | 131 |
123 insets += focus_border_padding; | 132 // We need a focus border iff we contain a link that will have a focus border. |
| 133 // That in turn will be true only if the link is non-empty. |
| 134 for (StyleRanges::const_iterator i(style_ranges_.begin()); |
| 135 i != style_ranges_.end(); ++i) { |
| 136 if (i->style_info.is_link && !i->range.is_empty()) { |
| 137 const gfx::Insets focus_border_padding( |
| 138 Label::kFocusBorderPadding, Label::kFocusBorderPadding, |
| 139 Label::kFocusBorderPadding, Label::kFocusBorderPadding); |
| 140 insets += focus_border_padding; |
| 141 break; |
| 142 } |
| 143 } |
| 144 |
124 return insets; | 145 return insets; |
125 } | 146 } |
126 | 147 |
127 int StyledLabel::GetHeightForWidth(int w) { | 148 int StyledLabel::GetHeightForWidth(int w) { |
128 if (w != calculated_size_.width()) | 149 if (w != calculated_size_.width()) |
129 calculated_size_ = gfx::Size(w, CalculateAndDoLayout(w, true)); | 150 calculated_size_ = CalculateAndDoLayout(w, true); |
130 | |
131 return calculated_size_.height(); | 151 return calculated_size_.height(); |
132 } | 152 } |
133 | 153 |
134 void StyledLabel::Layout() { | 154 void StyledLabel::Layout() { |
135 CalculateAndDoLayout(GetLocalBounds().width(), false); | 155 calculated_size_ = CalculateAndDoLayout(GetLocalBounds().width(), false); |
136 } | 156 } |
137 | 157 |
138 void StyledLabel::PreferredSizeChanged() { | 158 void StyledLabel::PreferredSizeChanged() { |
139 calculated_size_ = gfx::Size(); | 159 calculated_size_ = gfx::Size(); |
140 View::PreferredSizeChanged(); | 160 View::PreferredSizeChanged(); |
141 } | 161 } |
142 | 162 |
143 void StyledLabel::LinkClicked(Link* source, int event_flags) { | 163 void StyledLabel::LinkClicked(Link* source, int event_flags) { |
144 if (listener_) | 164 if (listener_) |
145 listener_->StyledLabelLinkClicked(link_targets_[source], event_flags); | 165 listener_->StyledLabelLinkClicked(link_targets_[source], event_flags); |
146 } | 166 } |
147 | 167 |
148 int StyledLabel::CalculateAndDoLayout(int width, bool dry_run) { | 168 gfx::Size StyledLabel::CalculateAndDoLayout(int width, bool dry_run) { |
149 if (!dry_run) { | 169 if (!dry_run) { |
150 RemoveAllChildViews(true); | 170 RemoveAllChildViews(true); |
151 link_targets_.clear(); | 171 link_targets_.clear(); |
152 } | 172 } |
153 | 173 |
154 width -= GetInsets().width(); | 174 width -= GetInsets().width(); |
155 if (width <= 0 || text_.empty()) | 175 if (width <= 0 || text_.empty()) |
156 return 0; | 176 return gfx::Size(); |
157 | 177 |
158 const int line_height = CalculateLineHeight(); | 178 const int line_height = CalculateLineHeight(); |
159 // The index of the line we're on. | 179 // The index of the line we're on. |
160 int line = 0; | 180 int line = 0; |
161 // The x position (in pixels) of the line we're on, relative to content | 181 // The x position (in pixels) of the line we're on, relative to content |
162 // bounds. | 182 // bounds. |
163 int x = 0; | 183 int x = 0; |
164 | 184 |
165 string16 remaining_string = text_; | 185 string16 remaining_string = text_; |
166 std::priority_queue<StyleRange> style_ranges = style_ranges_; | 186 StyleRanges::const_iterator current_range = style_ranges_.begin(); |
167 | 187 |
168 // Iterate over the text, creating a bunch of labels and links and laying them | 188 // Iterate over the text, creating a bunch of labels and links and laying them |
169 // out in the appropriate positions. | 189 // out in the appropriate positions. |
170 while (!remaining_string.empty()) { | 190 while (!remaining_string.empty()) { |
171 // Don't put whitespace at beginning of a line with an exception for the | 191 // Don't put whitespace at beginning of a line with an exception for the |
172 // first line (so the text's leading whitespace is respected). | 192 // first line (so the text's leading whitespace is respected). |
173 if (x == 0 && line > 0) | 193 if (x == 0 && line > 0) |
174 TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string); | 194 TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string); |
175 | 195 |
176 gfx::Range range(gfx::Range::InvalidRange()); | 196 gfx::Range range(gfx::Range::InvalidRange()); |
177 if (!style_ranges.empty()) | 197 if (current_range != style_ranges_.end()) |
178 range = style_ranges.top().range; | 198 range = current_range->range; |
179 | 199 |
180 const size_t position = text_.size() - remaining_string.size(); | 200 const size_t position = text_.size() - remaining_string.size(); |
181 | 201 |
182 const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height); | 202 const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height); |
183 std::vector<string16> substrings; | 203 std::vector<string16> substrings; |
184 gfx::FontList text_font_list; | 204 gfx::FontList text_font_list; |
185 // If the start of the remaining text is inside a styled range, the font | 205 // If the start of the remaining text is inside a styled range, the font |
186 // style may differ from the base font. The font specified by the range | 206 // style may differ from the base font. The font specified by the range |
187 // should be used when eliding text. | 207 // should be used when eliding text. |
188 if (position >= range.start()) { | 208 if (position >= range.start()) { |
189 text_font_list = text_font_list.DeriveFontListWithSizeDeltaAndStyle( | 209 text_font_list = text_font_list.DeriveFontListWithSizeDeltaAndStyle( |
190 0, style_ranges.top().style_info.font_style); | 210 0, current_range->style_info.font_style); |
191 } | 211 } |
192 gfx::ElideRectangleText(remaining_string, | 212 gfx::ElideRectangleText(remaining_string, |
193 text_font_list, | 213 text_font_list, |
194 chunk_bounds.width(), | 214 chunk_bounds.width(), |
195 chunk_bounds.height(), | 215 chunk_bounds.height(), |
196 gfx::IGNORE_LONG_WORDS, | 216 gfx::IGNORE_LONG_WORDS, |
197 &substrings); | 217 &substrings); |
198 | 218 |
199 DCHECK(!substrings.empty()); | 219 DCHECK(!substrings.empty()); |
200 string16 chunk = substrings[0]; | 220 string16 chunk = substrings[0]; |
(...skipping 10 matching lines...) Expand all Loading... |
211 break; | 231 break; |
212 } | 232 } |
213 | 233 |
214 x = 0; | 234 x = 0; |
215 line++; | 235 line++; |
216 continue; | 236 continue; |
217 } | 237 } |
218 | 238 |
219 scoped_ptr<Label> label; | 239 scoped_ptr<Label> label; |
220 if (position >= range.start()) { | 240 if (position >= range.start()) { |
221 const RangeStyleInfo& style_info = style_ranges.top().style_info; | 241 const RangeStyleInfo& style_info = current_range->style_info; |
222 | 242 |
223 if (style_info.disable_line_wrapping && chunk.size() < range.length() && | 243 if (style_info.disable_line_wrapping && chunk.size() < range.length() && |
224 position == range.start() && x != 0) { | 244 position == range.start() && x != 0) { |
225 // If the chunk should not be wrapped, try to fit it entirely on the | 245 // If the chunk should not be wrapped, try to fit it entirely on the |
226 // next line. | 246 // next line. |
227 x = 0; | 247 x = 0; |
228 line++; | 248 line++; |
229 continue; | 249 continue; |
230 } | 250 } |
231 | 251 |
232 chunk = chunk.substr(0, std::min(chunk.size(), range.end() - position)); | 252 chunk = chunk.substr(0, std::min(chunk.size(), range.end() - position)); |
233 | 253 |
234 label = CreateLabelRange(chunk, style_info, this); | 254 label = CreateLabelRange(chunk, style_info, this); |
235 | 255 |
236 if (style_info.is_link && !dry_run) | 256 if (style_info.is_link && !dry_run) |
237 link_targets_[label.get()] = range; | 257 link_targets_[label.get()] = range; |
238 | 258 |
239 if (position + chunk.size() >= range.end()) | 259 if (position + chunk.size() >= range.end()) |
240 style_ranges.pop(); | 260 ++current_range; |
241 } else { | 261 } else { |
242 // This chunk is normal text. | 262 // This chunk is normal text. |
243 if (position + chunk.size() > range.start()) | 263 if (position + chunk.size() > range.start()) |
244 chunk = chunk.substr(0, range.start() - position); | 264 chunk = chunk.substr(0, range.start() - position); |
245 label = CreateLabelRange(chunk, default_style_info_, this); | 265 label = CreateLabelRange(chunk, default_style_info_, this); |
246 } | 266 } |
247 | 267 |
248 if (displayed_on_background_color_set_) | 268 if (displayed_on_background_color_set_) |
249 label->SetBackgroundColor(displayed_on_background_color_); | 269 label->SetBackgroundColor(displayed_on_background_color_); |
250 label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled_); | 270 label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled_); |
251 | 271 |
252 // Lay out the views to overlap by 1 pixel to compensate for their border | 272 // Calculate the size of the optional focus border, and overlap by that |
253 // spacing. Otherwise, "<a>link</a>," will render as "link ,". | 273 // amount. Otherwise, "<a>link</a>," will render as "link ,". |
254 const int overlap = 1; | 274 gfx::Insets focus_border_insets(label->GetInsets()); |
| 275 focus_border_insets += -label->View::GetInsets(); |
255 const gfx::Size view_size = label->GetPreferredSize(); | 276 const gfx::Size view_size = label->GetPreferredSize(); |
256 DCHECK_EQ(line_height, view_size.height() - 2 * overlap); | 277 DCHECK_EQ(line_height, view_size.height() - focus_border_insets.height()); |
257 if (!dry_run) { | 278 if (!dry_run) { |
258 label->SetBoundsRect(gfx::Rect( | 279 label->SetBoundsRect(gfx::Rect( |
259 gfx::Point(GetInsets().left() + x - overlap, | 280 gfx::Point(GetInsets().left() + x - focus_border_insets.left(), |
260 GetInsets().top() + line * line_height - overlap), | 281 GetInsets().top() + line * line_height - |
| 282 focus_border_insets.top()), |
261 view_size)); | 283 view_size)); |
262 AddChildView(label.release()); | 284 AddChildView(label.release()); |
263 } | 285 } |
264 x += view_size.width() - 2 * overlap; | 286 x += view_size.width() - focus_border_insets.width(); |
265 | 287 |
266 remaining_string = remaining_string.substr(chunk.size()); | 288 remaining_string = remaining_string.substr(chunk.size()); |
267 } | 289 } |
268 | 290 |
269 return (line + 1) * line_height + GetInsets().height(); | 291 return gfx::Size(width, (line + 1) * line_height + GetInsets().height()); |
270 } | 292 } |
271 | 293 |
272 } // namespace views | 294 } // namespace views |
OLD | NEW |