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
& new_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(
117 base::JoinString(wrapped
, base::ASCIIToUTF16("\n")),
118 font_list(), &text_width
, &text_height
, 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_
= base::JoinString(GetWrappedText(bounds
.width(), lines
),
181 base::ASCIIToUTF16("\n"));
182 wrapped_text_width_
= bounds
.width();
183 wrapped_text_lines_
= lines
;
185 bounds
.set_x(GetMirroredXForRect(bounds
));
186 canvas
->DrawStringRectWithFlags(
187 wrapped_text_
, font_list(), enabled_color(), bounds
, GetTextFlags());
191 void InnerBoundedLabel::SetText(const base::string16
& new_text
) {
192 if (text() == new_text
)
194 views::Label::SetText(new_text
);
198 int InnerBoundedLabel::GetTextFlags() {
199 int flags
= gfx::Canvas::MULTI_LINE
| gfx::Canvas::CHARACTER_BREAK
;
201 // We can't use subpixel rendering if the background is non-opaque.
202 if (SkColorGetA(background_color()) != 0xFF)
203 flags
|= gfx::Canvas::NO_SUBPIXEL_RENDERING
;
205 return flags
| gfx::Canvas::TEXT_ALIGN_TO_HEAD
;
208 void InnerBoundedLabel::ClearCaches() {
209 wrapped_text_width_
= 0;
210 wrapped_text_lines_
= 0;
211 lines_cache_
.clear();
212 lines_widths_
.clear();
214 size_widths_and_lines_
.clear();
217 int InnerBoundedLabel::GetCachedLines(int width
) {
218 int lines
= std::numeric_limits
<int>::max();
219 std::map
<int, int>::const_iterator found
;
220 if ((found
= lines_cache_
.find(width
)) != lines_cache_
.end()) {
221 lines
= found
->second
;
222 lines_widths_
.remove(width
);
223 lines_widths_
.push_front(width
);
228 void InnerBoundedLabel::SetCachedLines(int width
, int lines
) {
229 if (lines_cache_
.size() >= kPreferredLinesCacheSize
) {
230 lines_cache_
.erase(lines_widths_
.back());
231 lines_widths_
.pop_back();
233 lines_cache_
[width
] = lines
;
234 lines_widths_
.push_front(width
);
237 gfx::Size
InnerBoundedLabel::GetCachedSize(
238 const std::pair
<int, int>& width_and_lines
) {
239 gfx::Size
size(width_and_lines
.first
, std::numeric_limits
<int>::max());
240 std::map
<std::pair
<int, int>, gfx::Size
>::const_iterator found
;
241 if ((found
= size_cache_
.find(width_and_lines
)) != size_cache_
.end()) {
242 size
= found
->second
;
243 size_widths_and_lines_
.remove(width_and_lines
);
244 size_widths_and_lines_
.push_front(width_and_lines
);
249 void InnerBoundedLabel::SetCachedSize(std::pair
<int, int> width_and_lines
,
251 if (size_cache_
.size() >= kPreferredLinesCacheSize
) {
252 size_cache_
.erase(size_widths_and_lines_
.back());
253 size_widths_and_lines_
.pop_back();
255 size_cache_
[width_and_lines
] = size
;
256 size_widths_and_lines_
.push_front(width_and_lines
);
259 // BoundedLabel ///////////////////////////////////////////////////////////
261 BoundedLabel::BoundedLabel(const base::string16
& text
,
262 const gfx::FontList
& font_list
)
264 label_
.reset(new InnerBoundedLabel(*this));
265 label_
->SetFontList(font_list
);
266 label_
->SetText(text
);
269 BoundedLabel::BoundedLabel(const base::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 void BoundedLabel::SetText(const base::string16
& text
) {
292 label_
->SetText(text
);
295 int BoundedLabel::GetLineHeight() const {
296 return label_
->line_height();
299 int BoundedLabel::GetLineLimit() const {
303 int BoundedLabel::GetLinesForWidthAndLimit(int width
, int limit
) {
304 return visible() ? label_
->GetLinesForWidthAndLimit(width
, limit
) : 0;
307 gfx::Size
BoundedLabel::GetSizeForWidthAndLines(int width
, int lines
) {
309 label_
->GetSizeForWidthAndLines(width
, lines
) : gfx::Size();
312 int BoundedLabel::GetBaseline() const {
313 return label_
->GetBaseline();
316 gfx::Size
BoundedLabel::GetPreferredSize() const {
317 return visible() ? label_
->GetSizeForWidthAndLines(-1, -1) : gfx::Size();
320 int BoundedLabel::GetHeightForWidth(int width
) const {
322 label_
->GetSizeForWidthAndLines(width
, line_limit_
).height() : 0;
325 void BoundedLabel::OnPaint(gfx::Canvas
* canvas
) {
326 label_
->OnPaint(canvas
);
329 bool BoundedLabel::CanProcessEventsWithinSubtree() const {
330 return label_
->CanProcessEventsWithinSubtree();
333 void BoundedLabel::GetAccessibleState(ui::AXViewState
* state
) {
334 label_
->GetAccessibleState(state
);
337 void BoundedLabel::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
338 label_
->SetBoundsRect(bounds());
339 views::View::OnBoundsChanged(previous_bounds
);
342 void BoundedLabel::OnNativeThemeChanged(const ui::NativeTheme
* theme
) {
343 label_
->SetNativeTheme(theme
);
346 base::string16
BoundedLabel::GetWrappedTextForTest(int width
, int lines
) {
347 return base::JoinString(label_
->GetWrappedText(width
, lines
),
348 base::ASCIIToUTF16("\n"));
351 } // namespace message_center