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

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
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.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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698