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/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)); | |
|
Evan Stade
2013/03/18 23:20:50
setting a different font means that the bounds cal
tbarzic
2013/03/19 01:07:34
good point.. is it safe to assume that the font he
Evan Stade
2013/03/19 04:49:29
height and width would both need to stay the same
tonibarzic
2013/03/19 16:27:53
Yes, I understand that width is important for wrap
| |
| 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 : text_(text), |
| 36 listener_(listener) {} | 72 listener_(listener) {} |
| 37 | 73 |
| 38 StyledLabel::~StyledLabel() {} | 74 StyledLabel::~StyledLabel() {} |
| 39 | 75 |
| 40 void StyledLabel::AddLink(const ui::Range& range) { | 76 void StyledLabel::AddLink(const ui::Range& range) { |
| 41 DCHECK(!range.is_reversed()); | 77 DCHECK(!range.is_reversed()); |
| 42 DCHECK(!range.is_empty()); | 78 DCHECK(!range.is_empty()); |
| 43 DCHECK(ui::Range(0, text_.size()).Contains(range)); | 79 DCHECK(ui::Range(0, text_.size()).Contains(range)); |
| 44 link_ranges_.push(LinkRange(range)); | 80 |
| 81 RangeStyleInfo style_info; | |
| 82 style_info.disable_line_wrapping = true; | |
| 83 style_info.linkify = true; | |
| 84 style_info.font_style = gfx::Font::UNDERLINE; | |
| 85 style_ranges_.push(StyleRange(range, style_info)); | |
| 86 | |
| 45 calculated_size_ = gfx::Size(); | 87 calculated_size_ = gfx::Size(); |
| 46 InvalidateLayout(); | 88 InvalidateLayout(); |
| 47 } | 89 } |
| 90 | |
| 91 void StyledLabel::AddStyleRange(const ui::Range& range, | |
| 92 const RangeStyleInfo& style_info) { | |
| 93 DCHECK(!range.is_reversed()); | |
| 94 DCHECK(!range.is_empty()); | |
| 95 DCHECK(ui::Range(0, text_.size()).Contains(range)); | |
| 96 | |
| 97 style_ranges_.push(StyleRange(range, style_info)); | |
| 98 | |
| 99 calculated_size_ = gfx::Size(); | |
| 100 InvalidateLayout(); | |
| 101 } | |
| 48 | 102 |
| 49 gfx::Insets StyledLabel::GetInsets() const { | 103 gfx::Insets StyledLabel::GetInsets() const { |
| 50 gfx::Insets insets = View::GetInsets(); | 104 gfx::Insets insets = View::GetInsets(); |
| 51 const gfx::Insets focus_border_padding(1, 1, 1, 1); | 105 const gfx::Insets focus_border_padding(1, 1, 1, 1); |
| 52 insets += focus_border_padding; | 106 insets += focus_border_padding; |
| 53 return insets; | 107 return insets; |
| 54 } | 108 } |
| 55 | 109 |
| 56 int StyledLabel::GetHeightForWidth(int w) { | 110 int StyledLabel::GetHeightForWidth(int w) { |
| 57 if (w != calculated_size_.width()) | 111 if (w != calculated_size_.width()) |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 79 return 0; | 133 return 0; |
| 80 | 134 |
| 81 const int line_height = CalculateLineHeight(); | 135 const int line_height = CalculateLineHeight(); |
| 82 // The index of the line we're on. | 136 // The index of the line we're on. |
| 83 int line = 0; | 137 int line = 0; |
| 84 // The x position (in pixels) of the line we're on, relative to content | 138 // The x position (in pixels) of the line we're on, relative to content |
| 85 // bounds. | 139 // bounds. |
| 86 int x = 0; | 140 int x = 0; |
| 87 | 141 |
| 88 string16 remaining_string = text_; | 142 string16 remaining_string = text_; |
| 89 std::priority_queue<LinkRange> link_ranges = link_ranges_; | 143 std::priority_queue<StyleRange> style_ranges = style_ranges_; |
| 90 | 144 |
| 91 // Iterate over the text, creating a bunch of labels and links and laying them | 145 // Iterate over the text, creating a bunch of labels and links and laying them |
| 92 // out in the appropriate positions. | 146 // out in the appropriate positions. |
| 93 while (!remaining_string.empty()) { | 147 while (!remaining_string.empty()) { |
| 94 // Don't put whitespace at beginning of a line. | 148 // Don't put whitespace at beginning of a line. |
| 95 if (x == 0) | 149 if (x == 0) |
| 96 TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string); | 150 TrimWhitespace(remaining_string, TRIM_LEADING, &remaining_string); |
| 97 | 151 |
| 98 ui::Range range(ui::Range::InvalidRange()); | 152 ui::Range range(ui::Range::InvalidRange()); |
| 99 if (!link_ranges.empty()) | 153 if (!style_ranges.empty()) |
| 100 range = link_ranges.top().range; | 154 range = style_ranges.top().range; |
| 101 | 155 |
| 102 const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height); | 156 const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height); |
| 103 std::vector<string16> substrings; | 157 std::vector<string16> substrings; |
| 104 ui::ElideRectangleText(remaining_string, | 158 ui::ElideRectangleText(remaining_string, |
| 105 gfx::Font(), | 159 gfx::Font(), |
| 106 chunk_bounds.width(), | 160 chunk_bounds.width(), |
| 107 chunk_bounds.height(), | 161 chunk_bounds.height(), |
| 108 ui::IGNORE_LONG_WORDS, | 162 ui::IGNORE_LONG_WORDS, |
| 109 &substrings); | 163 &substrings); |
| 110 | 164 |
| 165 // This happens when only whitespace is left. | |
|
Evan Stade
2013/03/18 23:20:50
it doesn't seem like this should be what happens i
tbarzic
2013/03/19 01:07:34
yeah we could do that, the downside is trimming th
Evan Stade
2013/03/25 22:30:11
but you still don't handle that correctly, because
tbarzic
2013/03/26 21:32:33
ok then, added trimming in the constructor.
note t
| |
| 166 if (substrings.empty()) | |
| 167 break; | |
| 168 | |
| 111 string16 chunk = substrings[0]; | 169 string16 chunk = substrings[0]; |
| 112 if (chunk.empty()) { | 170 if (chunk.empty()) { |
| 113 // Nothing fit on this line. Start a new line. If x is 0, there's no room | 171 // Nothing fit on this line. Start a new line. If x is 0, there's no room |
| 114 // for anything. Just abort. | 172 // for anything. Just abort. |
| 115 if (x == 0) | 173 if (x == 0) |
| 116 break; | 174 break; |
| 117 | 175 |
| 118 x = 0; | 176 x = 0; |
| 119 line++; | 177 line++; |
| 120 continue; | 178 continue; |
| 121 } | 179 } |
| 122 | 180 |
| 123 scoped_ptr<View> view; | 181 scoped_ptr<View> view; |
| 124 const size_t position = text_.size() - remaining_string.size(); | 182 const size_t position = text_.size() - remaining_string.size(); |
| 125 if (position >= range.start()) { | 183 if (position >= range.start()) { |
| 126 // This chunk is a link. | 184 const RangeStyleInfo& style_info = style_ranges.top().style_info; |
| 127 if (chunk.size() < range.length() && x != 0) { | 185 |
| 128 // Don't wrap links. Try to fit them entirely on one line. | 186 if (style_info.disable_line_wrapping && chunk.size() < range.length() && |
| 187 position == range.start() && x != 0) { | |
| 188 // If the chunk should not be wrapped, try to fit it entirely on the | |
| 189 // next line. | |
| 129 x = 0; | 190 x = 0; |
| 130 line++; | 191 line++; |
| 131 continue; | 192 continue; |
| 132 } | 193 } |
| 133 | 194 |
| 134 chunk = chunk.substr(0, range.length()); | 195 chunk = chunk.substr(0, std::min(chunk.size(), range.end() - position)); |
| 135 Link* link = new Link(chunk); | 196 |
| 136 link->set_listener(this); | 197 view = CreateLabelRange(chunk, style_info, this); |
| 137 if (!dry_run) | 198 |
| 138 link_targets_[link] = range; | 199 if (style_info.linkify && !dry_run) |
| 139 view.reset(link); | 200 link_targets_[view.get()] = range; |
| 140 link_ranges.pop(); | 201 |
| 202 if (position + chunk.size() >= range.end()) | |
| 203 style_ranges.pop(); | |
| 141 } else { | 204 } else { |
| 142 // This chunk is normal text. | 205 // This chunk is normal text. |
| 143 if (position + chunk.size() > range.start()) | 206 if (position + chunk.size() > range.start()) |
| 144 chunk = chunk.substr(0, range.start() - position); | 207 chunk = chunk.substr(0, range.start() - position); |
| 145 | 208 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 } | 209 } |
| 152 | 210 |
| 153 // Lay out the views to overlap by 1 pixel to compensate for their border | 211 // Lay out the views to overlap by 1 pixel to compensate for their border |
| 154 // spacing. Otherwise, "<a>link</a>," will render as "link ,". | 212 // spacing. Otherwise, "<a>link</a>," will render as "link ,". |
| 155 const int overlap = 1; | 213 const int overlap = 1; |
| 156 const gfx::Size view_size = view->GetPreferredSize(); | 214 const gfx::Size view_size = view->GetPreferredSize(); |
| 157 DCHECK_EQ(line_height, view_size.height() - 2 * overlap); | 215 DCHECK_EQ(line_height, view_size.height() - 2 * overlap); |
| 158 if (!dry_run) { | 216 if (!dry_run) { |
| 159 view->SetBoundsRect(gfx::Rect( | 217 view->SetBoundsRect(gfx::Rect( |
| 160 gfx::Point(GetInsets().left() + x - overlap, | 218 gfx::Point(GetInsets().left() + x - overlap, |
| 161 GetInsets().top() + line * line_height - overlap), | 219 GetInsets().top() + line * line_height - overlap), |
| 162 view_size)); | 220 view_size)); |
| 163 AddChildView(view.release()); | 221 AddChildView(view.release()); |
| 164 } | 222 } |
| 165 x += view_size.width() - 2 * overlap; | 223 x += view_size.width() - 2 * overlap; |
| 166 | 224 |
| 167 remaining_string = remaining_string.substr(chunk.size()); | 225 remaining_string = remaining_string.substr(chunk.size()); |
| 168 } | 226 } |
| 169 | 227 |
| 170 return (line + 1) * line_height + GetInsets().height(); | 228 return (line + 1) * line_height + GetInsets().height(); |
| 171 } | 229 } |
| 172 | 230 |
| 173 } // namespace views | 231 } // namespace views |
| OLD | NEW |