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.
5 #include "ui/message_center/views/bounded_label.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "ui/base/text/text_elider.h"
12 #include "ui/gfx/canvas.h"
13 #include "ui/views/controls/label.h"
17 const size_t kPreferredLinesCacheSize
= 10;
18 const size_t kPreferredSizeCacheSize
= 10;
22 namespace message_center
{
24 // InnerBoundedLabel ///////////////////////////////////////////////////////////
26 // InnerBoundedLabel is a views::Label subclass that does all of the work for
27 // BoundedLabel. It is kept private to prevent outside code from calling a
28 // number of views::Label methods like setFont() that break BoundedLabel's
29 // caching but can't be overridden.
31 // TODO(dharcourt): Move the line limiting functionality to views::Label to make
34 class InnerBoundedLabel
: public views::Label
{
36 InnerBoundedLabel(const BoundedLabel
& owner
);
37 virtual ~InnerBoundedLabel();
39 void SetNativeTheme(const ui::NativeTheme
* theme
);
41 // Pass in a -1 width to use the preferred width, a -1 limit to skip limits.
42 int GetLinesForWidthAndLimit(int width
, int limit
);
43 gfx::Size
GetSizeForWidthAndLines(int width
, int lines
);
44 std::vector
<string16
> GetWrappedText(int width
, int lines
);
47 // Overridden from views::Label.
48 virtual void OnBoundsChanged(const gfx::Rect
& previous_bounds
) OVERRIDE
;
49 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
;
55 int GetCachedLines(int width
);
56 void SetCachedLines(int width
, int lines
);
57 gfx::Size
GetCachedSize(const std::pair
<int, int>& width_and_lines
);
58 void SetCachedSize(std::pair
<int, int> width_and_lines
, gfx::Size size
);
60 const BoundedLabel
* owner_
; // Weak reference.
61 string16 wrapped_text_
;
62 int wrapped_text_width_
;
63 int wrapped_text_lines_
;
64 std::map
<int, int> lines_cache_
;
65 std::list
<int> lines_widths_
; // Most recently used in front.
66 std::map
<std::pair
<int, int>, gfx::Size
> size_cache_
;
67 std::list
<std::pair
<int, int> > size_widths_and_lines_
; // Recent in front.
69 DISALLOW_COPY_AND_ASSIGN(InnerBoundedLabel
);
72 InnerBoundedLabel::InnerBoundedLabel(const BoundedLabel
& owner
)
74 wrapped_text_width_(0),
75 wrapped_text_lines_(0) {
77 SetAllowCharacterBreak(true);
78 SetHorizontalAlignment(gfx::ALIGN_LEFT
);
79 set_collapse_when_hidden(true);
82 InnerBoundedLabel::~InnerBoundedLabel() {
85 void InnerBoundedLabel::SetNativeTheme(const ui::NativeTheme
* theme
) {
87 OnNativeThemeChanged(theme
);
90 int InnerBoundedLabel::GetLinesForWidthAndLimit(int width
, int limit
) {
91 if (width
== 0 || limit
== 0)
93 int lines
= GetCachedLines(width
);
94 if (lines
== std::numeric_limits
<int>::max()) {
95 int text_width
= std::max(width
- owner_
->GetInsets().width(), 0);
96 lines
= GetWrappedText(text_width
, lines
).size();
97 SetCachedLines(width
, lines
);
99 return (limit
< 0 || lines
<= limit
) ? lines
: limit
;
102 gfx::Size
InnerBoundedLabel::GetSizeForWidthAndLines(int width
, int lines
) {
103 if (width
== 0 || lines
== 0)
105 std::pair
<int, int> key(width
, lines
);
106 gfx::Size size
= GetCachedSize(key
);
107 if (size
.height() == std::numeric_limits
<int>::max()) {
108 gfx::Insets insets
= owner_
->GetInsets();
109 int text_width
= (width
< 0) ? std::numeric_limits
<int>::max() :
110 std::max(width
- insets
.width(), 0);
111 int text_height
= std::numeric_limits
<int>::max();
112 std::vector
<string16
> wrapped
= GetWrappedText(text_width
, lines
);
113 gfx::Canvas::SizeStringInt(JoinString(wrapped
, '\n'), font(),
114 &text_width
, &text_height
,
115 owner_
->GetLineHeight(),
117 size
.set_width(text_width
+ insets
.width());
118 size
.set_height(text_height
+ insets
.height());
119 SetCachedSize(key
, size
);
124 std::vector
<string16
> InnerBoundedLabel::GetWrappedText(int width
, int lines
) {
125 // Short circuit simple case.
126 if (width
== 0 || lines
== 0)
127 return std::vector
<string16
>();
129 // Restrict line limit to ensure (lines + 1) * line_height <= INT_MAX and
130 // use it to calculate a reasonable text height.
131 int height
= std::numeric_limits
<int>::max();
133 int line_height
= std::max(font().GetHeight(), 2); // At least 2 pixels.
134 int max_lines
= std::numeric_limits
<int>::max() / line_height
- 1;
135 lines
= std::min(lines
, max_lines
);
136 height
= (lines
+ 1) * line_height
;
139 // Try to ensure that the width is no smaller than the width of the text's
140 // characters to avoid the http://crbug.com/237700 infinite loop.
141 // TODO(dharcourt): Remove when http://crbug.com/237700 is fixed.
142 width
= std::max(width
, 2 * font().GetStringWidth(UTF8ToUTF16("W")));
144 // Wrap, using INT_MAX for -1 widths that indicate no wrapping.
145 std::vector
<string16
> wrapped
;
146 ui::ElideRectangleText(text(), font(),
147 (width
< 0) ? std::numeric_limits
<int>::max() : width
,
148 height
, ui::WRAP_LONG_WORDS
, &wrapped
);
150 // Elide if necessary.
151 if (lines
> 0 && wrapped
.size() > static_cast<unsigned int>(lines
)) {
152 // Add an ellipsis to the last line. If this ellipsis makes the last line
153 // too wide, that line will be further elided by the ui::ElideText below,
154 // so for example "ABC" could become "ABC..." and then "AB...".
155 string16 last
= wrapped
[lines
- 1] + UTF8ToUTF16(ui::kEllipsis
);
156 if (width
> 0 && font().GetStringWidth(last
) > width
)
157 last
= ui::ElideText(last
, font(), width
, ui::ELIDE_AT_END
);
158 wrapped
.resize(lines
- 1);
159 wrapped
.push_back(last
);
165 void InnerBoundedLabel::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
167 views::Label::OnBoundsChanged(previous_bounds
);
170 void InnerBoundedLabel::OnPaint(gfx::Canvas
* canvas
) {
171 views::Label::OnPaintBackground(canvas
);
172 views::Label::OnPaintFocusBorder(canvas
);
173 views::Label::OnPaintBorder(canvas
);
174 int lines
= owner_
->GetLineLimit();
175 int height
= GetSizeForWidthAndLines(width(), lines
).height();
177 gfx::Rect
bounds(width(), height
);
178 bounds
.Inset(owner_
->GetInsets());
179 if (bounds
.width() != wrapped_text_width_
|| lines
!= wrapped_text_lines_
) {
180 wrapped_text_
= JoinString(GetWrappedText(bounds
.width(), lines
), '\n');
181 wrapped_text_width_
= bounds
.width();
182 wrapped_text_lines_
= lines
;
184 bounds
.set_x(GetMirroredXForRect(bounds
));
185 PaintText(canvas
, wrapped_text_
, bounds
, GetTextFlags());
189 int InnerBoundedLabel::GetTextFlags() {
190 int flags
= gfx::Canvas::MULTI_LINE
| gfx::Canvas::CHARACTER_BREAK
;
192 // We can't use subpixel rendering if the background is non-opaque.
193 if (SkColorGetA(background_color()) != 0xFF)
194 flags
|= gfx::Canvas::NO_SUBPIXEL_RENDERING
;
196 if (directionality_mode() ==
197 views::Label::AUTO_DETECT_DIRECTIONALITY
) {
198 base::i18n::TextDirection direction
=
199 base::i18n::GetFirstStrongCharacterDirection(text());
200 if (direction
== base::i18n::RIGHT_TO_LEFT
)
201 flags
|= gfx::Canvas::FORCE_RTL_DIRECTIONALITY
;
203 flags
|= gfx::Canvas::FORCE_LTR_DIRECTIONALITY
;
209 void InnerBoundedLabel::ClearCaches() {
210 wrapped_text_width_
= 0;
211 wrapped_text_lines_
= 0;
212 lines_cache_
.clear();
213 lines_widths_
.clear();
215 size_widths_and_lines_
.clear();
218 int InnerBoundedLabel::GetCachedLines(int width
) {
219 int lines
= std::numeric_limits
<int>::max();
220 std::map
<int, int>::const_iterator found
;
221 if ((found
= lines_cache_
.find(width
)) != lines_cache_
.end()) {
222 lines
= found
->second
;
223 lines_widths_
.remove(width
);
224 lines_widths_
.push_front(width
);
229 void InnerBoundedLabel::SetCachedLines(int width
, int lines
) {
230 if (lines_cache_
.size() >= kPreferredLinesCacheSize
) {
231 lines_cache_
.erase(lines_widths_
.back());
232 lines_widths_
.pop_back();
234 lines_cache_
[width
] = lines
;
235 lines_widths_
.push_front(width
);
238 gfx::Size
InnerBoundedLabel::GetCachedSize(
239 const std::pair
<int, int>& width_and_lines
) {
240 gfx::Size
size(width_and_lines
.first
, std::numeric_limits
<int>::max());
241 std::map
<std::pair
<int, int>, gfx::Size
>::const_iterator found
;
242 if ((found
= size_cache_
.find(width_and_lines
)) != size_cache_
.end()) {
243 size
= found
->second
;
244 size_widths_and_lines_
.remove(width_and_lines
);
245 size_widths_and_lines_
.push_front(width_and_lines
);
250 void InnerBoundedLabel::SetCachedSize(std::pair
<int, int> width_and_lines
,
252 if (size_cache_
.size() >= kPreferredLinesCacheSize
) {
253 size_cache_
.erase(size_widths_and_lines_
.back());
254 size_widths_and_lines_
.pop_back();
256 size_cache_
[width_and_lines
] = size
;
257 size_widths_and_lines_
.push_front(width_and_lines
);
260 // BoundedLabel ///////////////////////////////////////////////////////////
262 BoundedLabel::BoundedLabel(const string16
& text
, gfx::Font font
)
264 label_
.reset(new InnerBoundedLabel(*this));
265 label_
->SetFont(font
);
266 label_
->SetText(text
);
269 BoundedLabel::BoundedLabel(const string16
& text
)
271 label_
.reset(new InnerBoundedLabel(*this));
272 label_
->SetText(text
);
275 BoundedLabel::~BoundedLabel() {
278 void BoundedLabel::SetColors(SkColor textColor
, SkColor backgroundColor
) {
279 label_
->SetEnabledColor(textColor
);
280 label_
->SetBackgroundColor(backgroundColor
);
283 void BoundedLabel::SetLineHeight(int height
) {
284 label_
->SetLineHeight(height
);
287 void BoundedLabel::SetLineLimit(int lines
) {
288 line_limit_
= std::max(lines
, -1);
291 int BoundedLabel::GetLineHeight() const {
292 return label_
->line_height();
295 int BoundedLabel::GetLineLimit() const {
299 int BoundedLabel::GetLinesForWidthAndLimit(int width
, int limit
) {
300 return visible() ? label_
->GetLinesForWidthAndLimit(width
, limit
) : 0;
303 gfx::Size
BoundedLabel::GetSizeForWidthAndLines(int width
, int lines
) {
305 label_
->GetSizeForWidthAndLines(width
, lines
) : gfx::Size();
308 int BoundedLabel::GetBaseline() const {
309 return label_
->GetBaseline();
312 gfx::Size
BoundedLabel::GetPreferredSize() {
313 return visible() ? label_
->GetSizeForWidthAndLines(-1, -1) : gfx::Size();
316 int BoundedLabel::GetHeightForWidth(int width
) {
318 label_
->GetSizeForWidthAndLines(width
, line_limit_
).height() : 0;
321 void BoundedLabel::Paint(gfx::Canvas
* canvas
) {
323 label_
->Paint(canvas
);
326 bool BoundedLabel::HitTestRect(const gfx::Rect
& rect
) const {
327 return label_
->HitTestRect(rect
);
330 void BoundedLabel::GetAccessibleState(ui::AccessibleViewState
* state
) {
331 label_
->GetAccessibleState(state
);
334 void BoundedLabel::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
335 label_
->SetBoundsRect(bounds());
336 views::View::OnBoundsChanged(previous_bounds
);
339 void BoundedLabel::OnNativeThemeChanged(const ui::NativeTheme
* theme
) {
340 label_
->SetNativeTheme(theme
);
343 string16
BoundedLabel::GetWrappedTextForTest(int width
, int lines
) {
344 return JoinString(label_
->GetWrappedText(width
, lines
), '\n');
347 } // namespace message_center