Separate Simple Backend creation from initialization.
[chromium-blink-merge.git] / ui / message_center / views / bounded_label.cc
blob4018c0d8de12121704cd2f39d5d5f782a1f63460
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"
7 #include <limits>
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"
15 namespace {
17 const size_t kPreferredLinesCacheSize = 10;
18 const size_t kPreferredSizeCacheSize = 10;
20 } // namespace
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
32 // this unnecessary.
34 class InnerBoundedLabel : public views::Label {
35 public:
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;
52 protected:
53 // Overridden from views::Label.
54 virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE;
55 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
57 private:
58 gfx::Insets GetOwnerInsets();
59 size_t GetPreferredLinesForWidth(int width);
60 gfx::Size GetSizeForWidth(int width);
61 int GetTextFlags();
63 void ClearCaches();
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.
70 size_t line_limit_;
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,
80 size_t line_limit) {
81 SetMultiLine(true);
82 SetAllowCharacterBreak(true);
83 SetElideBehavior(views::Label::ELIDE_AT_END);
84 SetHorizontalAlignment(gfx::ALIGN_LEFT);
85 set_collapse_when_hidden(true);
86 owner_ = &owner;
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) {
97 ClearCaches();
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) {
116 ClearCaches();
117 OnNativeThemeChanged(theme);
120 std::vector<string16> InnerBoundedLabel::GetWrappedText(int width,
121 size_t line_limit) {
122 // Short circuit simple case.
123 if (line_limit == 0)
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);
150 return lines;
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) {
162 ClearCaches();
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();
171 if (height > 0) {
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);
192 return 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);
199 int text_height = 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);
209 return 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;
225 else
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();
235 size_cache_.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);
246 return lines;
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);
265 return size;
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,
280 gfx::Font font,
281 size_t line_limit) {
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) {
329 if (visible())
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