| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "ui/views/controls/styled_label.h" | |
| 6 | |
| 7 #include <vector> | |
| 8 | |
| 9 #include "base/strings/string_util.h" | |
| 10 #include "ui/gfx/font_list.h" | |
| 11 #include "ui/gfx/text_elider.h" | |
| 12 #include "ui/native_theme/native_theme.h" | |
| 13 #include "ui/views/controls/label.h" | |
| 14 #include "ui/views/controls/link.h" | |
| 15 #include "ui/views/controls/styled_label_listener.h" | |
| 16 | |
| 17 namespace views { | |
| 18 | |
| 19 | |
| 20 // Helpers -------------------------------------------------------------------- | |
| 21 | |
| 22 namespace { | |
| 23 | |
| 24 // Calculates the height of a line of text. Currently returns the height of | |
| 25 // a label. | |
| 26 int CalculateLineHeight(const gfx::FontList& font_list) { | |
| 27 Label label; | |
| 28 label.SetFontList(font_list); | |
| 29 return label.GetPreferredSize().height(); | |
| 30 } | |
| 31 | |
| 32 scoped_ptr<Label> CreateLabelRange( | |
| 33 const base::string16& text, | |
| 34 const gfx::FontList& font_list, | |
| 35 const StyledLabel::RangeStyleInfo& style_info, | |
| 36 views::LinkListener* link_listener) { | |
| 37 scoped_ptr<Label> result; | |
| 38 | |
| 39 if (style_info.is_link) { | |
| 40 Link* link = new Link(text); | |
| 41 link->set_listener(link_listener); | |
| 42 link->SetUnderline((style_info.font_style & gfx::Font::UNDERLINE) != 0); | |
| 43 result.reset(link); | |
| 44 } else { | |
| 45 result.reset(new Label(text)); | |
| 46 } | |
| 47 | |
| 48 result->SetEnabledColor(style_info.color); | |
| 49 result->SetFontList(font_list); | |
| 50 | |
| 51 if (!style_info.tooltip.empty()) | |
| 52 result->SetTooltipText(style_info.tooltip); | |
| 53 if (style_info.font_style != gfx::Font::NORMAL) { | |
| 54 result->SetFontList( | |
| 55 result->font_list().DeriveWithStyle(style_info.font_style)); | |
| 56 } | |
| 57 | |
| 58 return result.Pass(); | |
| 59 } | |
| 60 | |
| 61 } // namespace | |
| 62 | |
| 63 | |
| 64 // StyledLabel::RangeStyleInfo ------------------------------------------------ | |
| 65 | |
| 66 StyledLabel::RangeStyleInfo::RangeStyleInfo() | |
| 67 : font_style(gfx::Font::NORMAL), | |
| 68 color(ui::NativeTheme::instance()->GetSystemColor( | |
| 69 ui::NativeTheme::kColorId_LabelEnabledColor)), | |
| 70 disable_line_wrapping(false), | |
| 71 is_link(false) {} | |
| 72 | |
| 73 StyledLabel::RangeStyleInfo::~RangeStyleInfo() {} | |
| 74 | |
| 75 // static | |
| 76 StyledLabel::RangeStyleInfo StyledLabel::RangeStyleInfo::CreateForLink() { | |
| 77 RangeStyleInfo result; | |
| 78 result.disable_line_wrapping = true; | |
| 79 result.is_link = true; | |
| 80 result.color = Link::GetDefaultEnabledColor(); | |
| 81 return result; | |
| 82 } | |
| 83 | |
| 84 | |
| 85 // StyledLabel::StyleRange ---------------------------------------------------- | |
| 86 | |
| 87 bool StyledLabel::StyleRange::operator<( | |
| 88 const StyledLabel::StyleRange& other) const { | |
| 89 return range.start() < other.range.start(); | |
| 90 } | |
| 91 | |
| 92 | |
| 93 // StyledLabel ---------------------------------------------------------------- | |
| 94 | |
| 95 StyledLabel::StyledLabel(const base::string16& text, | |
| 96 StyledLabelListener* listener) | |
| 97 : specified_line_height_(0), | |
| 98 listener_(listener), | |
| 99 displayed_on_background_color_set_(false), | |
| 100 auto_color_readability_enabled_(true) { | |
| 101 base::TrimWhitespace(text, base::TRIM_TRAILING, &text_); | |
| 102 } | |
| 103 | |
| 104 StyledLabel::~StyledLabel() {} | |
| 105 | |
| 106 void StyledLabel::SetText(const base::string16& text) { | |
| 107 text_ = text; | |
| 108 style_ranges_.clear(); | |
| 109 RemoveAllChildViews(true); | |
| 110 PreferredSizeChanged(); | |
| 111 } | |
| 112 | |
| 113 void StyledLabel::SetBaseFontList(const gfx::FontList& font_list) { | |
| 114 font_list_ = font_list; | |
| 115 PreferredSizeChanged(); | |
| 116 } | |
| 117 | |
| 118 void StyledLabel::AddStyleRange(const gfx::Range& range, | |
| 119 const RangeStyleInfo& style_info) { | |
| 120 DCHECK(!range.is_reversed()); | |
| 121 DCHECK(!range.is_empty()); | |
| 122 DCHECK(gfx::Range(0, text_.size()).Contains(range)); | |
| 123 | |
| 124 // Insert the new range in sorted order. | |
| 125 StyleRanges new_range; | |
| 126 new_range.push_front(StyleRange(range, style_info)); | |
| 127 style_ranges_.merge(new_range); | |
| 128 | |
| 129 PreferredSizeChanged(); | |
| 130 } | |
| 131 | |
| 132 void StyledLabel::SetDefaultStyle(const RangeStyleInfo& style_info) { | |
| 133 default_style_info_ = style_info; | |
| 134 PreferredSizeChanged(); | |
| 135 } | |
| 136 | |
| 137 void StyledLabel::SetLineHeight(int line_height) { | |
| 138 specified_line_height_ = line_height; | |
| 139 PreferredSizeChanged(); | |
| 140 } | |
| 141 | |
| 142 void StyledLabel::SetDisplayedOnBackgroundColor(SkColor color) { | |
| 143 displayed_on_background_color_ = color; | |
| 144 displayed_on_background_color_set_ = true; | |
| 145 } | |
| 146 | |
| 147 gfx::Insets StyledLabel::GetInsets() const { | |
| 148 gfx::Insets insets = View::GetInsets(); | |
| 149 | |
| 150 // We need a focus border iff we contain a link that will have a focus border. | |
| 151 // That in turn will be true only if the link is non-empty. | |
| 152 for (StyleRanges::const_iterator i(style_ranges_.begin()); | |
| 153 i != style_ranges_.end(); ++i) { | |
| 154 if (i->style_info.is_link && !i->range.is_empty()) { | |
| 155 const gfx::Insets focus_border_padding( | |
| 156 Label::kFocusBorderPadding, Label::kFocusBorderPadding, | |
| 157 Label::kFocusBorderPadding, Label::kFocusBorderPadding); | |
| 158 insets += focus_border_padding; | |
| 159 break; | |
| 160 } | |
| 161 } | |
| 162 | |
| 163 return insets; | |
| 164 } | |
| 165 | |
| 166 int StyledLabel::GetHeightForWidth(int w) const { | |
| 167 if (w != calculated_size_.width()) { | |
| 168 // TODO(erg): Munge the const-ness of the style label. CalculateAndDoLayout | |
| 169 // doesn't actually make any changes to member variables when |dry_run| is | |
| 170 // set to true. In general, the mutating and non-mutating parts shouldn't | |
| 171 // be in the same codepath. | |
| 172 calculated_size_ = | |
| 173 const_cast<StyledLabel*>(this)->CalculateAndDoLayout(w, true); | |
| 174 } | |
| 175 return calculated_size_.height(); | |
| 176 } | |
| 177 | |
| 178 void StyledLabel::Layout() { | |
| 179 calculated_size_ = CalculateAndDoLayout(GetLocalBounds().width(), false); | |
| 180 } | |
| 181 | |
| 182 void StyledLabel::PreferredSizeChanged() { | |
| 183 calculated_size_ = gfx::Size(); | |
| 184 View::PreferredSizeChanged(); | |
| 185 } | |
| 186 | |
| 187 void StyledLabel::LinkClicked(Link* source, int event_flags) { | |
| 188 if (listener_) | |
| 189 listener_->StyledLabelLinkClicked(link_targets_[source], event_flags); | |
| 190 } | |
| 191 | |
| 192 gfx::Size StyledLabel::CalculateAndDoLayout(int width, bool dry_run) { | |
| 193 if (!dry_run) { | |
| 194 RemoveAllChildViews(true); | |
| 195 link_targets_.clear(); | |
| 196 } | |
| 197 | |
| 198 width -= GetInsets().width(); | |
| 199 if (width <= 0 || text_.empty()) | |
| 200 return gfx::Size(); | |
| 201 | |
| 202 const int line_height = specified_line_height_ > 0 ? specified_line_height_ | |
| 203 : CalculateLineHeight(font_list_); | |
| 204 // The index of the line we're on. | |
| 205 int line = 0; | |
| 206 // The x position (in pixels) of the line we're on, relative to content | |
| 207 // bounds. | |
| 208 int x = 0; | |
| 209 | |
| 210 base::string16 remaining_string = text_; | |
| 211 StyleRanges::const_iterator current_range = style_ranges_.begin(); | |
| 212 | |
| 213 // Iterate over the text, creating a bunch of labels and links and laying them | |
| 214 // out in the appropriate positions. | |
| 215 while (!remaining_string.empty()) { | |
| 216 // Don't put whitespace at beginning of a line with an exception for the | |
| 217 // first line (so the text's leading whitespace is respected). | |
| 218 if (x == 0 && line > 0) { | |
| 219 base::TrimWhitespace(remaining_string, base::TRIM_LEADING, | |
| 220 &remaining_string); | |
| 221 } | |
| 222 | |
| 223 gfx::Range range(gfx::Range::InvalidRange()); | |
| 224 if (current_range != style_ranges_.end()) | |
| 225 range = current_range->range; | |
| 226 | |
| 227 const size_t position = text_.size() - remaining_string.size(); | |
| 228 | |
| 229 const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height); | |
| 230 std::vector<base::string16> substrings; | |
| 231 gfx::FontList text_font_list = font_list_; | |
| 232 // If the start of the remaining text is inside a styled range, the font | |
| 233 // style may differ from the base font. The font specified by the range | |
| 234 // should be used when eliding text. | |
| 235 if (position >= range.start()) { | |
| 236 text_font_list = text_font_list.DeriveWithStyle( | |
| 237 current_range->style_info.font_style); | |
| 238 } | |
| 239 gfx::ElideRectangleText(remaining_string, | |
| 240 text_font_list, | |
| 241 chunk_bounds.width(), | |
| 242 chunk_bounds.height(), | |
| 243 gfx::IGNORE_LONG_WORDS, | |
| 244 &substrings); | |
| 245 | |
| 246 DCHECK(!substrings.empty()); | |
| 247 base::string16 chunk = substrings[0]; | |
| 248 if (chunk.empty()) { | |
| 249 // Nothing fits on this line. Start a new line. | |
| 250 // If x is 0, first line may have leading whitespace that doesn't fit in a | |
| 251 // single line, so try trimming those. Otherwise there is no room for | |
| 252 // anything; abort. | |
| 253 if (x == 0) { | |
| 254 if (line == 0) { | |
| 255 base::TrimWhitespace(remaining_string, base::TRIM_LEADING, | |
| 256 &remaining_string); | |
| 257 continue; | |
| 258 } | |
| 259 break; | |
| 260 } | |
| 261 | |
| 262 x = 0; | |
| 263 line++; | |
| 264 continue; | |
| 265 } | |
| 266 | |
| 267 scoped_ptr<Label> label; | |
| 268 if (position >= range.start()) { | |
| 269 const RangeStyleInfo& style_info = current_range->style_info; | |
| 270 | |
| 271 if (style_info.disable_line_wrapping && chunk.size() < range.length() && | |
| 272 position == range.start() && x != 0) { | |
| 273 // If the chunk should not be wrapped, try to fit it entirely on the | |
| 274 // next line. | |
| 275 x = 0; | |
| 276 line++; | |
| 277 continue; | |
| 278 } | |
| 279 | |
| 280 chunk = chunk.substr(0, std::min(chunk.size(), range.end() - position)); | |
| 281 | |
| 282 label = CreateLabelRange(chunk, font_list_, style_info, this); | |
| 283 | |
| 284 if (style_info.is_link && !dry_run) | |
| 285 link_targets_[label.get()] = range; | |
| 286 | |
| 287 if (position + chunk.size() >= range.end()) | |
| 288 ++current_range; | |
| 289 } else { | |
| 290 // This chunk is normal text. | |
| 291 if (position + chunk.size() > range.start()) | |
| 292 chunk = chunk.substr(0, range.start() - position); | |
| 293 label = CreateLabelRange(chunk, font_list_, default_style_info_, this); | |
| 294 } | |
| 295 | |
| 296 if (displayed_on_background_color_set_) | |
| 297 label->SetBackgroundColor(displayed_on_background_color_); | |
| 298 label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled_); | |
| 299 | |
| 300 // Calculate the size of the optional focus border, and overlap by that | |
| 301 // amount. Otherwise, "<a>link</a>," will render as "link ,". | |
| 302 gfx::Insets focus_border_insets(label->GetInsets()); | |
| 303 focus_border_insets += -label->View::GetInsets(); | |
| 304 const gfx::Size view_size = label->GetPreferredSize(); | |
| 305 if (!dry_run) { | |
| 306 label->SetBoundsRect(gfx::Rect( | |
| 307 gfx::Point(GetInsets().left() + x - focus_border_insets.left(), | |
| 308 GetInsets().top() + line * line_height - | |
| 309 focus_border_insets.top()), | |
| 310 view_size)); | |
| 311 AddChildView(label.release()); | |
| 312 } | |
| 313 x += view_size.width() - focus_border_insets.width(); | |
| 314 | |
| 315 remaining_string = remaining_string.substr(chunk.size()); | |
| 316 } | |
| 317 | |
| 318 // The user-specified line height only applies to interline spacing, so the | |
| 319 // final line's height is unaffected. | |
| 320 int total_height = line * line_height + | |
| 321 CalculateLineHeight(font_list_) + GetInsets().height(); | |
| 322 return gfx::Size(width, total_height); | |
| 323 } | |
| 324 | |
| 325 } // namespace views | |
| OLD | NEW |