| // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "ui/views/controls/label.h" | 
 |  | 
 | #include <algorithm> | 
 | #include <cmath> | 
 | #include <limits> | 
 | #include <vector> | 
 |  | 
 | #include "base/i18n/rtl.h" | 
 | #include "base/logging.h" | 
 | #include "base/strings/string_split.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "ui/accessibility/ax_view_state.h" | 
 | #include "ui/gfx/canvas.h" | 
 | #include "ui/gfx/color_utils.h" | 
 | #include "ui/gfx/insets.h" | 
 | #include "ui/gfx/text_elider.h" | 
 | #include "ui/gfx/text_utils.h" | 
 | #include "ui/gfx/utf16_indexing.h" | 
 | #include "ui/native_theme/native_theme.h" | 
 | #include "ui/views/background.h" | 
 |  | 
 | namespace { | 
 |  | 
 | const int kCachedSizeLimit = 10; | 
 | const base::char16 kPasswordReplacementChar = '*'; | 
 |  | 
 | }  // namespace | 
 |  | 
 | namespace views { | 
 |  | 
 | // static | 
 | const char Label::kViewClassName[] = "Label"; | 
 | const int Label::kFocusBorderPadding = 1; | 
 |  | 
 | Label::Label() { | 
 |   Init(base::string16(), gfx::FontList()); | 
 | } | 
 |  | 
 | Label::Label(const base::string16& text) { | 
 |   Init(text, gfx::FontList()); | 
 | } | 
 |  | 
 | Label::Label(const base::string16& text, const gfx::FontList& font_list) { | 
 |   Init(text, font_list); | 
 | } | 
 |  | 
 | Label::~Label() { | 
 | } | 
 |  | 
 | void Label::SetFontList(const gfx::FontList& font_list) { | 
 |   font_list_ = font_list; | 
 |   ResetCachedSize(); | 
 |   PreferredSizeChanged(); | 
 |   SchedulePaint(); | 
 | } | 
 |  | 
 | void Label::SetText(const base::string16& text) { | 
 |   if (text != text_) | 
 |     SetTextInternal(text); | 
 | } | 
 |  | 
 | void Label::SetTextInternal(const base::string16& text) { | 
 |   text_ = text; | 
 |  | 
 |   if (obscured_) { | 
 |     size_t obscured_text_length = | 
 |         static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, text_.length())); | 
 |     layout_text_.assign(obscured_text_length, kPasswordReplacementChar); | 
 |   } else { | 
 |     layout_text_ = text_; | 
 |   } | 
 |  | 
 |   ResetCachedSize(); | 
 |   PreferredSizeChanged(); | 
 |   SchedulePaint(); | 
 | } | 
 |  | 
 | void Label::SetAutoColorReadabilityEnabled(bool enabled) { | 
 |   auto_color_readability_ = enabled; | 
 |   RecalculateColors(); | 
 | } | 
 |  | 
 | void Label::SetEnabledColor(SkColor color) { | 
 |   requested_enabled_color_ = color; | 
 |   enabled_color_set_ = true; | 
 |   RecalculateColors(); | 
 | } | 
 |  | 
 | void Label::SetDisabledColor(SkColor color) { | 
 |   requested_disabled_color_ = color; | 
 |   disabled_color_set_ = true; | 
 |   RecalculateColors(); | 
 | } | 
 |  | 
 | void Label::SetBackgroundColor(SkColor color) { | 
 |   background_color_ = color; | 
 |   background_color_set_ = true; | 
 |   RecalculateColors(); | 
 | } | 
 |  | 
 | void Label::SetShadows(const gfx::ShadowValues& shadows) { | 
 |   shadows_ = shadows; | 
 |   text_size_valid_ = false; | 
 | } | 
 |  | 
 | void Label::SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled) { | 
 |   subpixel_rendering_enabled_ = subpixel_rendering_enabled; | 
 | } | 
 |  | 
 | void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) { | 
 |   // If the UI layout is right-to-left, flip the alignment direction. | 
 |   if (base::i18n::IsRTL() && | 
 |       (alignment == gfx::ALIGN_LEFT || alignment == gfx::ALIGN_RIGHT)) { | 
 |     alignment = (alignment == gfx::ALIGN_LEFT) ? | 
 |         gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT; | 
 |   } | 
 |   if (horizontal_alignment_ != alignment) { | 
 |     horizontal_alignment_ = alignment; | 
 |     SchedulePaint(); | 
 |   } | 
 | } | 
 |  | 
 | gfx::HorizontalAlignment Label::GetHorizontalAlignment() const { | 
 |   if (horizontal_alignment_ != gfx::ALIGN_TO_HEAD) | 
 |     return horizontal_alignment_; | 
 |  | 
 |   const base::i18n::TextDirection dir = | 
 |       base::i18n::GetFirstStrongCharacterDirection(layout_text_); | 
 |   return dir == base::i18n::RIGHT_TO_LEFT ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT; | 
 | } | 
 |  | 
 | void Label::SetLineHeight(int height) { | 
 |   if (height != line_height_) { | 
 |     line_height_ = height; | 
 |     ResetCachedSize(); | 
 |     PreferredSizeChanged(); | 
 |     SchedulePaint(); | 
 |   } | 
 | } | 
 |  | 
 | void Label::SetMultiLine(bool multi_line) { | 
 |   DCHECK(!multi_line || (elide_behavior_ == gfx::ELIDE_TAIL || | 
 |                          elide_behavior_ == gfx::NO_ELIDE)); | 
 |   if (multi_line != multi_line_) { | 
 |     multi_line_ = multi_line; | 
 |     ResetCachedSize(); | 
 |     PreferredSizeChanged(); | 
 |     SchedulePaint(); | 
 |   } | 
 | } | 
 |  | 
 | void Label::SetObscured(bool obscured) { | 
 |   if (obscured != obscured_) { | 
 |     obscured_ = obscured; | 
 |     SetTextInternal(text_); | 
 |   } | 
 | } | 
 |  | 
 | void Label::SetAllowCharacterBreak(bool allow_character_break) { | 
 |   if (allow_character_break != allow_character_break_) { | 
 |     allow_character_break_ = allow_character_break; | 
 |     ResetCachedSize(); | 
 |     PreferredSizeChanged(); | 
 |     SchedulePaint(); | 
 |   } | 
 | } | 
 |  | 
 | void Label::SetElideBehavior(gfx::ElideBehavior elide_behavior) { | 
 |   DCHECK(!multi_line_ || (elide_behavior_ == gfx::ELIDE_TAIL || | 
 |                           elide_behavior_ == gfx::NO_ELIDE)); | 
 |   if (elide_behavior != elide_behavior_) { | 
 |     elide_behavior_ = elide_behavior; | 
 |     ResetCachedSize(); | 
 |     PreferredSizeChanged(); | 
 |     SchedulePaint(); | 
 |   } | 
 | } | 
 |  | 
 | void Label::SetTooltipText(const base::string16& tooltip_text) { | 
 |   DCHECK(handles_tooltips_); | 
 |   tooltip_text_ = tooltip_text; | 
 | } | 
 |  | 
 | void Label::SetHandlesTooltips(bool enabled) { | 
 |   handles_tooltips_ = enabled; | 
 | } | 
 |  | 
 | void Label::SizeToFit(int max_width) { | 
 |   DCHECK(multi_line_); | 
 |  | 
 |   std::vector<base::string16> lines; | 
 |   base::SplitString(layout_text_, '\n', &lines); | 
 |  | 
 |   int label_width = 0; | 
 |   for (std::vector<base::string16>::const_iterator iter = lines.begin(); | 
 |        iter != lines.end(); ++iter) { | 
 |     label_width = std::max(label_width, gfx::GetStringWidth(*iter, font_list_)); | 
 |   } | 
 |  | 
 |   label_width += GetInsets().width(); | 
 |  | 
 |   if (max_width > 0) | 
 |     label_width = std::min(label_width, max_width); | 
 |  | 
 |   SetBounds(x(), y(), label_width, 0); | 
 |   SizeToPreferredSize(); | 
 | } | 
 |  | 
 | const base::string16& Label::GetLayoutTextForTesting() const { | 
 |   return layout_text_; | 
 | } | 
 |  | 
 | gfx::Insets Label::GetInsets() const { | 
 |   gfx::Insets insets = View::GetInsets(); | 
 |   if (focusable()) { | 
 |     insets += gfx::Insets(kFocusBorderPadding, kFocusBorderPadding, | 
 |                           kFocusBorderPadding, kFocusBorderPadding); | 
 |   } | 
 |   return insets; | 
 | } | 
 |  | 
 | int Label::GetBaseline() const { | 
 |   return GetInsets().top() + font_list_.GetBaseline(); | 
 | } | 
 |  | 
 | gfx::Size Label::GetPreferredSize() const { | 
 |   // Return a size of (0, 0) if the label is not visible and if the | 
 |   // collapse_when_hidden_ flag is set. | 
 |   // TODO(munjal): This logic probably belongs to the View class. But for now, | 
 |   // put it here since putting it in View class means all inheriting classes | 
 |   // need ot respect the collapse_when_hidden_ flag. | 
 |   if (!visible() && collapse_when_hidden_) | 
 |     return gfx::Size(); | 
 |  | 
 |   gfx::Size size(GetTextSize()); | 
 |   gfx::Insets insets = GetInsets(); | 
 |   size.Enlarge(insets.width(), insets.height()); | 
 |   return size; | 
 | } | 
 |  | 
 | gfx::Size Label::GetMinimumSize() const { | 
 |   gfx::Size text_size(GetTextSize()); | 
 |   if ((!visible() && collapse_when_hidden_) || text_size.IsEmpty()) | 
 |     return gfx::Size(); | 
 |  | 
 |   gfx::Size size(gfx::GetStringWidth(base::string16(gfx::kEllipsisUTF16), | 
 |                                      font_list_), | 
 |                  font_list_.GetHeight()); | 
 |   size.SetToMin(text_size);  // The actual text may be shorter than an ellipsis. | 
 |   gfx::Insets insets = GetInsets(); | 
 |   size.Enlarge(insets.width(), insets.height()); | 
 |   return size; | 
 | } | 
 |  | 
 | int Label::GetHeightForWidth(int w) const { | 
 |   if (!multi_line_) | 
 |     return View::GetHeightForWidth(w); | 
 |  | 
 |   w = std::max(0, w - GetInsets().width()); | 
 |  | 
 |   for (size_t i = 0; i < cached_heights_.size(); ++i) { | 
 |     const gfx::Size& s = cached_heights_[i]; | 
 |     if (s.width() == w) | 
 |       return s.height() + GetInsets().height(); | 
 |   } | 
 |  | 
 |   int cache_width = w; | 
 |  | 
 |   int h = font_list_.GetHeight(); | 
 |   const int flags = ComputeDrawStringFlags(); | 
 |   gfx::Canvas::SizeStringInt( | 
 |       layout_text_, font_list_, &w, &h, line_height_, flags); | 
 |   cached_heights_[cached_heights_cursor_] = gfx::Size(cache_width, h); | 
 |   cached_heights_cursor_ = (cached_heights_cursor_ + 1) % kCachedSizeLimit; | 
 |   return h + GetInsets().height(); | 
 | } | 
 |  | 
 | const char* Label::GetClassName() const { | 
 |   return kViewClassName; | 
 | } | 
 |  | 
 | View* Label::GetTooltipHandlerForPoint(const gfx::Point& point) { | 
 |   if (!handles_tooltips_ || | 
 |       (tooltip_text_.empty() && !ShouldShowDefaultTooltip())) | 
 |     return NULL; | 
 |  | 
 |   return HitTestPoint(point) ? this : NULL; | 
 | } | 
 |  | 
 | bool Label::CanProcessEventsWithinSubtree() const { | 
 |   // Send events to the parent view for handling. | 
 |   return false; | 
 | } | 
 |  | 
 | void Label::GetAccessibleState(ui::AXViewState* state) { | 
 |   state->role = ui::AX_ROLE_STATIC_TEXT; | 
 |   state->AddStateFlag(ui::AX_STATE_READ_ONLY); | 
 |   state->name = layout_text_; | 
 | } | 
 |  | 
 | bool Label::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const { | 
 |   if (!handles_tooltips_) | 
 |     return false; | 
 |  | 
 |   if (!tooltip_text_.empty()) { | 
 |     tooltip->assign(tooltip_text_); | 
 |     return true; | 
 |   } | 
 |  | 
 |   if (ShouldShowDefaultTooltip()) { | 
 |     *tooltip = layout_text_; | 
 |     return true; | 
 |   } | 
 |  | 
 |   return false; | 
 | } | 
 |  | 
 | void Label::PaintText(gfx::Canvas* canvas, | 
 |                       const base::string16& text, | 
 |                       const gfx::Rect& text_bounds, | 
 |                       int flags) { | 
 |   SkColor color = enabled() ? actual_enabled_color_ : actual_disabled_color_; | 
 |   if (elide_behavior_ == gfx::FADE_TAIL) { | 
 |     canvas->DrawFadedString(text, font_list_, color, text_bounds, flags); | 
 |   } else { | 
 |     canvas->DrawStringRectWithShadows(text, font_list_, color, text_bounds, | 
 |                                       line_height_, flags, shadows_); | 
 |   } | 
 |  | 
 |   if (HasFocus()) { | 
 |     gfx::Rect focus_bounds = text_bounds; | 
 |     focus_bounds.Inset(-kFocusBorderPadding, -kFocusBorderPadding); | 
 |     canvas->DrawFocusRect(focus_bounds); | 
 |   } | 
 | } | 
 |  | 
 | gfx::Size Label::GetTextSize() const { | 
 |   if (!text_size_valid_) { | 
 |     // For single-line strings, we supply the largest possible width, because | 
 |     // while adding NO_ELLIPSIS to the flags works on Windows for forcing | 
 |     // SizeStringInt() to calculate the desired width, it doesn't seem to work | 
 |     // on Linux. | 
 |     int w = multi_line_ ? | 
 |         GetAvailableRect().width() : std::numeric_limits<int>::max(); | 
 |     int h = font_list_.GetHeight(); | 
 |     // For single-line strings, ignore the available width and calculate how | 
 |     // wide the text wants to be. | 
 |     int flags = ComputeDrawStringFlags(); | 
 |     if (!multi_line_) | 
 |       flags |= gfx::Canvas::NO_ELLIPSIS; | 
 |     gfx::Canvas::SizeStringInt( | 
 |         layout_text_, font_list_, &w, &h, line_height_, flags); | 
 |     text_size_.SetSize(w, h); | 
 |     const gfx::Insets shadow_margin = -gfx::ShadowValue::GetMargin(shadows_); | 
 |     text_size_.Enlarge(shadow_margin.width(), shadow_margin.height()); | 
 |     text_size_valid_ = true; | 
 |   } | 
 |  | 
 |   return text_size_; | 
 | } | 
 |  | 
 | void Label::OnBoundsChanged(const gfx::Rect& previous_bounds) { | 
 |   text_size_valid_ &= !multi_line_; | 
 | } | 
 |  | 
 | void Label::OnPaint(gfx::Canvas* canvas) { | 
 |   OnPaintBackground(canvas); | 
 |   // We skip painting the focus border because it is being handled seperately by | 
 |   // some subclasses of Label. We do not want View's focus border painting to | 
 |   // interfere with that. | 
 |   OnPaintBorder(canvas); | 
 |  | 
 |   base::string16 paint_text; | 
 |   gfx::Rect text_bounds; | 
 |   int flags = 0; | 
 |   CalculateDrawStringParams(&paint_text, &text_bounds, &flags); | 
 |   PaintText(canvas, paint_text, text_bounds, flags); | 
 | } | 
 |  | 
 | void Label::OnNativeThemeChanged(const ui::NativeTheme* theme) { | 
 |   UpdateColorsFromTheme(theme); | 
 | } | 
 |  | 
 | void Label::Init(const base::string16& text, const gfx::FontList& font_list) { | 
 |   font_list_ = font_list; | 
 |   enabled_color_set_ = disabled_color_set_ = background_color_set_ = false; | 
 |   subpixel_rendering_enabled_ = true; | 
 |   auto_color_readability_ = true; | 
 |   UpdateColorsFromTheme(ui::NativeTheme::instance()); | 
 |   horizontal_alignment_ = gfx::ALIGN_CENTER; | 
 |   line_height_ = 0; | 
 |   multi_line_ = false; | 
 |   obscured_ = false; | 
 |   allow_character_break_ = false; | 
 |   elide_behavior_ = gfx::ELIDE_TAIL; | 
 |   handles_tooltips_ = true; | 
 |   collapse_when_hidden_ = false; | 
 |   cached_heights_.resize(kCachedSizeLimit); | 
 |   ResetCachedSize(); | 
 |  | 
 |   SetText(text); | 
 | } | 
 |  | 
 | void Label::RecalculateColors() { | 
 |   actual_enabled_color_ = auto_color_readability_ ? | 
 |       color_utils::GetReadableColor(requested_enabled_color_, | 
 |                                     background_color_) : | 
 |       requested_enabled_color_; | 
 |   actual_disabled_color_ = auto_color_readability_ ? | 
 |       color_utils::GetReadableColor(requested_disabled_color_, | 
 |                                     background_color_) : | 
 |       requested_disabled_color_; | 
 | } | 
 |  | 
 | gfx::Rect Label::GetTextBounds() const { | 
 |   gfx::Rect available(GetAvailableRect()); | 
 |   gfx::Size text_size(GetTextSize()); | 
 |   text_size.set_width(std::min(available.width(), text_size.width())); | 
 |   gfx::Point origin(GetInsets().left(), GetInsets().top()); | 
 |   switch (GetHorizontalAlignment()) { | 
 |     case gfx::ALIGN_LEFT: | 
 |       break; | 
 |     case gfx::ALIGN_CENTER: | 
 |       // Put any extra margin pixel on the left to match the legacy behavior | 
 |       // from the use of GetTextExtentPoint32() on Windows. | 
 |       origin.Offset((available.width() + 1 - text_size.width()) / 2, 0); | 
 |       break; | 
 |     case gfx::ALIGN_RIGHT: | 
 |       origin.set_x(available.right() - text_size.width()); | 
 |       break; | 
 |     default: | 
 |       NOTREACHED(); | 
 |       break; | 
 |   } | 
 |   text_size.set_height(available.height()); | 
 |   return gfx::Rect(origin, text_size); | 
 | } | 
 |  | 
 | int Label::ComputeDrawStringFlags() const { | 
 |   int flags = 0; | 
 |  | 
 |   // We can't use subpixel rendering if the background is non-opaque. | 
 |   if (SkColorGetA(background_color_) != 0xFF || !subpixel_rendering_enabled_) | 
 |     flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING; | 
 |  | 
 |   base::i18n::TextDirection direction = | 
 |       base::i18n::GetFirstStrongCharacterDirection(layout_text_); | 
 |   if (direction == base::i18n::RIGHT_TO_LEFT) | 
 |     flags |= gfx::Canvas::FORCE_RTL_DIRECTIONALITY; | 
 |   else | 
 |     flags |= gfx::Canvas::FORCE_LTR_DIRECTIONALITY; | 
 |  | 
 |   switch (GetHorizontalAlignment()) { | 
 |     case gfx::ALIGN_LEFT: | 
 |       flags |= gfx::Canvas::TEXT_ALIGN_LEFT; | 
 |       break; | 
 |     case gfx::ALIGN_CENTER: | 
 |       flags |= gfx::Canvas::TEXT_ALIGN_CENTER; | 
 |       break; | 
 |     case gfx::ALIGN_RIGHT: | 
 |       flags |= gfx::Canvas::TEXT_ALIGN_RIGHT; | 
 |       break; | 
 |     default: | 
 |       NOTREACHED(); | 
 |       break; | 
 |   } | 
 |  | 
 |   if (!multi_line_) | 
 |     return flags; | 
 |  | 
 |   flags |= gfx::Canvas::MULTI_LINE; | 
 | #if !defined(OS_WIN) | 
 |     // Don't elide multiline labels on Linux. | 
 |     // Todo(davemoore): Do we depend on eliding multiline text? | 
 |     // Pango insists on limiting the number of lines to one if text is | 
 |     // elided. You can get around this if you can pass a maximum height | 
 |     // but we don't currently have that data when we call the pango code. | 
 |     flags |= gfx::Canvas::NO_ELLIPSIS; | 
 | #endif | 
 |   if (allow_character_break_) | 
 |     flags |= gfx::Canvas::CHARACTER_BREAK; | 
 |  | 
 |   return flags; | 
 | } | 
 |  | 
 | gfx::Rect Label::GetAvailableRect() const { | 
 |   gfx::Rect bounds(size()); | 
 |   bounds.Inset(GetInsets()); | 
 |   return bounds; | 
 | } | 
 |  | 
 | void Label::CalculateDrawStringParams(base::string16* paint_text, | 
 |                                       gfx::Rect* text_bounds, | 
 |                                       int* flags) const { | 
 |   DCHECK(paint_text && text_bounds && flags); | 
 |  | 
 |   const bool forbid_ellipsis = elide_behavior_ == gfx::NO_ELIDE || | 
 |                                elide_behavior_ == gfx::FADE_TAIL; | 
 |   if (multi_line_ || forbid_ellipsis) { | 
 |     *paint_text = layout_text_; | 
 |   } else { | 
 |     *paint_text = gfx::ElideText(layout_text_, font_list_, | 
 |                                  GetAvailableRect().width(), elide_behavior_); | 
 |   } | 
 |  | 
 |   *text_bounds = GetTextBounds(); | 
 |   *flags = ComputeDrawStringFlags(); | 
 |   // TODO(msw): Elide multi-line text with ElideRectangleText instead. | 
 |   if (!multi_line_ || forbid_ellipsis) | 
 |     *flags |= gfx::Canvas::NO_ELLIPSIS; | 
 | } | 
 |  | 
 | void Label::UpdateColorsFromTheme(const ui::NativeTheme* theme) { | 
 |   if (!enabled_color_set_) { | 
 |     requested_enabled_color_ = theme->GetSystemColor( | 
 |         ui::NativeTheme::kColorId_LabelEnabledColor); | 
 |   } | 
 |   if (!disabled_color_set_) { | 
 |     requested_disabled_color_ = theme->GetSystemColor( | 
 |         ui::NativeTheme::kColorId_LabelDisabledColor); | 
 |   } | 
 |   if (!background_color_set_) { | 
 |     background_color_ = theme->GetSystemColor( | 
 |         ui::NativeTheme::kColorId_LabelBackgroundColor); | 
 |   } | 
 |   RecalculateColors(); | 
 | } | 
 |  | 
 | void Label::ResetCachedSize() { | 
 |   text_size_valid_ = false; | 
 |   cached_heights_cursor_ = 0; | 
 |   for (int i = 0; i < kCachedSizeLimit; ++i) | 
 |     cached_heights_[i] = gfx::Size(); | 
 | } | 
 |  | 
 | bool Label::ShouldShowDefaultTooltip() const { | 
 |   const gfx::Size text_size = GetTextSize(); | 
 |   const gfx::Size size = GetContentsBounds().size(); | 
 |   return !obscured() && (text_size.width() > size.width() || | 
 |                          (multi_line_ && text_size.height() > size.height())); | 
 | } | 
 |  | 
 | }  // namespace views |