Upstream version 5.34.92.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/base/accessibility/accessible_view_state.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/gfx/color_utils.h"
21 #include "ui/gfx/insets.h"
22 #include "ui/gfx/shadow_value.h"
23 #include "ui/gfx/text_elider.h"
24 #include "ui/gfx/text_utils.h"
25 #include "ui/native_theme/native_theme.h"
26 #include "ui/views/background.h"
27
28 namespace {
29
30 const int kCachedSizeLimit = 10;
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     return;
65   text_ = text;
66   ResetCachedSize();
67   PreferredSizeChanged();
68   SchedulePaint();
69 }
70
71 void Label::SetAutoColorReadabilityEnabled(bool enabled) {
72   auto_color_readability_ = enabled;
73   RecalculateColors();
74 }
75
76 void Label::SetEnabledColor(SkColor color) {
77   requested_enabled_color_ = color;
78   enabled_color_set_ = true;
79   RecalculateColors();
80 }
81
82 void Label::SetDisabledColor(SkColor color) {
83   requested_disabled_color_ = color;
84   disabled_color_set_ = true;
85   RecalculateColors();
86 }
87
88 void Label::SetBackgroundColor(SkColor color) {
89   background_color_ = color;
90   background_color_set_ = true;
91   RecalculateColors();
92 }
93
94 void Label::SetShadowColors(SkColor enabled_color, SkColor disabled_color) {
95   enabled_shadow_color_ = enabled_color;
96   disabled_shadow_color_ = disabled_color;
97   has_shadow_ = true;
98 }
99
100 void Label::SetShadowOffset(int x, int y) {
101   shadow_offset_.SetPoint(x, y);
102 }
103
104 void Label::ClearEmbellishing() {
105   has_shadow_ = false;
106 }
107
108 void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
109   // If the View's UI layout is right-to-left and directionality_mode_ is
110   // USE_UI_DIRECTIONALITY, we need to flip the alignment so that the alignment
111   // settings take into account the text directionality.
112   if (base::i18n::IsRTL() && (directionality_mode_ == USE_UI_DIRECTIONALITY) &&
113       (alignment != gfx::ALIGN_CENTER)) {
114     alignment = (alignment == gfx::ALIGN_LEFT) ?
115         gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
116   }
117   if (horizontal_alignment_ != alignment) {
118     horizontal_alignment_ = alignment;
119     SchedulePaint();
120   }
121 }
122
123 void Label::SetLineHeight(int height) {
124   if (height != line_height_) {
125     line_height_ = height;
126     ResetCachedSize();
127     PreferredSizeChanged();
128     SchedulePaint();
129   }
130 }
131
132 void Label::SetMultiLine(bool multi_line) {
133   DCHECK(!multi_line || elide_behavior_ != ELIDE_IN_MIDDLE);
134   if (multi_line != is_multi_line_) {
135     is_multi_line_ = multi_line;
136     ResetCachedSize();
137     PreferredSizeChanged();
138     SchedulePaint();
139   }
140 }
141
142 void Label::SetAllowCharacterBreak(bool allow_character_break) {
143   if (allow_character_break != allow_character_break_) {
144     allow_character_break_ = allow_character_break;
145     ResetCachedSize();
146     PreferredSizeChanged();
147     SchedulePaint();
148   }
149 }
150
151 void Label::SetElideBehavior(ElideBehavior elide_behavior) {
152   DCHECK(elide_behavior != ELIDE_IN_MIDDLE || !is_multi_line_);
153   if (elide_behavior != elide_behavior_) {
154     elide_behavior_ = elide_behavior;
155     ResetCachedSize();
156     PreferredSizeChanged();
157     SchedulePaint();
158   }
159 }
160
161 void Label::SetTooltipText(const base::string16& tooltip_text) {
162   tooltip_text_ = tooltip_text;
163 }
164
165 void Label::SizeToFit(int max_width) {
166   DCHECK(is_multi_line_);
167
168   std::vector<base::string16> lines;
169   base::SplitString(text_, '\n', &lines);
170
171   int label_width = 0;
172   for (std::vector<base::string16>::const_iterator iter = lines.begin();
173        iter != lines.end(); ++iter) {
174     label_width = std::max(label_width, gfx::GetStringWidth(*iter, font_list_));
175   }
176
177   label_width += GetInsets().width();
178
179   if (max_width > 0)
180     label_width = std::min(label_width, max_width);
181
182   SetBounds(x(), y(), label_width, 0);
183   SizeToPreferredSize();
184 }
185
186 gfx::Insets Label::GetInsets() const {
187   gfx::Insets insets = View::GetInsets();
188   if (focusable()) {
189     insets += gfx::Insets(kFocusBorderPadding, kFocusBorderPadding,
190                           kFocusBorderPadding, kFocusBorderPadding);
191   }
192   return insets;
193 }
194
195 int Label::GetBaseline() const {
196   return GetInsets().top() + font_list_.GetBaseline();
197 }
198
199 gfx::Size Label::GetPreferredSize() {
200   // Return a size of (0, 0) if the label is not visible and if the
201   // collapse_when_hidden_ flag is set.
202   // TODO(munjal): This logic probably belongs to the View class. But for now,
203   // put it here since putting it in View class means all inheriting classes
204   // need ot respect the collapse_when_hidden_ flag.
205   if (!visible() && collapse_when_hidden_)
206     return gfx::Size();
207
208   gfx::Size size(GetTextSize());
209   gfx::Insets insets = GetInsets();
210   size.Enlarge(insets.width(), insets.height());
211   return size;
212 }
213
214 gfx::Size Label::GetMinimumSize() {
215   gfx::Size text_size(GetTextSize());
216   if ((!visible() && collapse_when_hidden_) || text_size.IsEmpty())
217     return gfx::Size();
218
219   gfx::Size size(gfx::GetStringWidth(base::string16(gfx::kEllipsisUTF16),
220                                      font_list_),
221                  font_list_.GetHeight());
222   size.SetToMin(text_size);  // The actual text may be shorter than an ellipsis.
223   gfx::Insets insets = GetInsets();
224   size.Enlarge(insets.width(), insets.height());
225   return size;
226 }
227
228 int Label::GetHeightForWidth(int w) {
229   if (!is_multi_line_)
230     return View::GetHeightForWidth(w);
231
232   w = std::max(0, w - GetInsets().width());
233
234   for (size_t i = 0; i < cached_heights_.size(); ++i) {
235     const gfx::Size& s = cached_heights_[i];
236     if (s.width() == w)
237       return s.height() + GetInsets().height();
238   }
239
240   int cache_width = w;
241
242   int h = font_list_.GetHeight();
243   const int flags = ComputeDrawStringFlags();
244   gfx::Canvas::SizeStringInt(text_, font_list_, &w, &h, line_height_, flags);
245   cached_heights_[cached_heights_cursor_] = gfx::Size(cache_width, h);
246   cached_heights_cursor_ = (cached_heights_cursor_ + 1) % kCachedSizeLimit;
247   return h + GetInsets().height();
248 }
249
250 const char* Label::GetClassName() const {
251   return kViewClassName;
252 }
253
254 View* Label::GetTooltipHandlerForPoint(const gfx::Point& point) {
255   // Bail out if the label does not contain the point.
256   // Note that HitTestPoint() cannot be used here as it uses
257   // Label::HitTestRect() to determine if the point hits the label; and
258   // Label::HitTestRect() always fails. Instead, default HitTestRect()
259   // implementation should be used.
260   if (!View::HitTestRect(gfx::Rect(point, gfx::Size(1, 1))))
261     return NULL;
262
263   if (tooltip_text_.empty() && !ShouldShowDefaultTooltip())
264     return NULL;
265
266   return this;
267 }
268
269 bool Label::HitTestRect(const gfx::Rect& rect) const {
270   return false;
271 }
272
273 bool Label::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const {
274   DCHECK(tooltip);
275
276   // If a tooltip has been explicitly set, use it.
277   if (!tooltip_text_.empty()) {
278     tooltip->assign(tooltip_text_);
279     return true;
280   }
281
282   // Show the full text if the text does not fit.
283   if (ShouldShowDefaultTooltip()) {
284     *tooltip = text_;
285     return true;
286   }
287
288   return false;
289 }
290
291 void Label::GetAccessibleState(ui::AccessibleViewState* state) {
292   state->role = ui::AccessibilityTypes::ROLE_STATICTEXT;
293   state->state = ui::AccessibilityTypes::STATE_READONLY;
294   state->name = text_;
295 }
296
297 void Label::PaintText(gfx::Canvas* canvas,
298                       const base::string16& text,
299                       const gfx::Rect& text_bounds,
300                       int flags) {
301   gfx::ShadowValues shadows;
302   if (has_shadow_)
303     shadows.push_back(gfx::ShadowValue(shadow_offset_, 0,
304         enabled() ? enabled_shadow_color_ : disabled_shadow_color_));
305   canvas->DrawStringRectWithShadows(text, font_list_,
306       enabled() ? actual_enabled_color_ : actual_disabled_color_,
307       text_bounds, line_height_, flags, shadows);
308
309   if (HasFocus()) {
310     gfx::Rect focus_bounds = text_bounds;
311     focus_bounds.Inset(-kFocusBorderPadding, -kFocusBorderPadding);
312     canvas->DrawFocusRect(focus_bounds);
313   }
314 }
315
316 gfx::Size Label::GetTextSize() const {
317   if (!text_size_valid_) {
318     // For single-line strings, we supply the largest possible width, because
319     // while adding NO_ELLIPSIS to the flags works on Windows for forcing
320     // SizeStringInt() to calculate the desired width, it doesn't seem to work
321     // on Linux.
322     int w = is_multi_line_ ?
323         GetAvailableRect().width() : std::numeric_limits<int>::max();
324     int h = font_list_.GetHeight();
325     // For single-line strings, ignore the available width and calculate how
326     // wide the text wants to be.
327     int flags = ComputeDrawStringFlags();
328     if (!is_multi_line_)
329       flags |= gfx::Canvas::NO_ELLIPSIS;
330     gfx::Canvas::SizeStringInt(text_, font_list_, &w, &h, line_height_, flags);
331     text_size_.SetSize(w, h);
332     text_size_valid_ = true;
333   }
334
335   return text_size_;
336 }
337
338 void Label::OnBoundsChanged(const gfx::Rect& previous_bounds) {
339   text_size_valid_ &= !is_multi_line_;
340 }
341
342 void Label::OnPaint(gfx::Canvas* canvas) {
343   OnPaintBackground(canvas);
344   // We skip painting the focus border because it is being handled seperately by
345   // some subclasses of Label. We do not want View's focus border painting to
346   // interfere with that.
347   OnPaintBorder(canvas);
348
349   base::string16 paint_text;
350   gfx::Rect text_bounds;
351   int flags = 0;
352   CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
353   PaintText(canvas, paint_text, text_bounds, flags);
354 }
355
356 void Label::OnNativeThemeChanged(const ui::NativeTheme* theme) {
357   UpdateColorsFromTheme(theme);
358 }
359
360 void Label::Init(const base::string16& text, const gfx::FontList& font_list) {
361   font_list_ = font_list;
362   enabled_color_set_ = disabled_color_set_ = background_color_set_ = false;
363   auto_color_readability_ = true;
364   UpdateColorsFromTheme(ui::NativeTheme::instance());
365   horizontal_alignment_ = gfx::ALIGN_CENTER;
366   line_height_ = 0;
367   is_multi_line_ = false;
368   allow_character_break_ = false;
369   elide_behavior_ = ELIDE_AT_END;
370   collapse_when_hidden_ = false;
371   directionality_mode_ = USE_UI_DIRECTIONALITY;
372   enabled_shadow_color_ = 0;
373   disabled_shadow_color_ = 0;
374   shadow_offset_.SetPoint(1, 1);
375   has_shadow_ = false;
376   cached_heights_.resize(kCachedSizeLimit);
377   ResetCachedSize();
378
379   SetText(text);
380 }
381
382 void Label::RecalculateColors() {
383   actual_enabled_color_ = auto_color_readability_ ?
384       color_utils::GetReadableColor(requested_enabled_color_,
385                                     background_color_) :
386       requested_enabled_color_;
387   actual_disabled_color_ = auto_color_readability_ ?
388       color_utils::GetReadableColor(requested_disabled_color_,
389                                     background_color_) :
390       requested_disabled_color_;
391 }
392
393 gfx::Rect Label::GetTextBounds() const {
394   gfx::Rect available_rect(GetAvailableRect());
395   gfx::Size text_size(GetTextSize());
396   text_size.set_width(std::min(available_rect.width(), text_size.width()));
397
398   gfx::Insets insets = GetInsets();
399   gfx::Point text_origin(insets.left(), insets.top());
400   switch (horizontal_alignment_) {
401     case gfx::ALIGN_LEFT:
402       break;
403     case gfx::ALIGN_CENTER:
404       // We put any extra margin pixel on the left rather than the right.  We
405       // used to do this because measurement on Windows used
406       // GetTextExtentPoint32(), which could report a value one too large on the
407       // right; we now use DrawText(), and who knows if it can also do this.
408       text_origin.Offset((available_rect.width() + 1 - text_size.width()) / 2,
409                          0);
410       break;
411     case gfx::ALIGN_RIGHT:
412       text_origin.set_x(available_rect.right() - text_size.width());
413       break;
414     default:
415       NOTREACHED();
416       break;
417   }
418   text_origin.Offset(0,
419       std::max(0, (available_rect.height() - text_size.height())) / 2);
420   return gfx::Rect(text_origin, text_size);
421 }
422
423 int Label::ComputeDrawStringFlags() const {
424   int flags = 0;
425
426   // We can't use subpixel rendering if the background is non-opaque.
427   if (SkColorGetA(background_color_) != 0xFF)
428     flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING;
429
430   if (directionality_mode_ == AUTO_DETECT_DIRECTIONALITY) {
431     base::i18n::TextDirection direction =
432         base::i18n::GetFirstStrongCharacterDirection(text_);
433     if (direction == base::i18n::RIGHT_TO_LEFT)
434       flags |= gfx::Canvas::FORCE_RTL_DIRECTIONALITY;
435     else
436       flags |= gfx::Canvas::FORCE_LTR_DIRECTIONALITY;
437   }
438
439   switch (horizontal_alignment_) {
440     case gfx::ALIGN_LEFT:
441       flags |= gfx::Canvas::TEXT_ALIGN_LEFT;
442       break;
443     case gfx::ALIGN_CENTER:
444       flags |= gfx::Canvas::TEXT_ALIGN_CENTER;
445       break;
446     case gfx::ALIGN_RIGHT:
447       flags |= gfx::Canvas::TEXT_ALIGN_RIGHT;
448       break;
449   }
450
451   if (!is_multi_line_)
452     return flags;
453
454   flags |= gfx::Canvas::MULTI_LINE;
455 #if !defined(OS_WIN)
456     // Don't elide multiline labels on Linux.
457     // Todo(davemoore): Do we depend on eliding multiline text?
458     // Pango insists on limiting the number of lines to one if text is
459     // elided. You can get around this if you can pass a maximum height
460     // but we don't currently have that data when we call the pango code.
461     flags |= gfx::Canvas::NO_ELLIPSIS;
462 #endif
463   if (allow_character_break_)
464     flags |= gfx::Canvas::CHARACTER_BREAK;
465
466   return flags;
467 }
468
469 gfx::Rect Label::GetAvailableRect() const {
470   gfx::Rect bounds(size());
471   bounds.Inset(GetInsets());
472   return bounds;
473 }
474
475 void Label::CalculateDrawStringParams(base::string16* paint_text,
476                                       gfx::Rect* text_bounds,
477                                       int* flags) const {
478   DCHECK(paint_text && text_bounds && flags);
479
480   // TODO(msw): Use ElideRectangleText to support eliding multi-line text.  Once
481   // this is done, we can set NO_ELLIPSIS unconditionally at the bottom.
482   if (is_multi_line_ || (elide_behavior_ == NO_ELIDE)) {
483     *paint_text = text_;
484   } else if (elide_behavior_ == ELIDE_IN_MIDDLE) {
485     *paint_text = gfx::ElideText(text_, font_list_, GetAvailableRect().width(),
486                                  gfx::ELIDE_IN_MIDDLE);
487   } else if (elide_behavior_ == ELIDE_AT_END) {
488     *paint_text = gfx::ElideText(text_, font_list_, GetAvailableRect().width(),
489                                  gfx::ELIDE_AT_END);
490   } else {
491     DCHECK_EQ(ELIDE_AS_EMAIL, elide_behavior_);
492     *paint_text = gfx::ElideEmail(text_, font_list_,
493                                   GetAvailableRect().width());
494   }
495
496   *text_bounds = GetTextBounds();
497   *flags = ComputeDrawStringFlags();
498   if (!is_multi_line_ || (elide_behavior_ == NO_ELIDE))
499      *flags |= gfx::Canvas::NO_ELLIPSIS;
500 }
501
502 void Label::UpdateColorsFromTheme(const ui::NativeTheme* theme) {
503   if (!enabled_color_set_) {
504     requested_enabled_color_ = theme->GetSystemColor(
505         ui::NativeTheme::kColorId_LabelEnabledColor);
506   }
507   if (!disabled_color_set_) {
508     requested_disabled_color_ = theme->GetSystemColor(
509         ui::NativeTheme::kColorId_LabelDisabledColor);
510   }
511   if (!background_color_set_) {
512     background_color_ = theme->GetSystemColor(
513         ui::NativeTheme::kColorId_LabelBackgroundColor);
514   }
515   RecalculateColors();
516 }
517
518 void Label::ResetCachedSize() {
519   text_size_valid_ = false;
520   cached_heights_cursor_ = 0;
521   for (int i = 0; i < kCachedSizeLimit; ++i)
522     cached_heights_[i] = gfx::Size();
523 }
524
525 bool Label::ShouldShowDefaultTooltip() const {
526   return !is_multi_line_ &&
527       gfx::GetStringWidth(text_, font_list_) > GetAvailableRect().width();
528 }
529
530 }  // namespace views