Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(612)

Side by Side Diff: ui/views/controls/styled_label.cc

Issue 12906002: Add ability to defined ranges with different styles in StyledLabel (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: . Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « ui/views/controls/styled_label.h ('k') | ui/views/controls/styled_label_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
OLDNEW
« no previous file with comments | « ui/views/controls/styled_label.h ('k') | ui/views/controls/styled_label_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698