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