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/string_util.h"
10 #include "base/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 BoundedLabel to prevent outside code from
28 // calling a number of views::Label methods like SetFont() that would break
29 // BoundedLabel caching but can't be fixed because they're not virtual.
31 // TODO(dharcourt): Move the line limiting functionality to views::Label to make
34 class InnerBoundedLabel
: public views::Label
{
36 InnerBoundedLabel(const BoundedLabel
& owner
, size_t line_limit
);
37 virtual ~InnerBoundedLabel();
39 void SetLineLimit(size_t limit
);
40 size_t GetLinesForWidth(int width
);
41 size_t GetPreferredLines();
42 size_t GetActualLines();
44 void ChangeNativeTheme(const ui::NativeTheme
* theme
);
46 std::vector
<string16
> GetWrappedText(int width
, size_t line_limit
);
48 // Overridden from views::Label.
49 virtual gfx::Size
GetPreferredSize() OVERRIDE
;
50 virtual int GetHeightForWidth(int width
) OVERRIDE
;
53 // Overridden from views::Label.
54 virtual void OnBoundsChanged(const gfx::Rect
& previous_bounds
) OVERRIDE
;
55 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
;
58 gfx::Insets
GetOwnerInsets();
59 size_t GetPreferredLinesForWidth(int width
);
60 gfx::Size
GetSizeForWidth(int width
);
64 size_t GetCachedLinesForWidth(int width
);
65 void SetCachedLinesForWidth(int width
, size_t lines
);
66 gfx::Size
GetCachedSizeForWidth(int width
);
67 void SetCachedSizeForWidth(int width
, gfx::Size size
);
69 const BoundedLabel
* owner_
; // Weak reference.
71 std::map
<int,size_t> lines_cache_
;
72 std::list
<int> lines_widths_
; // Most recently used in front.
73 std::map
<int,gfx::Size
> size_cache_
;
74 std::list
<int> size_widths_
; // Most recently used in front.
76 DISALLOW_COPY_AND_ASSIGN(InnerBoundedLabel
);
79 InnerBoundedLabel::InnerBoundedLabel(const BoundedLabel
& owner
,
82 SetAllowCharacterBreak(true);
83 SetElideBehavior(views::Label::ELIDE_AT_END
);
84 SetHorizontalAlignment(gfx::ALIGN_LEFT
);
85 set_collapse_when_hidden(true);
87 line_limit_
= line_limit
;
90 InnerBoundedLabel::~InnerBoundedLabel() {
93 void InnerBoundedLabel::SetLineLimit(size_t limit
) {
94 if (limit
!= line_limit_
) {
95 std::swap(limit
, line_limit_
);
96 if (GetPreferredLines() > line_limit_
|| GetPreferredLines() > limit
) {
98 PreferredSizeChanged();
103 size_t InnerBoundedLabel::GetLinesForWidth(int width
) {
104 return std::min(GetPreferredLinesForWidth(width
), line_limit_
);
107 size_t InnerBoundedLabel::GetPreferredLines() {
108 return GetPreferredLinesForWidth(width());
111 size_t InnerBoundedLabel::GetActualLines() {
112 return std::min(GetPreferredLinesForWidth(width()), line_limit_
);
115 void InnerBoundedLabel::ChangeNativeTheme(const ui::NativeTheme
* theme
) {
117 OnNativeThemeChanged(theme
);
120 std::vector
<string16
> InnerBoundedLabel::GetWrappedText(int width
,
122 // Short circuit simple case.
124 return std::vector
<string16
>();
126 // Restrict line_limit to ensure (line_limit + 1) * line_height <= INT_MAX.
127 int line_height
= std::max(font().GetHeight(), 2); // At least 2 pixels.
128 unsigned int max_limit
= std::numeric_limits
<int>::max() / line_height
- 1;
129 line_limit
= (line_limit
<= max_limit
) ? line_limit
: max_limit
;
131 // Split, using ui::IGNORE_LONG_WORDS instead of ui::WRAP_LONG_WORDS to
132 // avoid an infinite loop in ui::ElideRectangleText() for small widths.
133 std::vector
<string16
> lines
;
134 int height
= static_cast<int>((line_limit
+ 1) * line_height
);
135 ui::ElideRectangleText(text(), font(), width
, height
,
136 ui::IGNORE_LONG_WORDS
, &lines
);
138 // Elide if necessary.
139 if (lines
.size() > line_limit
) {
140 // Add an ellipsis to the last line. If this ellipsis makes the last line
141 // too wide, that line will be further elided by the ui::ElideText below,
142 // so for example "ABC" could become "ABC..." and then "AB...".
143 string16 last
= lines
[line_limit
- 1] + UTF8ToUTF16(ui::kEllipsis
);
144 if (font().GetStringWidth(last
) > width
)
145 last
= ui::ElideText(last
, font(), width
, ui::ELIDE_AT_END
);
146 lines
.resize(line_limit
- 1);
147 lines
.push_back(last
);
153 gfx::Size
InnerBoundedLabel::GetPreferredSize() {
154 return GetSizeForWidth(std::numeric_limits
<int>::max());
157 int InnerBoundedLabel::GetHeightForWidth(int width
) {
158 return GetSizeForWidth(width
).height();
161 void InnerBoundedLabel::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
163 views::Label::OnBoundsChanged(previous_bounds
);
166 void InnerBoundedLabel::OnPaint(gfx::Canvas
* canvas
) {
167 views::Label::OnPaintBackground(canvas
);
168 views::Label::OnPaintFocusBorder(canvas
);
169 views::Label::OnPaintBorder(canvas
);
170 int height
= GetSizeForWidth(width()).height() - GetOwnerInsets().height();
172 gfx::Rect bounds
= GetLocalBounds();
173 bounds
.Inset(GetOwnerInsets());
174 bounds
.set_y(bounds
.y() + (bounds
.height() - height
) / 2);
175 bounds
.set_height(height
);
176 std::vector
<string16
> text
= GetWrappedText(bounds
.width(), line_limit_
);
177 PaintText(canvas
, JoinString(text
, '\n'), bounds
, GetTextFlags());
181 gfx::Insets
InnerBoundedLabel::GetOwnerInsets() {
182 return owner_
->GetInsets();
185 size_t InnerBoundedLabel::GetPreferredLinesForWidth(int width
) {
186 size_t lines
= GetCachedLinesForWidth(width
);
187 if (lines
== std::numeric_limits
<size_t>::max()) {
188 int content_width
= std::max(width
- GetOwnerInsets().width(), 0);
189 lines
= GetWrappedText(content_width
, lines
).size();
190 SetCachedLinesForWidth(width
, lines
);
195 gfx::Size
InnerBoundedLabel::GetSizeForWidth(int width
) {
196 gfx::Size size
= GetCachedSizeForWidth(width
);
197 if (size
.height() == std::numeric_limits
<int>::max()) {
198 int text_width
= std::max(width
- GetOwnerInsets().width(), 0);
200 if (line_limit_
> 0) {
201 std::vector
<string16
> text
= GetWrappedText(text_width
, line_limit_
);
202 gfx::Canvas::SizeStringInt(JoinString(text
, '\n'), font(),
203 &text_width
, &text_height
, GetTextFlags());
205 size
.set_width(text_width
+ GetOwnerInsets().width());
206 size
.set_height(text_height
+ GetOwnerInsets().height());
207 SetCachedSizeForWidth(width
, size
);
212 int InnerBoundedLabel::GetTextFlags() {
213 int flags
= gfx::Canvas::MULTI_LINE
| gfx::Canvas::CHARACTER_BREAK
;
215 // We can't use subpixel rendering if the background is non-opaque.
216 if (SkColorGetA(background_color()) != 0xFF)
217 flags
|= gfx::Canvas::NO_SUBPIXEL_RENDERING
;
219 if (directionality_mode() ==
220 views::Label::AUTO_DETECT_DIRECTIONALITY
) {
221 base::i18n::TextDirection direction
=
222 base::i18n::GetFirstStrongCharacterDirection(text());
223 if (direction
== base::i18n::RIGHT_TO_LEFT
)
224 flags
|= gfx::Canvas::FORCE_RTL_DIRECTIONALITY
;
226 flags
|= gfx::Canvas::FORCE_LTR_DIRECTIONALITY
;
229 return flags
| gfx::Canvas::TEXT_ALIGN_LEFT
;
232 void InnerBoundedLabel::ClearCaches() {
233 lines_cache_
.clear();
234 lines_widths_
.clear();
236 size_widths_
.clear();
239 size_t InnerBoundedLabel::GetCachedLinesForWidth(int width
) {
240 size_t lines
= std::numeric_limits
<size_t>::max();
241 if (lines_cache_
.find(width
) != lines_cache_
.end()) {
242 lines
= lines_cache_
[width
];
243 lines_widths_
.remove(width
);
244 lines_widths_
.push_front(width
);
249 void InnerBoundedLabel::SetCachedLinesForWidth(int width
, size_t lines
) {
250 if (lines_cache_
.size() >= kPreferredLinesCacheSize
) {
251 lines_cache_
.erase(lines_widths_
.back());
252 lines_widths_
.pop_back();
254 lines_cache_
[width
] = lines
;
255 lines_widths_
.push_front(width
);
258 gfx::Size
InnerBoundedLabel::GetCachedSizeForWidth(int width
) {
259 gfx::Size
size(width
, std::numeric_limits
<int>::max());
260 if (size_cache_
.find(width
) != size_cache_
.end()) {
261 size
= size_cache_
[width
];
262 size_widths_
.remove(width
);
263 size_widths_
.push_front(width
);
268 void InnerBoundedLabel::SetCachedSizeForWidth(int width
, gfx::Size size
) {
269 if (size_cache_
.size() >= kPreferredLinesCacheSize
) {
270 size_cache_
.erase(size_widths_
.back());
271 size_widths_
.pop_back();
273 size_cache_
[width
] = size
;
274 size_widths_
.push_front(width
);
277 // BoundedLabel ///////////////////////////////////////////////////////////
279 BoundedLabel::BoundedLabel(const string16
& text
,
282 label_
.reset(new InnerBoundedLabel(*this, line_limit
));
283 label_
->SetFont(font
);
284 label_
->SetText(text
);
287 BoundedLabel::BoundedLabel(const string16
& text
, size_t line_limit
) {
288 label_
.reset(new InnerBoundedLabel(*this, line_limit
));
289 label_
->SetText(text
);
292 BoundedLabel::~BoundedLabel() {
295 void BoundedLabel::SetLineLimit(size_t lines
) {
296 label_
->SetLineLimit(lines
);
299 size_t BoundedLabel::GetLinesForWidth(int width
) {
300 return visible() ? label_
->GetLinesForWidth(width
) : 0;
303 size_t BoundedLabel::GetPreferredLines() {
304 return visible() ? label_
->GetPreferredLines() : 0;
307 size_t BoundedLabel::GetActualLines() {
308 return visible() ? label_
->GetActualLines() : 0;
311 void BoundedLabel::SetColors(SkColor textColor
, SkColor backgroundColor
) {
312 label_
->SetEnabledColor(textColor
);
313 label_
->SetBackgroundColor(backgroundColor
);
316 int BoundedLabel::GetBaseline() const {
317 return label_
->GetBaseline();
320 gfx::Size
BoundedLabel::GetPreferredSize() {
321 return visible() ? label_
->GetPreferredSize() : gfx::Size();
324 int BoundedLabel::GetHeightForWidth(int weight
) {
325 return visible() ? label_
->GetHeightForWidth(weight
) : 0;
328 void BoundedLabel::Paint(gfx::Canvas
* canvas
) {
330 label_
->Paint(canvas
);
333 bool BoundedLabel::HitTestRect(const gfx::Rect
& rect
) const {
334 return label_
->HitTestRect(rect
);
337 void BoundedLabel::GetAccessibleState(ui::AccessibleViewState
* state
) {
338 label_
->GetAccessibleState(state
);
341 void BoundedLabel::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
342 label_
->SetBoundsRect(bounds());
343 views::View::OnBoundsChanged(previous_bounds
);
346 void BoundedLabel::OnNativeThemeChanged(const ui::NativeTheme
* theme
) {
347 label_
->ChangeNativeTheme(theme
);
350 string16
BoundedLabel::GetWrappedTextForTest(int width
, size_t line_limit
) {
351 return JoinString(label_
->GetWrappedText(width
, line_limit
), '\n');
354 } // namespace message_center