Chromium Code Reviews| 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/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 Loading... | |
| 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 |
| OLD | NEW |