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/gfx/canvas.h"
12 #include "ui/gfx/text_elider.h"
13 #include "ui/gfx/text_utils.h"
14 #include "ui/views/controls/label.h"
18 const size_t kPreferredLinesCacheSize
= 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 SetFontList() 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 ~InnerBoundedLabel() override
;
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
<base::string16
> GetWrappedText(int width
, int lines
);
46 // Overridden from views::Label.
47 void SetText(const base::string16
& text
) override
;
48 void OnPaint(gfx::Canvas
* canvas
) override
;
51 // Overridden from views::Label.
52 void OnBoundsChanged(const gfx::Rect
& previous_bounds
) override
;
58 int GetCachedLines(int width
);
59 void SetCachedLines(int width
, int lines
);
60 gfx::Size
GetCachedSize(const std::pair
<int, int>& width_and_lines
);
61 void SetCachedSize(std::pair
<int, int> width_and_lines
, gfx::Size size
);
63 const BoundedLabel
* owner_
; // Weak reference.
64 base::string16 wrapped_text_
;
65 int wrapped_text_width_
;
66 int wrapped_text_lines_
;
67 std::map
<int, int> lines_cache_
;
68 std::list
<int> lines_widths_
; // Most recently used in front.
69 std::map
<std::pair
<int, int>, gfx::Size
> size_cache_
;
70 std::list
<std::pair
<int, int> > size_widths_and_lines_
; // Recent in front.
72 DISALLOW_COPY_AND_ASSIGN(InnerBoundedLabel
);
75 InnerBoundedLabel::InnerBoundedLabel(const BoundedLabel
& owner
)
77 wrapped_text_width_(0),
78 wrapped_text_lines_(0) {
80 SetAllowCharacterBreak(true);
81 SetHorizontalAlignment(gfx::ALIGN_LEFT
);
82 set_collapse_when_hidden(true);
85 InnerBoundedLabel::~InnerBoundedLabel() {
88 void InnerBoundedLabel::SetNativeTheme(const ui::NativeTheme
* theme
) {
90 OnNativeThemeChanged(theme
);
93 int InnerBoundedLabel::GetLinesForWidthAndLimit(int width
, int limit
) {
94 if (width
== 0 || limit
== 0)
96 int lines
= GetCachedLines(width
);
97 if (lines
== std::numeric_limits
<int>::max()) {
98 int text_width
= std::max(width
- owner_
->GetInsets().width(), 0);
99 lines
= GetWrappedText(text_width
, lines
).size();
100 SetCachedLines(width
, lines
);
102 return (limit
< 0 || lines
<= limit
) ? lines
: limit
;
105 gfx::Size
InnerBoundedLabel::GetSizeForWidthAndLines(int width
, int lines
) {
106 if (width
== 0 || lines
== 0)
108 std::pair
<int, int> key(width
, lines
);
109 gfx::Size size
= GetCachedSize(key
);
110 if (size
.height() == std::numeric_limits
<int>::max()) {
111 gfx::Insets insets
= owner_
->GetInsets();
112 int text_width
= (width
< 0) ? std::numeric_limits
<int>::max() :
113 std::max(width
- insets
.width(), 0);
114 int text_height
= std::numeric_limits
<int>::max();
115 std::vector
<base::string16
> wrapped
= GetWrappedText(text_width
, lines
);
116 gfx::Canvas::SizeStringInt(JoinString(wrapped
, '\n'), font_list(),
117 &text_width
, &text_height
,
118 owner_
->GetLineHeight(),
120 size
.set_width(text_width
+ insets
.width());
121 size
.set_height(text_height
+ insets
.height());
122 SetCachedSize(key
, size
);
127 std::vector
<base::string16
> InnerBoundedLabel::GetWrappedText(int width
,
129 // Short circuit simple case.
130 if (width
== 0 || lines
== 0)
131 return std::vector
<base::string16
>();
133 // Restrict line limit to ensure (lines + 1) * line_height <= INT_MAX and
134 // use it to calculate a reasonable text height.
135 int height
= std::numeric_limits
<int>::max();
137 int line_height
= std::max(font_list().GetHeight(),
138 2); // At least 2 pixels.
139 int max_lines
= std::numeric_limits
<int>::max() / line_height
- 1;
140 lines
= std::min(lines
, max_lines
);
141 height
= (lines
+ 1) * line_height
;
144 // Wrap, using INT_MAX for -1 widths that indicate no wrapping.
145 std::vector
<base::string16
> wrapped
;
146 gfx::ElideRectangleText(text(), font_list(),
147 (width
< 0) ? std::numeric_limits
<int>::max() : width
,
148 height
, gfx::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 gfx::ElideText below,
154 // so for example "ABC" could become "ABC..." and then "AB...".
155 base::string16 last
=
156 wrapped
[lines
- 1] + base::UTF8ToUTF16(gfx::kEllipsis
);
157 if (width
> 0 && gfx::GetStringWidth(last
, font_list()) > width
)
158 last
= gfx::ElideText(last
, font_list(), width
, gfx::ELIDE_TAIL
);
159 wrapped
.resize(lines
- 1);
160 wrapped
.push_back(last
);
166 void InnerBoundedLabel::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
168 views::Label::OnBoundsChanged(previous_bounds
);
171 void InnerBoundedLabel::OnPaint(gfx::Canvas
* canvas
) {
172 views::Label::OnPaintBackground(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 canvas
->DrawStringRectWithFlags(
186 wrapped_text_
, font_list(), enabled_color(), bounds
, GetTextFlags());
190 void InnerBoundedLabel::SetText(const base::string16
& text
) {
191 views::Label::SetText(text
);
195 int InnerBoundedLabel::GetTextFlags() {
196 int flags
= gfx::Canvas::MULTI_LINE
| gfx::Canvas::CHARACTER_BREAK
;
198 // We can't use subpixel rendering if the background is non-opaque.
199 if (SkColorGetA(background_color()) != 0xFF)
200 flags
|= gfx::Canvas::NO_SUBPIXEL_RENDERING
;
202 return flags
| gfx::Canvas::TEXT_ALIGN_TO_HEAD
;
205 void InnerBoundedLabel::ClearCaches() {
206 wrapped_text_width_
= 0;
207 wrapped_text_lines_
= 0;
208 lines_cache_
.clear();
209 lines_widths_
.clear();
211 size_widths_and_lines_
.clear();
214 int InnerBoundedLabel::GetCachedLines(int width
) {
215 int lines
= std::numeric_limits
<int>::max();
216 std::map
<int, int>::const_iterator found
;
217 if ((found
= lines_cache_
.find(width
)) != lines_cache_
.end()) {
218 lines
= found
->second
;
219 lines_widths_
.remove(width
);
220 lines_widths_
.push_front(width
);
225 void InnerBoundedLabel::SetCachedLines(int width
, int lines
) {
226 if (lines_cache_
.size() >= kPreferredLinesCacheSize
) {
227 lines_cache_
.erase(lines_widths_
.back());
228 lines_widths_
.pop_back();
230 lines_cache_
[width
] = lines
;
231 lines_widths_
.push_front(width
);
234 gfx::Size
InnerBoundedLabel::GetCachedSize(
235 const std::pair
<int, int>& width_and_lines
) {
236 gfx::Size
size(width_and_lines
.first
, std::numeric_limits
<int>::max());
237 std::map
<std::pair
<int, int>, gfx::Size
>::const_iterator found
;
238 if ((found
= size_cache_
.find(width_and_lines
)) != size_cache_
.end()) {
239 size
= found
->second
;
240 size_widths_and_lines_
.remove(width_and_lines
);
241 size_widths_and_lines_
.push_front(width_and_lines
);
246 void InnerBoundedLabel::SetCachedSize(std::pair
<int, int> width_and_lines
,
248 if (size_cache_
.size() >= kPreferredLinesCacheSize
) {
249 size_cache_
.erase(size_widths_and_lines_
.back());
250 size_widths_and_lines_
.pop_back();
252 size_cache_
[width_and_lines
] = size
;
253 size_widths_and_lines_
.push_front(width_and_lines
);
256 // BoundedLabel ///////////////////////////////////////////////////////////
258 BoundedLabel::BoundedLabel(const base::string16
& text
,
259 const gfx::FontList
& font_list
)
261 label_
.reset(new InnerBoundedLabel(*this));
262 label_
->SetFontList(font_list
);
263 label_
->SetText(text
);
266 BoundedLabel::BoundedLabel(const base::string16
& text
)
268 label_
.reset(new InnerBoundedLabel(*this));
269 label_
->SetText(text
);
272 BoundedLabel::~BoundedLabel() {
275 void BoundedLabel::SetColors(SkColor textColor
, SkColor backgroundColor
) {
276 label_
->SetEnabledColor(textColor
);
277 label_
->SetBackgroundColor(backgroundColor
);
280 void BoundedLabel::SetLineHeight(int height
) {
281 label_
->SetLineHeight(height
);
284 void BoundedLabel::SetLineLimit(int lines
) {
285 line_limit_
= std::max(lines
, -1);
288 void BoundedLabel::SetText(const base::string16
& text
) {
289 label_
->SetText(text
);
292 int BoundedLabel::GetLineHeight() const {
293 return label_
->line_height();
296 int BoundedLabel::GetLineLimit() const {
300 int BoundedLabel::GetLinesForWidthAndLimit(int width
, int limit
) {
301 return visible() ? label_
->GetLinesForWidthAndLimit(width
, limit
) : 0;
304 gfx::Size
BoundedLabel::GetSizeForWidthAndLines(int width
, int lines
) {
306 label_
->GetSizeForWidthAndLines(width
, lines
) : gfx::Size();
309 int BoundedLabel::GetBaseline() const {
310 return label_
->GetBaseline();
313 gfx::Size
BoundedLabel::GetPreferredSize() const {
314 return visible() ? label_
->GetSizeForWidthAndLines(-1, -1) : gfx::Size();
317 int BoundedLabel::GetHeightForWidth(int width
) const {
319 label_
->GetSizeForWidthAndLines(width
, line_limit_
).height() : 0;
322 void BoundedLabel::OnPaint(gfx::Canvas
* canvas
) {
323 label_
->OnPaint(canvas
);
326 bool BoundedLabel::CanProcessEventsWithinSubtree() const {
327 return label_
->CanProcessEventsWithinSubtree();
330 void BoundedLabel::GetAccessibleState(ui::AXViewState
* 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 base::string16
BoundedLabel::GetWrappedTextForTest(int width
, int lines
) {
344 return JoinString(label_
->GetWrappedText(width
, lines
), '\n');
347 } // namespace message_center