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/string_util.h" | 9 #include "base/string_util.h" |
10 #include "ui/base/text/text_elider.h" | 10 #include "ui/base/text/text_elider.h" |
11 #include "ui/views/controls/label.h" | 11 #include "ui/views/controls/label.h" |
12 #include "ui/views/controls/link.h" | 12 #include "ui/views/controls/link.h" |
13 #include "ui/views/controls/styled_label_listener.h" | 13 #include "ui/views/controls/styled_label_listener.h" |
14 | 14 |
15 namespace views { | 15 namespace views { |
16 | 16 |
17 namespace { | 17 namespace { |
18 | 18 |
19 // Calculates the height of a line of text. Currently returns the height of | 19 // Calculates the height of a line of text. Currently returns the height of |
20 // a label. | 20 // a label. |
21 int CalculateLineHeight() { | 21 int CalculateLineHeight() { |
22 Label label; | 22 Label label; |
23 return label.GetPreferredSize().height(); | 23 return label.GetPreferredSize().height(); |
24 } | 24 } |
25 | 25 |
| 26 scoped_ptr<View> CreateLabelRange(const string16& text, |
| 27 const StyledLabel::RangeStyleInfo& style_info, |
| 28 views::LinkListener* link_listener) { |
| 29 scoped_ptr<Label> result; |
| 30 |
| 31 if (style_info.is_link) { |
| 32 Link* link = new Link(text); |
| 33 link->set_listener(link_listener); |
| 34 link->SetUnderline((style_info.font_style & gfx::Font::UNDERLINE) != 0); |
| 35 result.reset(link); |
| 36 } else { |
| 37 Label* label = new Label(text); |
| 38 // Give the label a focus border so that its preferred size matches |
| 39 // links' preferred sizes |
| 40 label->SetHasFocusBorder(true); |
| 41 |
| 42 result.reset(label); |
| 43 } |
| 44 |
| 45 if (!style_info.tooltip.empty()) |
| 46 result->SetTooltipText(style_info.tooltip); |
| 47 if (style_info.font_style != gfx::Font::NORMAL) |
| 48 result->SetFont(result->font().DeriveFont(0, style_info.font_style)); |
| 49 |
| 50 return scoped_ptr<View>(result.release()); |
| 51 } |
| 52 |
26 } // namespace | 53 } // namespace |
27 | 54 |
28 bool StyledLabel::LinkRange::operator<( | 55 |
29 const StyledLabel::LinkRange& other) const { | 56 StyledLabel::RangeStyleInfo::RangeStyleInfo() |
| 57 : font_style(gfx::Font::NORMAL), |
| 58 disable_line_wrapping(false), |
| 59 is_link(false) { |
| 60 } |
| 61 |
| 62 StyledLabel::RangeStyleInfo::~RangeStyleInfo() {} |
| 63 |
| 64 // static |
| 65 StyledLabel::RangeStyleInfo StyledLabel::RangeStyleInfo::CreateForLink() { |
| 66 RangeStyleInfo result; |
| 67 result.disable_line_wrapping = true; |
| 68 result.is_link = true; |
| 69 result.font_style = gfx::Font::UNDERLINE; |
| 70 return result; |
| 71 } |
| 72 |
| 73 bool StyledLabel::StyleRange::operator<( |
| 74 const StyledLabel::StyleRange& other) const { |
30 // Intentionally reversed so the priority queue is sorted by smallest first. | 75 // Intentionally reversed so the priority queue is sorted by smallest first. |
31 return range.start() > other.range.start(); | 76 return range.start() > other.range.start(); |
32 } | 77 } |
33 | 78 |
34 StyledLabel::StyledLabel(const string16& text, StyledLabelListener* listener) | 79 StyledLabel::StyledLabel(const string16& text, StyledLabelListener* listener) |
35 : text_(text), | 80 : listener_(listener) { |
36 listener_(listener) {} | 81 TrimWhitespace(text, TRIM_TRAILING, &text_); |
| 82 } |
37 | 83 |
38 StyledLabel::~StyledLabel() {} | 84 StyledLabel::~StyledLabel() {} |
39 | 85 |
40 void StyledLabel::SetText(const string16& text) { | 86 void StyledLabel::SetText(const string16& text) { |
41 text_ = text; | 87 text_ = text; |
42 calculated_size_ = gfx::Size(); | 88 calculated_size_ = gfx::Size(); |
43 link_ranges_ = std::priority_queue<LinkRange>(); | 89 style_ranges_ = std::priority_queue<StyleRange>(); |
44 RemoveAllChildViews(true); | 90 RemoveAllChildViews(true); |
45 PreferredSizeChanged(); | 91 PreferredSizeChanged(); |
46 } | 92 } |
47 | 93 |
48 void StyledLabel::AddLink(const ui::Range& range) { | 94 void StyledLabel::AddStyleRange(const ui::Range& range, |
| 95 const RangeStyleInfo& style_info) { |
49 DCHECK(!range.is_reversed()); | 96 DCHECK(!range.is_reversed()); |
50 DCHECK(!range.is_empty()); | 97 DCHECK(!range.is_empty()); |
51 DCHECK(ui::Range(0, text_.size()).Contains(range)); | 98 DCHECK(ui::Range(0, text_.size()).Contains(range)); |
52 link_ranges_.push(LinkRange(range)); | 99 |
| 100 style_ranges_.push(StyleRange(range, style_info)); |
| 101 |
53 calculated_size_ = gfx::Size(); | 102 calculated_size_ = gfx::Size(); |
54 PreferredSizeChanged(); | 103 PreferredSizeChanged(); |
55 } | 104 } |
56 | 105 |
57 gfx::Insets StyledLabel::GetInsets() const { | 106 gfx::Insets StyledLabel::GetInsets() const { |
58 gfx::Insets insets = View::GetInsets(); | 107 gfx::Insets insets = View::GetInsets(); |
59 const gfx::Insets focus_border_padding(1, 1, 1, 1); | 108 const gfx::Insets focus_border_padding(1, 1, 1, 1); |
60 insets += focus_border_padding; | 109 insets += focus_border_padding; |
61 return insets; | 110 return insets; |
62 } | 111 } |
(...skipping 24 matching lines...) Expand all Loading... |
87 return 0; | 136 return 0; |
88 | 137 |
89 const int line_height = CalculateLineHeight(); | 138 const int line_height = CalculateLineHeight(); |
90 // The index of the line we're on. | 139 // The index of the line we're on. |
91 int line = 0; | 140 int line = 0; |
92 // The x position (in pixels) of the line we're on, relative to content | 141 // The x position (in pixels) of the line we're on, relative to content |
93 // bounds. | 142 // bounds. |
94 int x = 0; | 143 int x = 0; |
95 | 144 |
96 string16 remaining_string = text_; | 145 string16 remaining_string = text_; |
97 std::priority_queue<LinkRange> link_ranges = link_ranges_; | 146 std::priority_queue<StyleRange> style_ranges = style_ranges_; |
98 | 147 |
99 // Iterate over the text, creating a bunch of labels and links and laying them | 148 // Iterate over the text, creating a bunch of labels and links and laying them |
100 // out in the appropriate positions. | 149 // out in the appropriate positions. |
101 while (!remaining_string.empty()) { | 150 while (!remaining_string.empty()) { |
102 // Don't put whitespace at beginning of a line. | 151 // Don't put whitespace at beginning of a line with an exception for the |
103 if (x == 0) | 152 // first line (so the text's leading whitespace is respected). |
| 153 if (x == 0 && line > 0) |
104 TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string); | 154 TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string); |
105 | 155 |
106 ui::Range range(ui::Range::InvalidRange()); | 156 ui::Range range(ui::Range::InvalidRange()); |
107 if (!link_ranges.empty()) | 157 if (!style_ranges.empty()) |
108 range = link_ranges.top().range; | 158 range = style_ranges.top().range; |
| 159 |
| 160 const size_t position = text_.size() - remaining_string.size(); |
109 | 161 |
110 const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height); | 162 const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height); |
111 std::vector<string16> substrings; | 163 std::vector<string16> substrings; |
| 164 gfx::Font text_font; |
| 165 // If the start of the remaining text is inside a styled range, the font |
| 166 // style may differ from the base font. The font specified by the range |
| 167 // should be used when eliding text. |
| 168 if (position >= range.start()) { |
| 169 text_font = |
| 170 text_font.DeriveFont(0, style_ranges.top().style_info.font_style); |
| 171 } |
112 ui::ElideRectangleText(remaining_string, | 172 ui::ElideRectangleText(remaining_string, |
113 gfx::Font(), | 173 text_font, |
114 chunk_bounds.width(), | 174 chunk_bounds.width(), |
115 chunk_bounds.height(), | 175 chunk_bounds.height(), |
116 ui::IGNORE_LONG_WORDS, | 176 ui::IGNORE_LONG_WORDS, |
117 &substrings); | 177 &substrings); |
118 | 178 |
| 179 DCHECK(!substrings.empty()); |
119 string16 chunk = substrings[0]; | 180 string16 chunk = substrings[0]; |
120 if (chunk.empty()) { | 181 if (chunk.empty()) { |
121 // Nothing fit on this line. Start a new line. If x is 0, there's no room | 182 // Nothing fits on this line. Start a new line. |
122 // for anything. Just abort. | 183 // If x is 0, first line may have leading whitespace that doesn't fit in a |
123 if (x == 0) | 184 // single line, so try trimming those. Otherwise there is no room for |
| 185 // anything; abort. |
| 186 if (x == 0) { |
| 187 if (line == 0) { |
| 188 TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string); |
| 189 continue; |
| 190 } |
124 break; | 191 break; |
| 192 } |
125 | 193 |
126 x = 0; | 194 x = 0; |
127 line++; | 195 line++; |
128 continue; | 196 continue; |
129 } | 197 } |
130 | 198 |
131 scoped_ptr<View> view; | 199 scoped_ptr<View> view; |
132 const size_t position = text_.size() - remaining_string.size(); | |
133 if (position >= range.start()) { | 200 if (position >= range.start()) { |
134 // This chunk is a link. | 201 const RangeStyleInfo& style_info = style_ranges.top().style_info; |
135 if (chunk.size() < range.length() && x != 0) { | 202 |
136 // Don't wrap links. Try to fit them entirely on one line. | 203 if (style_info.disable_line_wrapping && chunk.size() < range.length() && |
| 204 position == range.start() && x != 0) { |
| 205 // If the chunk should not be wrapped, try to fit it entirely on the |
| 206 // next line. |
137 x = 0; | 207 x = 0; |
138 line++; | 208 line++; |
139 continue; | 209 continue; |
140 } | 210 } |
141 | 211 |
142 chunk = chunk.substr(0, range.length()); | 212 chunk = chunk.substr(0, std::min(chunk.size(), range.end() - position)); |
143 Link* link = new Link(chunk); | 213 |
144 link->set_listener(this); | 214 view = CreateLabelRange(chunk, style_info, this); |
145 if (!dry_run) | 215 |
146 link_targets_[link] = range; | 216 if (style_info.is_link && !dry_run) |
147 view.reset(link); | 217 link_targets_[view.get()] = range; |
148 link_ranges.pop(); | 218 |
| 219 if (position + chunk.size() >= range.end()) |
| 220 style_ranges.pop(); |
149 } else { | 221 } else { |
150 // This chunk is normal text. | 222 // This chunk is normal text. |
151 if (position + chunk.size() > range.start()) | 223 if (position + chunk.size() > range.start()) |
152 chunk = chunk.substr(0, range.start() - position); | 224 chunk = chunk.substr(0, range.start() - position); |
153 | 225 view = CreateLabelRange(chunk, RangeStyleInfo(), this); |
154 Label* label = new Label(chunk); | |
155 // Give the label a focus border so that its preferred size matches | |
156 // links' preferred sizes. | |
157 label->SetHasFocusBorder(true); | |
158 view.reset(label); | |
159 } | 226 } |
160 | 227 |
161 // Lay out the views to overlap by 1 pixel to compensate for their border | 228 // Lay out the views to overlap by 1 pixel to compensate for their border |
162 // spacing. Otherwise, "<a>link</a>," will render as "link ,". | 229 // spacing. Otherwise, "<a>link</a>," will render as "link ,". |
163 const int overlap = 1; | 230 const int overlap = 1; |
164 const gfx::Size view_size = view->GetPreferredSize(); | 231 const gfx::Size view_size = view->GetPreferredSize(); |
165 DCHECK_EQ(line_height, view_size.height() - 2 * overlap); | 232 DCHECK_EQ(line_height, view_size.height() - 2 * overlap); |
166 if (!dry_run) { | 233 if (!dry_run) { |
167 view->SetBoundsRect(gfx::Rect( | 234 view->SetBoundsRect(gfx::Rect( |
168 gfx::Point(GetInsets().left() + x - overlap, | 235 gfx::Point(GetInsets().left() + x - overlap, |
169 GetInsets().top() + line * line_height - overlap), | 236 GetInsets().top() + line * line_height - overlap), |
170 view_size)); | 237 view_size)); |
171 AddChildView(view.release()); | 238 AddChildView(view.release()); |
172 } | 239 } |
173 x += view_size.width() - 2 * overlap; | 240 x += view_size.width() - 2 * overlap; |
174 | 241 |
175 remaining_string = remaining_string.substr(chunk.size()); | 242 remaining_string = remaining_string.substr(chunk.size()); |
176 } | 243 } |
177 | 244 |
178 return (line + 1) * line_height + GetInsets().height(); | 245 return (line + 1) * line_height + GetInsets().height(); |
179 } | 246 } |
180 | 247 |
181 } // namespace views | 248 } // namespace views |
OLD | NEW |