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

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

Issue 111023004: Add GetMinimumSize() for Labels and ensure it's zero for empty Links. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Created 7 years 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/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),
91 is_drawn_(false),
85 displayed_on_background_color_set_(false), 92 displayed_on_background_color_set_(false),
86 auto_color_readability_enabled_(true) { 93 auto_color_readability_enabled_(true) {
87 TrimWhitespace(text, TRIM_TRAILING, &text_); 94 TrimWhitespace(text, TRIM_TRAILING, &text_);
88 } 95 }
89 96
90 StyledLabel::~StyledLabel() {} 97 StyledLabel::~StyledLabel() {}
91 98
92 void StyledLabel::SetText(const string16& text) { 99 void StyledLabel::SetText(const string16& text) {
93 text_ = text; 100 text_ = text;
94 style_ranges_ = std::priority_queue<StyleRange>(); 101 style_ranges_.clear();
95 RemoveAllChildViews(true); 102 RemoveAllChildViews(true);
96 PreferredSizeChanged(); 103 PreferredSizeChanged();
97 } 104 }
98 105
99 void StyledLabel::AddStyleRange(const gfx::Range& range, 106 void StyledLabel::AddStyleRange(const gfx::Range& range,
100 const RangeStyleInfo& style_info) { 107 const RangeStyleInfo& style_info) {
101 DCHECK(!range.is_reversed()); 108 DCHECK(!range.is_reversed());
102 DCHECK(!range.is_empty()); 109 DCHECK(!range.is_empty());
103 DCHECK(gfx::Range(0, text_.size()).Contains(range)); 110 DCHECK(gfx::Range(0, text_.size()).Contains(range));
104 111
105 style_ranges_.push(StyleRange(range, style_info)); 112 // Insert the new range in sorted order.
113 StyleRanges new_range;
114 new_range.push_front(StyleRange(range, style_info));
115 style_ranges_.merge(new_range);
106 116
107 PreferredSizeChanged(); 117 PreferredSizeChanged();
108 } 118 }
109 119
110 void StyledLabel::SetDefaultStyle(const RangeStyleInfo& style_info) { 120 void StyledLabel::SetDefaultStyle(const RangeStyleInfo& style_info) {
111 default_style_info_ = style_info; 121 default_style_info_ = style_info;
112 PreferredSizeChanged(); 122 PreferredSizeChanged();
113 } 123 }
114 124
115 void StyledLabel::SetDisplayedOnBackgroundColor(SkColor color) { 125 void StyledLabel::SetDisplayedOnBackgroundColor(SkColor color) {
116 displayed_on_background_color_ = color; 126 displayed_on_background_color_ = color;
117 displayed_on_background_color_set_ = true; 127 displayed_on_background_color_set_ = true;
118 } 128 }
119 129
120 gfx::Insets StyledLabel::GetInsets() const { 130 gfx::Insets StyledLabel::GetInsets() const {
121 gfx::Insets insets = View::GetInsets(); 131 gfx::Insets insets = View::GetInsets();
122 const gfx::Insets focus_border_padding(1, 1, 1, 1); 132
123 insets += focus_border_padding; 133 // We need a focus border iff we contain a link that will have a focus border.
134 // That in turn will be true only if the link is non-empty and we are drawn.
135 is_drawn_ = IsDrawn();
sky 2013/12/10 17:18:00 Similar to my comment in Label I think you should
136 if (is_drawn_) {
137 for (StyleRanges::const_iterator i(style_ranges_.begin());
138 i != style_ranges_.end(); ++i) {
139 if (i->style_info.is_link && !i->range.is_empty()) {
140 const gfx::Insets focus_border_padding(
141 Label::kFocusBorderPadding, Label::kFocusBorderPadding,
142 Label::kFocusBorderPadding, Label::kFocusBorderPadding);
143 insets += focus_border_padding;
144 break;
145 }
146 }
147 }
148
124 return insets; 149 return insets;
125 } 150 }
126 151
127 int StyledLabel::GetHeightForWidth(int w) { 152 int StyledLabel::GetHeightForWidth(int w) {
128 if (w != calculated_size_.width()) 153 if (w != calculated_size_.width())
129 calculated_size_ = gfx::Size(w, CalculateAndDoLayout(w, true)); 154 calculated_size_ = CalculateAndDoLayout(w, true);
130
131 return calculated_size_.height(); 155 return calculated_size_.height();
132 } 156 }
133 157
134 void StyledLabel::Layout() { 158 void StyledLabel::Layout() {
135 CalculateAndDoLayout(GetLocalBounds().width(), false); 159 calculated_size_ = CalculateAndDoLayout(GetLocalBounds().width(), false);
136 } 160 }
137 161
138 void StyledLabel::PreferredSizeChanged() { 162 void StyledLabel::PreferredSizeChanged() {
139 calculated_size_ = gfx::Size(); 163 calculated_size_ = gfx::Size();
140 View::PreferredSizeChanged(); 164 View::PreferredSizeChanged();
141 } 165 }
142 166
167 void StyledLabel::ViewHierarchyChanged(
168 const ViewHierarchyChangedDetails& details) {
169 if (IsDrawn() != is_drawn_)
170 PreferredSizeChanged();
171 }
172
173 void StyledLabel::VisibilityChanged(View* starting_from, bool is_visible) {
174 if (IsDrawn() != is_drawn_)
175 PreferredSizeChanged();
176 }
177
143 void StyledLabel::LinkClicked(Link* source, int event_flags) { 178 void StyledLabel::LinkClicked(Link* source, int event_flags) {
144 if (listener_) 179 if (listener_)
145 listener_->StyledLabelLinkClicked(link_targets_[source], event_flags); 180 listener_->StyledLabelLinkClicked(link_targets_[source], event_flags);
146 } 181 }
147 182
148 int StyledLabel::CalculateAndDoLayout(int width, bool dry_run) { 183 gfx::Size StyledLabel::CalculateAndDoLayout(int width, bool dry_run) {
149 if (!dry_run) { 184 if (!dry_run) {
150 RemoveAllChildViews(true); 185 RemoveAllChildViews(true);
151 link_targets_.clear(); 186 link_targets_.clear();
152 } 187 }
153 188
154 width -= GetInsets().width(); 189 width -= GetInsets().width();
155 if (width <= 0 || text_.empty()) 190 if (width <= 0 || text_.empty())
156 return 0; 191 return gfx::Size();
157 192
158 const int line_height = CalculateLineHeight(); 193 const int line_height = CalculateLineHeight();
159 // The index of the line we're on. 194 // The index of the line we're on.
160 int line = 0; 195 int line = 0;
161 // The x position (in pixels) of the line we're on, relative to content 196 // The x position (in pixels) of the line we're on, relative to content
162 // bounds. 197 // bounds.
163 int x = 0; 198 int x = 0;
164 199
165 string16 remaining_string = text_; 200 string16 remaining_string = text_;
166 std::priority_queue<StyleRange> style_ranges = style_ranges_; 201 StyleRanges::const_iterator current_range = style_ranges_.begin();
167 202
168 // Iterate over the text, creating a bunch of labels and links and laying them 203 // Iterate over the text, creating a bunch of labels and links and laying them
169 // out in the appropriate positions. 204 // out in the appropriate positions.
170 while (!remaining_string.empty()) { 205 while (!remaining_string.empty()) {
171 // Don't put whitespace at beginning of a line with an exception for the 206 // 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). 207 // first line (so the text's leading whitespace is respected).
173 if (x == 0 && line > 0) 208 if (x == 0 && line > 0)
174 TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string); 209 TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string);
175 210
176 gfx::Range range(gfx::Range::InvalidRange()); 211 gfx::Range range(gfx::Range::InvalidRange());
177 if (!style_ranges.empty()) 212 if (current_range != style_ranges_.end())
178 range = style_ranges.top().range; 213 range = current_range->range;
179 214
180 const size_t position = text_.size() - remaining_string.size(); 215 const size_t position = text_.size() - remaining_string.size();
181 216
182 const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height); 217 const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height);
183 std::vector<string16> substrings; 218 std::vector<string16> substrings;
184 gfx::FontList text_font_list; 219 gfx::FontList text_font_list;
185 // If the start of the remaining text is inside a styled range, the font 220 // 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 221 // style may differ from the base font. The font specified by the range
187 // should be used when eliding text. 222 // should be used when eliding text.
188 if (position >= range.start()) { 223 if (position >= range.start()) {
189 text_font_list = text_font_list.DeriveFontListWithSizeDeltaAndStyle( 224 text_font_list = text_font_list.DeriveFontListWithSizeDeltaAndStyle(
190 0, style_ranges.top().style_info.font_style); 225 0, current_range->style_info.font_style);
191 } 226 }
192 gfx::ElideRectangleText(remaining_string, 227 gfx::ElideRectangleText(remaining_string,
193 text_font_list, 228 text_font_list,
194 chunk_bounds.width(), 229 chunk_bounds.width(),
195 chunk_bounds.height(), 230 chunk_bounds.height(),
196 gfx::IGNORE_LONG_WORDS, 231 gfx::IGNORE_LONG_WORDS,
197 &substrings); 232 &substrings);
198 233
199 DCHECK(!substrings.empty()); 234 DCHECK(!substrings.empty());
200 string16 chunk = substrings[0]; 235 string16 chunk = substrings[0];
(...skipping 10 matching lines...) Expand all
211 break; 246 break;
212 } 247 }
213 248
214 x = 0; 249 x = 0;
215 line++; 250 line++;
216 continue; 251 continue;
217 } 252 }
218 253
219 scoped_ptr<Label> label; 254 scoped_ptr<Label> label;
220 if (position >= range.start()) { 255 if (position >= range.start()) {
221 const RangeStyleInfo& style_info = style_ranges.top().style_info; 256 const RangeStyleInfo& style_info = current_range->style_info;
222 257
223 if (style_info.disable_line_wrapping && chunk.size() < range.length() && 258 if (style_info.disable_line_wrapping && chunk.size() < range.length() &&
224 position == range.start() && x != 0) { 259 position == range.start() && x != 0) {
225 // If the chunk should not be wrapped, try to fit it entirely on the 260 // If the chunk should not be wrapped, try to fit it entirely on the
226 // next line. 261 // next line.
227 x = 0; 262 x = 0;
228 line++; 263 line++;
229 continue; 264 continue;
230 } 265 }
231 266
232 chunk = chunk.substr(0, std::min(chunk.size(), range.end() - position)); 267 chunk = chunk.substr(0, std::min(chunk.size(), range.end() - position));
233 268
234 label = CreateLabelRange(chunk, style_info, this); 269 label = CreateLabelRange(chunk, style_info, this);
235 270
236 if (style_info.is_link && !dry_run) 271 if (style_info.is_link && !dry_run)
237 link_targets_[label.get()] = range; 272 link_targets_[label.get()] = range;
238 273
239 if (position + chunk.size() >= range.end()) 274 if (position + chunk.size() >= range.end())
240 style_ranges.pop(); 275 ++current_range;
241 } else { 276 } else {
242 // This chunk is normal text. 277 // This chunk is normal text.
243 if (position + chunk.size() > range.start()) 278 if (position + chunk.size() > range.start())
244 chunk = chunk.substr(0, range.start() - position); 279 chunk = chunk.substr(0, range.start() - position);
245 label = CreateLabelRange(chunk, default_style_info_, this); 280 label = CreateLabelRange(chunk, default_style_info_, this);
246 } 281 }
247 282
248 if (displayed_on_background_color_set_) 283 if (displayed_on_background_color_set_)
249 label->SetBackgroundColor(displayed_on_background_color_); 284 label->SetBackgroundColor(displayed_on_background_color_);
250 label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled_); 285 label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled_);
251 286
252 // Lay out the views to overlap by 1 pixel to compensate for their border 287 // Calculate the size of the optional focus border, and overlap by that
253 // spacing. Otherwise, "<a>link</a>," will render as "link ,". 288 // amount. Otherwise, "<a>link</a>," will render as "link ,".
254 const int overlap = 1; 289 gfx::Insets focus_border_insets(label->GetInsets());
290 focus_border_insets += -label->View::GetInsets();
255 const gfx::Size view_size = label->GetPreferredSize(); 291 const gfx::Size view_size = label->GetPreferredSize();
256 DCHECK_EQ(line_height, view_size.height() - 2 * overlap); 292 DCHECK_EQ(line_height, view_size.height() - focus_border_insets.height());
257 if (!dry_run) { 293 if (!dry_run) {
258 label->SetBoundsRect(gfx::Rect( 294 label->SetBoundsRect(gfx::Rect(
259 gfx::Point(GetInsets().left() + x - overlap, 295 gfx::Point(GetInsets().left() + x - focus_border_insets.left(),
260 GetInsets().top() + line * line_height - overlap), 296 GetInsets().top() + line * line_height -
297 focus_border_insets.top()),
261 view_size)); 298 view_size));
262 AddChildView(label.release()); 299 AddChildView(label.release());
263 } 300 }
264 x += view_size.width() - 2 * overlap; 301 x += view_size.width() - focus_border_insets.width();
265 302
266 remaining_string = remaining_string.substr(chunk.size()); 303 remaining_string = remaining_string.substr(chunk.size());
267 } 304 }
268 305
269 return (line + 1) * line_height + GetInsets().height(); 306 return gfx::Size(width, (line + 1) * line_height + GetInsets().height());
270 } 307 }
271 308
272 } // namespace views 309 } // namespace views
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698