Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / ui / views / controls / label.cc
1 // Copyright (c) 2012 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.
4
5 #include "ui/views/controls/label.h"
6
7 #include <algorithm>
8 #include <cmath>
9 #include <limits>
10 #include <vector>
11
12 #include "base/i18n/rtl.h"
13 #include "base/logging.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "ui/accessibility/ax_view_state.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/gfx/color_utils.h"
20 #include "ui/gfx/insets.h"
21 #include "ui/gfx/text_elider.h"
22 #include "ui/gfx/text_utils.h"
23 #include "ui/gfx/utf16_indexing.h"
24 #include "ui/native_theme/native_theme.h"
25 #include "ui/views/background.h"
26
27 namespace {
28
29 const int kCachedSizeLimit = 10;
30 const base::char16 kPasswordReplacementChar = '*';
31
32 }  // namespace
33
34 namespace views {
35
36 // static
37 const char Label::kViewClassName[] = "Label";
38 const int Label::kFocusBorderPadding = 1;
39
40 Label::Label() {
41   Init(base::string16(), gfx::FontList());
42 }
43
44 Label::Label(const base::string16& text) {
45   Init(text, gfx::FontList());
46 }
47
48 Label::Label(const base::string16& text, const gfx::FontList& font_list) {
49   Init(text, font_list);
50 }
51
52 Label::~Label() {
53 }
54
55 void Label::SetFontList(const gfx::FontList& font_list) {
56   font_list_ = font_list;
57   ResetCachedSize();
58   PreferredSizeChanged();
59   SchedulePaint();
60 }
61
62 void Label::SetText(const base::string16& text) {
63   if (text != text_)
64     SetTextInternal(text);
65 }
66
67 void Label::SetTextInternal(const base::string16& text) {
68   text_ = text;
69
70   if (obscured_) {
71     size_t obscured_text_length =
72         static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, text_.length()));
73     layout_text_.assign(obscured_text_length, kPasswordReplacementChar);
74   } else {
75     layout_text_ = text_;
76   }
77
78   ResetCachedSize();
79   PreferredSizeChanged();
80   SchedulePaint();
81 }
82
83 void Label::SetAutoColorReadabilityEnabled(bool enabled) {
84   auto_color_readability_ = enabled;
85   RecalculateColors();
86 }
87
88 void Label::SetEnabledColor(SkColor color) {
89   requested_enabled_color_ = color;
90   enabled_color_set_ = true;
91   RecalculateColors();
92 }
93
94 void Label::SetDisabledColor(SkColor color) {
95   requested_disabled_color_ = color;
96   disabled_color_set_ = true;
97   RecalculateColors();
98 }
99
100 void Label::SetBackgroundColor(SkColor color) {
101   background_color_ = color;
102   background_color_set_ = true;
103   RecalculateColors();
104 }
105
106 void Label::SetShadows(const gfx::ShadowValues& shadows) {
107   shadows_ = shadows;
108   text_size_valid_ = false;
109 }
110
111 void Label::SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled) {
112   subpixel_rendering_enabled_ = subpixel_rendering_enabled;
113 }
114
115 void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
116   // If the UI layout is right-to-left, flip the alignment direction.
117   if (base::i18n::IsRTL() &&
118       (alignment == gfx::ALIGN_LEFT || alignment == gfx::ALIGN_RIGHT)) {
119     alignment = (alignment == gfx::ALIGN_LEFT) ?
120         gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
121   }
122   if (horizontal_alignment_ != alignment) {
123     horizontal_alignment_ = alignment;
124     SchedulePaint();
125   }
126 }
127
128 gfx::HorizontalAlignment Label::GetHorizontalAlignment() const {
129   if (horizontal_alignment_ != gfx::ALIGN_TO_HEAD)
130     return horizontal_alignment_;
131
132   const base::i18n::TextDirection dir =
133       base::i18n::GetFirstStrongCharacterDirection(layout_text_);
134   return dir == base::i18n::RIGHT_TO_LEFT ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
135 }
136
137 void Label::SetLineHeight(int height) {
138   if (height != line_height_) {
139     line_height_ = height;
140     ResetCachedSize();
141     PreferredSizeChanged();
142     SchedulePaint();
143   }
144 }
145
146 void Label::SetMultiLine(bool multi_line) {
147   DCHECK(!multi_line || (elide_behavior_ == gfx::ELIDE_TAIL ||
148                          elide_behavior_ == gfx::NO_ELIDE));
149   if (multi_line != multi_line_) {
150     multi_line_ = multi_line;
151     ResetCachedSize();
152     PreferredSizeChanged();
153     SchedulePaint();
154   }
155 }
156
157 void Label::SetObscured(bool obscured) {
158   if (obscured != obscured_) {
159     obscured_ = obscured;
160     SetTextInternal(text_);
161   }
162 }
163
164 void Label::SetAllowCharacterBreak(bool allow_character_break) {
165   if (allow_character_break != allow_character_break_) {
166     allow_character_break_ = allow_character_break;
167     ResetCachedSize();
168     PreferredSizeChanged();
169     SchedulePaint();
170   }
171 }
172
173 void Label::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
174   DCHECK(!multi_line_ || (elide_behavior_ == gfx::ELIDE_TAIL ||
175                           elide_behavior_ == gfx::NO_ELIDE));
176   if (elide_behavior != elide_behavior_) {
177     elide_behavior_ = elide_behavior;
178     ResetCachedSize();
179     PreferredSizeChanged();
180     SchedulePaint();
181   }
182 }
183
184 void Label::SetTooltipText(const base::string16& tooltip_text) {
185   DCHECK(handles_tooltips_);
186   tooltip_text_ = tooltip_text;
187 }
188
189 void Label::SetHandlesTooltips(bool enabled) {
190   handles_tooltips_ = enabled;
191 }
192
193 void Label::SizeToFit(int max_width) {
194   DCHECK(multi_line_);
195
196   std::vector<base::string16> lines;
197   base::SplitString(layout_text_, '\n', &lines);
198
199   int label_width = 0;
200   for (std::vector<base::string16>::const_iterator iter = lines.begin();
201        iter != lines.end(); ++iter) {
202     label_width = std::max(label_width, gfx::GetStringWidth(*iter, font_list_));
203   }
204
205   label_width += GetInsets().width();
206
207   if (max_width > 0)
208     label_width = std::min(label_width, max_width);
209
210   SetBounds(x(), y(), label_width, 0);
211   SizeToPreferredSize();
212 }
213
214 const base::string16& Label::GetLayoutTextForTesting() const {
215   return layout_text_;
216 }
217
218 gfx::Insets Label::GetInsets() const {
219   gfx::Insets insets = View::GetInsets();
220   if (focusable()) {
221     insets += gfx::Insets(kFocusBorderPadding, kFocusBorderPadding,
222                           kFocusBorderPadding, kFocusBorderPadding);
223   }
224   return insets;
225 }
226
227 int Label::GetBaseline() const {
228   return GetInsets().top() + font_list_.GetBaseline();
229 }
230
231 gfx::Size Label::GetPreferredSize() const {
232   // Return a size of (0, 0) if the label is not visible and if the
233   // collapse_when_hidden_ flag is set.
234   // TODO(munjal): This logic probably belongs to the View class. But for now,
235   // put it here since putting it in View class means all inheriting classes
236   // need ot respect the collapse_when_hidden_ flag.
237   if (!visible() && collapse_when_hidden_)
238     return gfx::Size();
239
240   gfx::Size size(GetTextSize());
241   gfx::Insets insets = GetInsets();
242   size.Enlarge(insets.width(), insets.height());
243   return size;
244 }
245
246 gfx::Size Label::GetMinimumSize() const {
247   gfx::Size text_size(GetTextSize());
248   if ((!visible() && collapse_when_hidden_) || text_size.IsEmpty())
249     return gfx::Size();
250
251   gfx::Size size(gfx::GetStringWidth(base::string16(gfx::kEllipsisUTF16),
252                                      font_list_),
253                  font_list_.GetHeight());
254   size.SetToMin(text_size);  // The actual text may be shorter than an ellipsis.
255   gfx::Insets insets = GetInsets();
256   size.Enlarge(insets.width(), insets.height());
257   return size;
258 }
259
260 int Label::GetHeightForWidth(int w) const {
261   if (!multi_line_)
262     return View::GetHeightForWidth(w);
263
264   w = std::max(0, w - GetInsets().width());
265
266   for (size_t i = 0; i < cached_heights_.size(); ++i) {
267     const gfx::Size& s = cached_heights_[i];
268     if (s.width() == w)
269       return s.height() + GetInsets().height();
270   }
271
272   int cache_width = w;
273
274   int h = font_list_.GetHeight();
275   const int flags = ComputeDrawStringFlags();
276   gfx::Canvas::SizeStringInt(
277       layout_text_, font_list_, &w, &h, line_height_, flags);
278   cached_heights_[cached_heights_cursor_] = gfx::Size(cache_width, h);
279   cached_heights_cursor_ = (cached_heights_cursor_ + 1) % kCachedSizeLimit;
280   return h + GetInsets().height();
281 }
282
283 const char* Label::GetClassName() const {
284   return kViewClassName;
285 }
286
287 View* Label::GetTooltipHandlerForPoint(const gfx::Point& point) {
288   if (!handles_tooltips_ ||
289       (tooltip_text_.empty() && !ShouldShowDefaultTooltip()))
290     return NULL;
291
292   return HitTestPoint(point) ? this : NULL;
293 }
294
295 bool Label::CanProcessEventsWithinSubtree() const {
296   // Send events to the parent view for handling.
297   return false;
298 }
299
300 void Label::GetAccessibleState(ui::AXViewState* state) {
301   state->role = ui::AX_ROLE_STATIC_TEXT;
302   state->AddStateFlag(ui::AX_STATE_READ_ONLY);
303   state->name = layout_text_;
304 }
305
306 bool Label::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const {
307   if (!handles_tooltips_)
308     return false;
309
310   if (!tooltip_text_.empty()) {
311     tooltip->assign(tooltip_text_);
312     return true;
313   }
314
315   if (ShouldShowDefaultTooltip()) {
316     *tooltip = layout_text_;
317     return true;
318   }
319
320   return false;
321 }
322
323 void Label::PaintText(gfx::Canvas* canvas,
324                       const base::string16& text,
325                       const gfx::Rect& text_bounds,
326                       int flags) {
327   SkColor color = enabled() ? actual_enabled_color_ : actual_disabled_color_;
328   if (elide_behavior_ == gfx::FADE_TAIL) {
329     canvas->DrawFadedString(text, font_list_, color, text_bounds, flags);
330   } else {
331     canvas->DrawStringRectWithShadows(text, font_list_, color, text_bounds,
332                                       line_height_, flags, shadows_);
333   }
334
335   if (HasFocus()) {
336     gfx::Rect focus_bounds = text_bounds;
337     focus_bounds.Inset(-kFocusBorderPadding, -kFocusBorderPadding);
338     canvas->DrawFocusRect(focus_bounds);
339   }
340 }
341
342 gfx::Size Label::GetTextSize() const {
343   if (!text_size_valid_) {
344     // For single-line strings, we supply the largest possible width, because
345     // while adding NO_ELLIPSIS to the flags works on Windows for forcing
346     // SizeStringInt() to calculate the desired width, it doesn't seem to work
347     // on Linux.
348     int w = multi_line_ ?
349         GetAvailableRect().width() : std::numeric_limits<int>::max();
350     int h = font_list_.GetHeight();
351     // For single-line strings, ignore the available width and calculate how
352     // wide the text wants to be.
353     int flags = ComputeDrawStringFlags();
354     if (!multi_line_)
355       flags |= gfx::Canvas::NO_ELLIPSIS;
356     gfx::Canvas::SizeStringInt(
357         layout_text_, font_list_, &w, &h, line_height_, flags);
358     text_size_.SetSize(w, h);
359     const gfx::Insets shadow_margin = -gfx::ShadowValue::GetMargin(shadows_);
360     text_size_.Enlarge(shadow_margin.width(), shadow_margin.height());
361     text_size_valid_ = true;
362   }
363
364   return text_size_;
365 }
366
367 void Label::OnBoundsChanged(const gfx::Rect& previous_bounds) {
368   text_size_valid_ &= !multi_line_;
369 }
370
371 void Label::OnPaint(gfx::Canvas* canvas) {
372   OnPaintBackground(canvas);
373   // We skip painting the focus border because it is being handled seperately by
374   // some subclasses of Label. We do not want View's focus border painting to
375   // interfere with that.
376   OnPaintBorder(canvas);
377
378   base::string16 paint_text;
379   gfx::Rect text_bounds;
380   int flags = 0;
381   CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
382   PaintText(canvas, paint_text, text_bounds, flags);
383 }
384
385 void Label::OnNativeThemeChanged(const ui::NativeTheme* theme) {
386   UpdateColorsFromTheme(theme);
387 }
388
389 void Label::Init(const base::string16& text, const gfx::FontList& font_list) {
390   font_list_ = font_list;
391   enabled_color_set_ = disabled_color_set_ = background_color_set_ = false;
392   subpixel_rendering_enabled_ = true;
393   auto_color_readability_ = true;
394   UpdateColorsFromTheme(ui::NativeTheme::instance());
395   horizontal_alignment_ = gfx::ALIGN_CENTER;
396   line_height_ = 0;
397   multi_line_ = false;
398   obscured_ = false;
399   allow_character_break_ = false;
400   elide_behavior_ = gfx::ELIDE_TAIL;
401   handles_tooltips_ = true;
402   collapse_when_hidden_ = false;
403   cached_heights_.resize(kCachedSizeLimit);
404   ResetCachedSize();
405
406   SetText(text);
407 }
408
409 void Label::RecalculateColors() {
410   actual_enabled_color_ = auto_color_readability_ ?
411       color_utils::GetReadableColor(requested_enabled_color_,
412                                     background_color_) :
413       requested_enabled_color_;
414   actual_disabled_color_ = auto_color_readability_ ?
415       color_utils::GetReadableColor(requested_disabled_color_,
416                                     background_color_) :
417       requested_disabled_color_;
418 }
419
420 gfx::Rect Label::GetTextBounds() const {
421   gfx::Rect available(GetAvailableRect());
422   gfx::Size text_size(GetTextSize());
423   text_size.set_width(std::min(available.width(), text_size.width()));
424   gfx::Point origin(GetInsets().left(), GetInsets().top());
425   switch (GetHorizontalAlignment()) {
426     case gfx::ALIGN_LEFT:
427       break;
428     case gfx::ALIGN_CENTER:
429       // Put any extra margin pixel on the left to match the legacy behavior
430       // from the use of GetTextExtentPoint32() on Windows.
431       origin.Offset((available.width() + 1 - text_size.width()) / 2, 0);
432       break;
433     case gfx::ALIGN_RIGHT:
434       origin.set_x(available.right() - text_size.width());
435       break;
436     default:
437       NOTREACHED();
438       break;
439   }
440   if (!multi_line_)
441     text_size.set_height(available.height());
442   // Support vertical centering of multi-line labels: http://crbug.com/429595
443   origin.Offset(0, std::max(0, (available.height() - text_size.height())) / 2);
444   return gfx::Rect(origin, text_size);
445 }
446
447 int Label::ComputeDrawStringFlags() const {
448   int flags = 0;
449
450   // We can't use subpixel rendering if the background is non-opaque.
451   if (SkColorGetA(background_color_) != 0xFF || !subpixel_rendering_enabled_)
452     flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING;
453
454   base::i18n::TextDirection direction =
455       base::i18n::GetFirstStrongCharacterDirection(layout_text_);
456   if (direction == base::i18n::RIGHT_TO_LEFT)
457     flags |= gfx::Canvas::FORCE_RTL_DIRECTIONALITY;
458   else
459     flags |= gfx::Canvas::FORCE_LTR_DIRECTIONALITY;
460
461   switch (GetHorizontalAlignment()) {
462     case gfx::ALIGN_LEFT:
463       flags |= gfx::Canvas::TEXT_ALIGN_LEFT;
464       break;
465     case gfx::ALIGN_CENTER:
466       flags |= gfx::Canvas::TEXT_ALIGN_CENTER;
467       break;
468     case gfx::ALIGN_RIGHT:
469       flags |= gfx::Canvas::TEXT_ALIGN_RIGHT;
470       break;
471     default:
472       NOTREACHED();
473       break;
474   }
475
476   if (!multi_line_)
477     return flags;
478
479   flags |= gfx::Canvas::MULTI_LINE;
480 #if !defined(OS_WIN)
481     // Don't elide multiline labels on Linux.
482     // Todo(davemoore): Do we depend on eliding multiline text?
483     // Pango insists on limiting the number of lines to one if text is
484     // elided. You can get around this if you can pass a maximum height
485     // but we don't currently have that data when we call the pango code.
486     flags |= gfx::Canvas::NO_ELLIPSIS;
487 #endif
488   if (allow_character_break_)
489     flags |= gfx::Canvas::CHARACTER_BREAK;
490
491   return flags;
492 }
493
494 gfx::Rect Label::GetAvailableRect() const {
495   gfx::Rect bounds(size());
496   bounds.Inset(GetInsets());
497   return bounds;
498 }
499
500 void Label::CalculateDrawStringParams(base::string16* paint_text,
501                                       gfx::Rect* text_bounds,
502                                       int* flags) const {
503   DCHECK(paint_text && text_bounds && flags);
504
505   const bool forbid_ellipsis = elide_behavior_ == gfx::NO_ELIDE ||
506                                elide_behavior_ == gfx::FADE_TAIL;
507   if (multi_line_ || forbid_ellipsis) {
508     *paint_text = layout_text_;
509   } else {
510     *paint_text = gfx::ElideText(layout_text_, font_list_,
511                                  GetAvailableRect().width(), elide_behavior_);
512   }
513
514   *text_bounds = GetTextBounds();
515   *flags = ComputeDrawStringFlags();
516   // TODO(msw): Elide multi-line text with ElideRectangleText instead.
517   if (!multi_line_ || forbid_ellipsis)
518     *flags |= gfx::Canvas::NO_ELLIPSIS;
519 }
520
521 void Label::UpdateColorsFromTheme(const ui::NativeTheme* theme) {
522   if (!enabled_color_set_) {
523     requested_enabled_color_ = theme->GetSystemColor(
524         ui::NativeTheme::kColorId_LabelEnabledColor);
525   }
526   if (!disabled_color_set_) {
527     requested_disabled_color_ = theme->GetSystemColor(
528         ui::NativeTheme::kColorId_LabelDisabledColor);
529   }
530   if (!background_color_set_) {
531     background_color_ = theme->GetSystemColor(
532         ui::NativeTheme::kColorId_LabelBackgroundColor);
533   }
534   RecalculateColors();
535 }
536
537 void Label::ResetCachedSize() {
538   text_size_valid_ = false;
539   cached_heights_cursor_ = 0;
540   for (int i = 0; i < kCachedSizeLimit; ++i)
541     cached_heights_[i] = gfx::Size();
542 }
543
544 bool Label::ShouldShowDefaultTooltip() const {
545   const gfx::Size text_size = GetTextSize();
546   const gfx::Size size = GetContentsBounds().size();
547   return !obscured() && (text_size.width() > size.width() ||
548                          (multi_line_ && text_size.height() > size.height()));
549 }
550
551 }  // namespace views