Home | History | Annotate | Download | only in button
      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/button/label_button.h"
      6 
      7 #include "base/logging.h"
      8 #include "grit/ui_resources.h"
      9 #include "ui/base/resource/resource_bundle.h"
     10 #include "ui/gfx/animation/throb_animation.h"
     11 #include "ui/gfx/canvas.h"
     12 #include "ui/gfx/font_list.h"
     13 #include "ui/gfx/sys_color_change_listener.h"
     14 #include "ui/native_theme/native_theme.h"
     15 #include "ui/views/background.h"
     16 #include "ui/views/controls/button/label_button_border.h"
     17 #include "ui/views/painter.h"
     18 #include "ui/views/window/dialog_delegate.h"
     19 
     20 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
     21 #include "ui/views/linux_ui/linux_ui.h"
     22 #endif
     23 
     24 namespace {
     25 
     26 // The spacing between the icon and text.
     27 const int kSpacing = 5;
     28 
     29 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
     30 // Default text and shadow colors for STYLE_BUTTON.
     31 const SkColor kStyleButtonTextColor = SK_ColorBLACK;
     32 const SkColor kStyleButtonShadowColor = SK_ColorWHITE;
     33 #endif
     34 
     35 }  // namespace
     36 
     37 namespace views {
     38 
     39 // static
     40 const int LabelButton::kHoverAnimationDurationMs = 170;
     41 
     42 // static
     43 const char LabelButton::kViewClassName[] = "LabelButton";
     44 
     45 LabelButton::LabelButton(ButtonListener* listener, const base::string16& text)
     46     : CustomButton(listener),
     47       image_(new ImageView()),
     48       label_(new Label()),
     49       button_state_images_(),
     50       button_state_colors_(),
     51       explicitly_set_colors_(),
     52       is_default_(false),
     53       style_(STYLE_TEXTBUTTON),
     54       border_is_themed_border_(true) {
     55   SetAnimationDuration(kHoverAnimationDurationMs);
     56   SetText(text);
     57   SetFontList(gfx::FontList());
     58 
     59   AddChildView(image_);
     60   image_->set_interactive(false);
     61 
     62   AddChildView(label_);
     63   label_->SetAutoColorReadabilityEnabled(false);
     64   label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
     65 
     66   // Initialize the colors, border, and layout.
     67   SetStyle(style_);
     68 
     69   SetAccessibleName(text);
     70 }
     71 
     72 LabelButton::~LabelButton() {}
     73 
     74 const gfx::ImageSkia& LabelButton::GetImage(ButtonState for_state) {
     75   if (for_state != STATE_NORMAL && button_state_images_[for_state].isNull())
     76     return button_state_images_[STATE_NORMAL];
     77   return button_state_images_[for_state];
     78 }
     79 
     80 void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
     81   button_state_images_[for_state] = image;
     82   UpdateImage();
     83 }
     84 
     85 const base::string16& LabelButton::GetText() const {
     86   return label_->text();
     87 }
     88 
     89 void LabelButton::SetText(const base::string16& text) {
     90   SetAccessibleName(text);
     91   label_->SetText(text);
     92 }
     93 
     94 void LabelButton::SetTextColor(ButtonState for_state, SkColor color) {
     95   button_state_colors_[for_state] = color;
     96   if (for_state == STATE_DISABLED)
     97     label_->SetDisabledColor(color);
     98   else if (for_state == state())
     99     label_->SetEnabledColor(color);
    100   explicitly_set_colors_[for_state] = true;
    101 }
    102 
    103 void LabelButton::SetTextShadows(const gfx::ShadowValues& shadows) {
    104   label_->set_shadows(shadows);
    105 }
    106 
    107 void LabelButton::SetTextSubpixelRenderingEnabled(bool enabled) {
    108   label_->set_subpixel_rendering_enabled(enabled);
    109 }
    110 
    111 bool LabelButton::GetTextMultiLine() const {
    112   return label_->is_multi_line();
    113 }
    114 
    115 void LabelButton::SetTextMultiLine(bool text_multi_line) {
    116   label_->SetMultiLine(text_multi_line);
    117 }
    118 
    119 const gfx::FontList& LabelButton::GetFontList() const {
    120   return label_->font_list();
    121 }
    122 
    123 void LabelButton::SetFontList(const gfx::FontList& font_list) {
    124   cached_normal_font_list_ = font_list;
    125   cached_bold_font_list_ = font_list.DeriveWithStyle(
    126       font_list.GetFontStyle() | gfx::Font::BOLD);
    127 
    128   // STYLE_BUTTON uses bold text to indicate default buttons.
    129   label_->SetFontList(
    130       style_ == STYLE_BUTTON && is_default_ ?
    131       cached_bold_font_list_ : cached_normal_font_list_);
    132 }
    133 
    134 void LabelButton::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
    135   label_->SetElideBehavior(elide_behavior);
    136 }
    137 
    138 gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const {
    139   return label_->GetHorizontalAlignment();
    140 }
    141 
    142 void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
    143   label_->SetHorizontalAlignment(alignment);
    144   InvalidateLayout();
    145 }
    146 
    147 void LabelButton::SetDirectionalityMode(gfx::DirectionalityMode mode) {
    148   label_->set_directionality_mode(mode);
    149 }
    150 
    151 void LabelButton::SetIsDefault(bool is_default) {
    152   if (is_default == is_default_)
    153     return;
    154   is_default_ = is_default;
    155   ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE);
    156   is_default_ ? AddAccelerator(accel) : RemoveAccelerator(accel);
    157 
    158   // STYLE_BUTTON uses bold text to indicate default buttons.
    159   if (style_ == STYLE_BUTTON) {
    160     label_->SetFontList(
    161         is_default ? cached_bold_font_list_ : cached_normal_font_list_);
    162   }
    163 }
    164 
    165 void LabelButton::SetStyle(ButtonStyle style) {
    166   style_ = style;
    167   // Inset the button focus rect from the actual border; roughly match Windows.
    168   if (style == STYLE_BUTTON) {
    169     SetFocusPainter(scoped_ptr<Painter>());
    170   } else {
    171     SetFocusPainter(Painter::CreateDashedFocusPainterWithInsets(
    172                         gfx::Insets(3, 3, 3, 3)));
    173   }
    174   if (style == STYLE_BUTTON) {
    175     label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
    176     SetFocusable(true);
    177   }
    178   if (style == STYLE_BUTTON)
    179     set_min_size(gfx::Size(70, 33));
    180 
    181   OnNativeThemeChanged(GetNativeTheme());
    182 }
    183 
    184 void LabelButton::SetFocusPainter(scoped_ptr<Painter> focus_painter) {
    185   focus_painter_ = focus_painter.Pass();
    186 }
    187 
    188 gfx::Size LabelButton::GetPreferredSize() const {
    189   // Use a temporary label copy for sizing to avoid calculation side-effects.
    190   Label label(GetText(), cached_normal_font_list_);
    191   label.set_shadows(label_->shadows());
    192   label.SetMultiLine(GetTextMultiLine());
    193 
    194   if (style() == STYLE_BUTTON) {
    195     // Some text appears wider when rendered normally than when rendered bold.
    196     // Accommodate the widest, as buttons may show bold and shouldn't resize.
    197     const int current_width = label.GetPreferredSize().width();
    198     label.SetFontList(cached_bold_font_list_);
    199     if (label.GetPreferredSize().width() < current_width)
    200       label.SetFontList(cached_normal_font_list_);
    201   }
    202 
    203   // Resize multi-line labels given the current limited available width.
    204   const gfx::Size image_size(image_->GetPreferredSize());
    205   const int image_width = image_size.width();
    206   if (GetTextMultiLine() && (width() > image_width + kSpacing))
    207     label.SizeToFit(width() - image_width - (image_width > 0 ? kSpacing : 0));
    208 
    209   // Calculate the required size.
    210   gfx::Size size(label.GetPreferredSize());
    211   if (image_width > 0 && size.width() > 0)
    212     size.Enlarge(kSpacing, 0);
    213   size.SetToMax(gfx::Size(0, image_size.height()));
    214   const gfx::Insets insets(GetInsets());
    215   size.Enlarge(image_size.width() + insets.width(), insets.height());
    216 
    217   // Make the size at least as large as the minimum size needed by the border.
    218   size.SetToMax(border() ? border()->GetMinimumSize() : gfx::Size());
    219 
    220   // Increase the minimum size monotonically with the preferred size.
    221   size.SetToMax(min_size_);
    222   min_size_ = size;
    223 
    224   // Return the largest known size clamped to the maximum size (if valid).
    225   if (max_size_.width() > 0)
    226     size.set_width(std::min(max_size_.width(), size.width()));
    227   if (max_size_.height() > 0)
    228     size.set_height(std::min(max_size_.height(), size.height()));
    229   return size;
    230 }
    231 
    232 void LabelButton::Layout() {
    233   gfx::HorizontalAlignment adjusted_alignment = GetHorizontalAlignment();
    234   if (base::i18n::IsRTL() && adjusted_alignment != gfx::ALIGN_CENTER)
    235     adjusted_alignment = (adjusted_alignment == gfx::ALIGN_LEFT) ?
    236         gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
    237 
    238   gfx::Rect child_area(GetChildAreaBounds());
    239   child_area.Inset(GetInsets());
    240 
    241   gfx::Size image_size(image_->GetPreferredSize());
    242   image_size.SetToMin(child_area.size());
    243 
    244   // The label takes any remaining width after sizing the image, unless both
    245   // views are centered. In that case, using the tighter preferred label width
    246   // avoids wasted space within the label that would look like awkward padding.
    247   // Labels can paint over the full button height, including the border height.
    248   gfx::Size label_size(child_area.width(), height());
    249   if (!image_size.IsEmpty() && !label_size.IsEmpty()) {
    250     label_size.set_width(
    251         std::max(child_area.width() - image_size.width() - kSpacing, 0));
    252     if (adjusted_alignment == gfx::ALIGN_CENTER) {
    253       // Ensure multi-line labels paired with images use their available width.
    254       if (GetTextMultiLine())
    255         label_->SizeToFit(label_size.width());
    256       label_size.set_width(
    257           std::min(label_size.width(), label_->GetPreferredSize().width()));
    258     }
    259   }
    260 
    261   gfx::Point image_origin(child_area.origin());
    262   image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
    263   if (adjusted_alignment == gfx::ALIGN_CENTER) {
    264     const int total_width = image_size.width() + label_size.width() +
    265         ((image_size.width() > 0 && label_size.width() > 0) ? kSpacing : 0);
    266     image_origin.Offset((child_area.width() - total_width) / 2, 0);
    267   } else if (adjusted_alignment == gfx::ALIGN_RIGHT) {
    268     image_origin.Offset(child_area.width() - image_size.width(), 0);
    269   }
    270 
    271   gfx::Point label_origin(child_area.x(), 0);
    272   if (!image_size.IsEmpty() && adjusted_alignment != gfx::ALIGN_RIGHT)
    273     label_origin.set_x(image_origin.x() + image_size.width() + kSpacing);
    274 
    275   image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
    276   label_->SetBoundsRect(gfx::Rect(label_origin, label_size));
    277 }
    278 
    279 const char* LabelButton::GetClassName() const {
    280   return kViewClassName;
    281 }
    282 
    283 scoped_ptr<LabelButtonBorder> LabelButton::CreateDefaultBorder() const {
    284   return scoped_ptr<LabelButtonBorder>(new LabelButtonBorder(style_));
    285 }
    286 
    287 void LabelButton::SetBorder(scoped_ptr<Border> border) {
    288   border_is_themed_border_ = false;
    289   View::SetBorder(border.Pass());
    290 }
    291 
    292 gfx::Rect LabelButton::GetChildAreaBounds() {
    293   return GetLocalBounds();
    294 }
    295 
    296 void LabelButton::OnPaint(gfx::Canvas* canvas) {
    297   View::OnPaint(canvas);
    298   Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
    299 }
    300 
    301 void LabelButton::OnFocus() {
    302   View::OnFocus();
    303   // Typically the border renders differently when focused.
    304   SchedulePaint();
    305 }
    306 
    307 void LabelButton::OnBlur() {
    308   View::OnBlur();
    309   // Typically the border renders differently when focused.
    310   SchedulePaint();
    311 }
    312 
    313 void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
    314   params->button.checked = false;
    315   params->button.indeterminate = false;
    316   params->button.is_default = is_default_;
    317   params->button.is_focused = HasFocus() && IsAccessibilityFocusable();
    318   params->button.has_border = false;
    319   params->button.classic_state = 0;
    320   params->button.background_color = label_->background_color();
    321 }
    322 
    323 void LabelButton::ResetColorsFromNativeTheme() {
    324   const ui::NativeTheme* theme = GetNativeTheme();
    325   SkColor colors[STATE_COUNT] = {
    326     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonEnabledColor),
    327     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
    328     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
    329     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonDisabledColor),
    330   };
    331 
    332   // Certain styles do not change text color when hovered or pressed.
    333   bool constant_text_color = false;
    334   // Use hardcoded colors for inverted color scheme support and STYLE_BUTTON.
    335   if (gfx::IsInvertedColorScheme()) {
    336     constant_text_color = true;
    337     colors[STATE_NORMAL] = SK_ColorWHITE;
    338     label_->SetBackgroundColor(SK_ColorBLACK);
    339     label_->set_background(Background::CreateSolidBackground(SK_ColorBLACK));
    340     label_->SetAutoColorReadabilityEnabled(true);
    341     label_->set_shadows(gfx::ShadowValues());
    342   } else if (style() == STYLE_BUTTON) {
    343     // TODO(erg): This is disabled on desktop linux because of the binary asset
    344     // confusion. These details should either be pushed into ui::NativeThemeWin
    345     // or should be obsoleted by rendering buttons with paint calls instead of
    346     // with static assets. http://crbug.com/350498
    347 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
    348     constant_text_color = true;
    349     colors[STATE_NORMAL] = kStyleButtonTextColor;
    350     label_->SetBackgroundColor(theme->GetSystemColor(
    351         ui::NativeTheme::kColorId_ButtonBackgroundColor));
    352     label_->SetAutoColorReadabilityEnabled(false);
    353     label_->set_shadows(gfx::ShadowValues(1,
    354         gfx::ShadowValue(gfx::Point(0, 1), 0, kStyleButtonShadowColor)));
    355 #endif
    356     label_->set_background(NULL);
    357   } else {
    358     label_->set_background(NULL);
    359   }
    360 
    361   if (constant_text_color)
    362     colors[STATE_HOVERED] = colors[STATE_PRESSED] = colors[STATE_NORMAL];
    363 
    364   for (size_t state = STATE_NORMAL; state < STATE_COUNT; ++state) {
    365     if (!explicitly_set_colors_[state]) {
    366       SetTextColor(static_cast<ButtonState>(state), colors[state]);
    367       explicitly_set_colors_[state] = false;
    368     }
    369   }
    370 }
    371 
    372 void LabelButton::UpdateImage() {
    373   image_->SetImage(GetImage(state()));
    374 }
    375 
    376 void LabelButton::UpdateThemedBorder() {
    377   // Don't override borders set by others.
    378   if (!border_is_themed_border_)
    379     return;
    380 
    381   scoped_ptr<LabelButtonBorder> label_button_border = CreateDefaultBorder();
    382 
    383 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
    384   views::LinuxUI* linux_ui = views::LinuxUI::instance();
    385   if (linux_ui) {
    386     SetBorder(linux_ui->CreateNativeBorder(
    387         this, label_button_border.Pass()));
    388   } else
    389 #endif
    390   {
    391     SetBorder(label_button_border.PassAs<Border>());
    392   }
    393 
    394   border_is_themed_border_ = true;
    395 }
    396 
    397 void LabelButton::StateChanged() {
    398   const gfx::Size previous_image_size(image_->GetPreferredSize());
    399   UpdateImage();
    400   const SkColor color = button_state_colors_[state()];
    401   if (state() != STATE_DISABLED && label_->enabled_color() != color)
    402     label_->SetEnabledColor(color);
    403   label_->SetEnabled(state() != STATE_DISABLED);
    404   if (image_->GetPreferredSize() != previous_image_size)
    405     Layout();
    406 }
    407 
    408 void LabelButton::ChildPreferredSizeChanged(View* child) {
    409   PreferredSizeChanged();
    410 }
    411 
    412 void LabelButton::OnNativeThemeChanged(const ui::NativeTheme* theme) {
    413   ResetColorsFromNativeTheme();
    414   UpdateThemedBorder();
    415   // Invalidate the layout to pickup the new insets from the border.
    416   InvalidateLayout();
    417 }
    418 
    419 ui::NativeTheme::Part LabelButton::GetThemePart() const {
    420   return ui::NativeTheme::kPushButton;
    421 }
    422 
    423 gfx::Rect LabelButton::GetThemePaintRect() const {
    424   return GetLocalBounds();
    425 }
    426 
    427 ui::NativeTheme::State LabelButton::GetThemeState(
    428     ui::NativeTheme::ExtraParams* params) const {
    429   GetExtraParams(params);
    430   switch (state()) {
    431     case STATE_NORMAL:   return ui::NativeTheme::kNormal;
    432     case STATE_HOVERED:  return ui::NativeTheme::kHovered;
    433     case STATE_PRESSED:  return ui::NativeTheme::kPressed;
    434     case STATE_DISABLED: return ui::NativeTheme::kDisabled;
    435     case STATE_COUNT:    NOTREACHED() << "Unknown state: " << state();
    436   }
    437   return ui::NativeTheme::kNormal;
    438 }
    439 
    440 const gfx::Animation* LabelButton::GetThemeAnimation() const {
    441   return hover_animation_.get();
    442 }
    443 
    444 ui::NativeTheme::State LabelButton::GetBackgroundThemeState(
    445     ui::NativeTheme::ExtraParams* params) const {
    446   GetExtraParams(params);
    447   return ui::NativeTheme::kNormal;
    448 }
    449 
    450 ui::NativeTheme::State LabelButton::GetForegroundThemeState(
    451     ui::NativeTheme::ExtraParams* params) const {
    452   GetExtraParams(params);
    453   return ui::NativeTheme::kHovered;
    454 }
    455 
    456 }  // namespace views
    457