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/text_button.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/logging.h"
     10 #include "grit/ui_resources.h"
     11 #include "ui/base/animation/throb_animation.h"
     12 #include "ui/base/resource/resource_bundle.h"
     13 #include "ui/gfx/canvas.h"
     14 #include "ui/gfx/image/image.h"
     15 #include "ui/views/controls/button/button.h"
     16 #include "ui/views/focus_border.h"
     17 #include "ui/views/widget/widget.h"
     18 
     19 #if defined(OS_WIN)
     20 #include "skia/ext/skia_utils_win.h"
     21 #include "ui/gfx/platform_font_win.h"
     22 #include "ui/native_theme/native_theme_win.h"
     23 #endif
     24 
     25 namespace views {
     26 
     27 namespace {
     28 
     29 // Default space between the icon and text.
     30 const int kDefaultIconTextSpacing = 5;
     31 
     32 // Preferred padding between text and edge.
     33 const int kPreferredPaddingHorizontal = 6;
     34 const int kPreferredPaddingVertical = 5;
     35 
     36 // Preferred padding between text and edge for NativeTheme border.
     37 const int kPreferredNativeThemePaddingHorizontal = 12;
     38 const int kPreferredNativeThemePaddingVertical = 5;
     39 
     40 // By default the focus rect is drawn at the border of the view.
     41 // For a button, we inset the focus rect by 3 pixels so that it
     42 // doesn't draw on top of the button's border. This roughly matches
     43 // how the Windows native focus rect for buttons looks. A subclass
     44 // that draws a button with different padding may need to
     45 // override OnPaintFocusBorder and do something different.
     46 const int kFocusRectInset = 3;
     47 
     48 // How long the hover fade animation should last.
     49 const int kHoverAnimationDurationMs = 170;
     50 
     51 #if defined(OS_WIN)
     52 // These sizes are from http://msdn.microsoft.com/en-us/library/aa511279.aspx
     53 const int kMinWidthDLUs = 50;
     54 const int kMinHeightDLUs = 14;
     55 #endif
     56 
     57 // The default hot and pushed button image IDs; normal has none by default.
     58 const int kHotImages[] = IMAGE_GRID(IDR_TEXTBUTTON_HOVER);
     59 const int kPushedImages[] = IMAGE_GRID(IDR_TEXTBUTTON_PRESSED);
     60 
     61 }  // namespace
     62 
     63 // static
     64 const char TextButtonBase::kViewClassName[] = "TextButtonBase";
     65 // static
     66 const char TextButton::kViewClassName[] = "TextButton";
     67 
     68 ////////////////////////////////////////////////////////////////////////////////
     69 //
     70 // TextButtonBorder
     71 //
     72 ////////////////////////////////////////////////////////////////////////////////
     73 
     74 TextButtonBorder::TextButtonBorder() {
     75 }
     76 
     77 TextButtonBorder::~TextButtonBorder() {
     78 }
     79 
     80 gfx::Insets TextButtonBorder::GetInsets() const {
     81   return insets_;
     82 }
     83 
     84 void TextButtonBorder::SetInsets(const gfx::Insets& insets) {
     85   insets_ = insets;
     86 }
     87 
     88 TextButtonBorder* TextButtonBorder::AsTextButtonBorder() {
     89   return this;
     90 }
     91 
     92 const TextButtonBorder* TextButtonBorder::AsTextButtonBorder() const {
     93   return this;
     94 }
     95 
     96 ////////////////////////////////////////////////////////////////////////////////
     97 //
     98 // TextButtonDefaultBorder - constructors, destructors, initialization
     99 //
    100 ////////////////////////////////////////////////////////////////////////////////
    101 
    102 TextButtonDefaultBorder::TextButtonDefaultBorder()
    103     : vertical_padding_(kPreferredPaddingVertical) {
    104   set_hot_painter(Painter::CreateImageGridPainter(kHotImages));
    105   set_pushed_painter(Painter::CreateImageGridPainter(kPushedImages));
    106   SetInsets(gfx::Insets(vertical_padding_, kPreferredPaddingHorizontal,
    107                         vertical_padding_, kPreferredPaddingHorizontal));
    108 }
    109 
    110 TextButtonDefaultBorder::~TextButtonDefaultBorder() {
    111 }
    112 
    113 ////////////////////////////////////////////////////////////////////////////////
    114 //
    115 // TextButtonDefaultBorder - painting
    116 //
    117 ////////////////////////////////////////////////////////////////////////////////
    118 void TextButtonDefaultBorder::Paint(const View& view, gfx::Canvas* canvas) {
    119   const TextButton* button = static_cast<const TextButton*>(&view);
    120   int state = button->state();
    121   bool animating = button->GetAnimation()->is_animating();
    122 
    123   Painter* painter = normal_painter_.get();
    124   // Use the hot painter when we're hovered. Also use the hot painter when we're
    125   // STATE_NORMAL and |animating| so that we show throb animations started from
    126   // CustomButton::StartThrobbing which should start throbbing the button
    127   // regardless of whether it is hovered.
    128   if (button->show_multiple_icon_states() &&
    129       ((state == TextButton::STATE_HOVERED) ||
    130        (state == TextButton::STATE_PRESSED) ||
    131        ((state == TextButton::STATE_NORMAL) && animating))) {
    132     painter = (state == TextButton::STATE_PRESSED) ?
    133         pushed_painter_.get() : hot_painter_.get();
    134   }
    135   if (painter) {
    136     if (animating) {
    137       // TODO(pkasting): Really this should crossfade between states so it could
    138       // handle the case of having a non-NULL |normal_painter_|.
    139       canvas->SaveLayerAlpha(static_cast<uint8>(
    140           button->GetAnimation()->CurrentValueBetween(0, 255)));
    141       painter->Paint(canvas, view.size());
    142       canvas->Restore();
    143     } else {
    144       painter->Paint(canvas, view.size());
    145     }
    146   }
    147 }
    148 
    149 ////////////////////////////////////////////////////////////////////////////////
    150 //
    151 // TextButtonNativeThemeBorder
    152 //
    153 ////////////////////////////////////////////////////////////////////////////////
    154 
    155 TextButtonNativeThemeBorder::TextButtonNativeThemeBorder(
    156     NativeThemeDelegate* delegate)
    157     : delegate_(delegate) {
    158   SetInsets(gfx::Insets(kPreferredNativeThemePaddingVertical,
    159                         kPreferredNativeThemePaddingHorizontal,
    160                         kPreferredNativeThemePaddingVertical,
    161                         kPreferredNativeThemePaddingHorizontal));
    162 }
    163 
    164 TextButtonNativeThemeBorder::~TextButtonNativeThemeBorder() {
    165 }
    166 
    167 void TextButtonNativeThemeBorder::Paint(const View& view, gfx::Canvas* canvas) {
    168   const ui::NativeTheme* theme = view.GetNativeTheme();
    169   const TextButtonBase* tb = static_cast<const TextButton*>(&view);
    170   ui::NativeTheme::Part part = delegate_->GetThemePart();
    171   gfx::Rect rect(delegate_->GetThemePaintRect());
    172 
    173   if (tb->show_multiple_icon_states() &&
    174       delegate_->GetThemeAnimation() != NULL &&
    175       delegate_->GetThemeAnimation()->is_animating()) {
    176     // Paint background state.
    177     ui::NativeTheme::ExtraParams prev_extra;
    178     ui::NativeTheme::State prev_state =
    179         delegate_->GetBackgroundThemeState(&prev_extra);
    180     theme->Paint(canvas->sk_canvas(), part, prev_state, rect, prev_extra);
    181 
    182     // Composite foreground state above it.
    183     ui::NativeTheme::ExtraParams extra;
    184     ui::NativeTheme::State state = delegate_->GetForegroundThemeState(&extra);
    185     int alpha = delegate_->GetThemeAnimation()->CurrentValueBetween(0, 255);
    186     canvas->SaveLayerAlpha(static_cast<uint8>(alpha));
    187     theme->Paint(canvas->sk_canvas(), part, state, rect, extra);
    188     canvas->Restore();
    189   } else {
    190     ui::NativeTheme::ExtraParams extra;
    191     ui::NativeTheme::State state = delegate_->GetThemeState(&extra);
    192     theme->Paint(canvas->sk_canvas(), part, state, rect, extra);
    193   }
    194 }
    195 
    196 ////////////////////////////////////////////////////////////////////////////////
    197 // TextButtonBase, public:
    198 
    199 TextButtonBase::TextButtonBase(ButtonListener* listener, const string16& text)
    200     : CustomButton(listener),
    201       alignment_(ALIGN_LEFT),
    202       font_(ResourceBundle::GetSharedInstance().GetFont(
    203           ResourceBundle::BaseFont)),
    204       has_text_shadow_(false),
    205       active_text_shadow_color_(0),
    206       inactive_text_shadow_color_(0),
    207       text_shadow_offset_(gfx::Point(1, 1)),
    208       min_width_(0),
    209       min_height_(0),
    210       max_width_(0),
    211       show_multiple_icon_states_(true),
    212       is_default_(false),
    213       multi_line_(false),
    214       use_enabled_color_from_theme_(true),
    215       use_disabled_color_from_theme_(true),
    216       use_highlight_color_from_theme_(true),
    217       use_hover_color_from_theme_(true) {
    218   SetText(text);
    219   // OnNativeThemeChanged sets the color member variables.
    220   TextButtonBase::OnNativeThemeChanged(GetNativeTheme());
    221   SetAnimationDuration(kHoverAnimationDurationMs);
    222 }
    223 
    224 TextButtonBase::~TextButtonBase() {
    225 }
    226 
    227 void TextButtonBase::SetIsDefault(bool is_default) {
    228   if (is_default == is_default_)
    229     return;
    230   is_default_ = is_default;
    231   if (is_default_)
    232     AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
    233   else
    234     RemoveAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
    235   SchedulePaint();
    236 }
    237 
    238 void TextButtonBase::SetText(const string16& text) {
    239   if (text == text_)
    240     return;
    241   text_ = text;
    242   SetAccessibleName(text);
    243   UpdateTextSize();
    244 }
    245 
    246 void TextButtonBase::SetFont(const gfx::Font& font) {
    247   font_ = font;
    248   UpdateTextSize();
    249 }
    250 
    251 void TextButtonBase::SetEnabledColor(SkColor color) {
    252   color_enabled_ = color;
    253   use_enabled_color_from_theme_ = false;
    254   UpdateColor();
    255 }
    256 
    257 void TextButtonBase::SetDisabledColor(SkColor color) {
    258   color_disabled_ = color;
    259   use_disabled_color_from_theme_ = false;
    260   UpdateColor();
    261 }
    262 
    263 void TextButtonBase::SetHighlightColor(SkColor color) {
    264   color_highlight_ = color;
    265   use_highlight_color_from_theme_ = false;
    266 }
    267 
    268 void TextButtonBase::SetHoverColor(SkColor color) {
    269   color_hover_ = color;
    270   use_hover_color_from_theme_ = false;
    271 }
    272 
    273 void TextButtonBase::SetTextShadowColors(SkColor active_color,
    274                                          SkColor inactive_color) {
    275   active_text_shadow_color_ = active_color;
    276   inactive_text_shadow_color_ = inactive_color;
    277   has_text_shadow_ = true;
    278 }
    279 
    280 void TextButtonBase::SetTextShadowOffset(int x, int y) {
    281   text_shadow_offset_.SetPoint(x, y);
    282 }
    283 
    284 void TextButtonBase::ClearEmbellishing() {
    285   has_text_shadow_ = false;
    286 }
    287 
    288 void TextButtonBase::ClearMaxTextSize() {
    289   max_text_size_ = text_size_;
    290 }
    291 
    292 void TextButtonBase::SetShowMultipleIconStates(bool show_multiple_icon_states) {
    293   show_multiple_icon_states_ = show_multiple_icon_states;
    294 }
    295 
    296 void TextButtonBase::SetMultiLine(bool multi_line) {
    297   if (multi_line != multi_line_) {
    298     multi_line_ = multi_line;
    299     max_text_size_.SetSize(0, 0);
    300     UpdateTextSize();
    301     SchedulePaint();
    302   }
    303 }
    304 
    305 gfx::Size TextButtonBase::GetPreferredSize() {
    306   gfx::Insets insets = GetInsets();
    307 
    308   // Use the max size to set the button boundaries.
    309   // In multiline mode max size can be undefined while
    310   // width() is 0, so max it out with current text size.
    311   gfx::Size prefsize(std::max(max_text_size_.width(),
    312                               text_size_.width()) + insets.width(),
    313                      std::max(max_text_size_.height(),
    314                               text_size_.height()) + insets.height());
    315 
    316   if (max_width_ > 0)
    317     prefsize.set_width(std::min(max_width_, prefsize.width()));
    318 
    319   prefsize.set_width(std::max(prefsize.width(), min_width_));
    320   prefsize.set_height(std::max(prefsize.height(), min_height_));
    321 
    322   return prefsize;
    323 }
    324 
    325 int TextButtonBase::GetHeightForWidth(int w) {
    326   if (!multi_line_)
    327     return View::GetHeightForWidth(w);
    328 
    329   if (max_width_ > 0)
    330     w = std::min(max_width_, w);
    331 
    332   gfx::Size text_size;
    333   CalculateTextSize(&text_size, w);
    334   int height = text_size.height() + GetInsets().height();
    335 
    336   return std::max(height, min_height_);
    337 }
    338 
    339 void TextButtonBase::OnPaint(gfx::Canvas* canvas) {
    340   PaintButton(canvas, PB_NORMAL);
    341 }
    342 
    343 void TextButtonBase::OnBoundsChanged(const gfx::Rect& previous_bounds) {
    344   if (multi_line_)
    345     UpdateTextSize();
    346 }
    347 
    348 const ui::Animation* TextButtonBase::GetAnimation() const {
    349   return hover_animation_.get();
    350 }
    351 
    352 void TextButtonBase::UpdateColor() {
    353   color_ = enabled() ? color_enabled_ : color_disabled_;
    354 }
    355 
    356 void TextButtonBase::UpdateTextSize() {
    357   int text_width = width();
    358   // If width is defined, use GetTextBounds.width() for maximum text width,
    359   // as it will take size of checkbox/radiobutton into account.
    360   if (text_width != 0) {
    361     gfx::Rect text_bounds = GetTextBounds();
    362     text_width = text_bounds.width();
    363   }
    364   CalculateTextSize(&text_size_, text_width);
    365   // Before layout width() is 0, and multiline text will be treated as one line.
    366   // Do not store max_text_size in this case. UpdateTextSize will be called
    367   // again once width() changes.
    368   if (!multi_line_ || text_width != 0) {
    369     max_text_size_.SetSize(std::max(max_text_size_.width(), text_size_.width()),
    370                            std::max(max_text_size_.height(),
    371                                     text_size_.height()));
    372     PreferredSizeChanged();
    373   }
    374 }
    375 
    376 void TextButtonBase::CalculateTextSize(gfx::Size* text_size, int max_width) {
    377   int h = font_.GetHeight();
    378   int w = multi_line_ ? max_width : 0;
    379   int flags = ComputeCanvasStringFlags();
    380   if (!multi_line_)
    381     flags |= gfx::Canvas::NO_ELLIPSIS;
    382 
    383   gfx::Canvas::SizeStringInt(text_, font_, &w, &h, 0, flags);
    384   text_size->SetSize(w, h);
    385 }
    386 
    387 int TextButtonBase::ComputeCanvasStringFlags() const {
    388   if (!multi_line_)
    389     return 0;
    390 
    391   int flags = gfx::Canvas::MULTI_LINE;
    392   switch (alignment_) {
    393     case ALIGN_LEFT:
    394       flags |= gfx::Canvas::TEXT_ALIGN_LEFT;
    395       break;
    396     case ALIGN_RIGHT:
    397       flags |= gfx::Canvas::TEXT_ALIGN_RIGHT;
    398       break;
    399     case ALIGN_CENTER:
    400       flags |= gfx::Canvas::TEXT_ALIGN_CENTER;
    401       break;
    402   }
    403   return flags;
    404 }
    405 
    406 void TextButtonBase::GetExtraParams(
    407     ui::NativeTheme::ExtraParams* params) const {
    408   params->button.checked = false;
    409   params->button.indeterminate = false;
    410   params->button.is_default = false;
    411   params->button.is_focused = false;
    412   params->button.has_border = false;
    413   params->button.classic_state = 0;
    414   params->button.background_color =
    415       GetNativeTheme()->GetSystemColor(
    416           ui::NativeTheme::kColorId_ButtonBackgroundColor);
    417 }
    418 
    419 gfx::Rect TextButtonBase::GetContentBounds(int extra_width) const {
    420   gfx::Insets insets = GetInsets();
    421   int available_width = width() - insets.width();
    422   int content_width = text_size_.width() + extra_width;
    423   int content_x = 0;
    424   switch(alignment_) {
    425     case ALIGN_LEFT:
    426       content_x = insets.left();
    427       break;
    428     case ALIGN_RIGHT:
    429       content_x = width() - insets.right() - content_width;
    430       if (content_x < insets.left())
    431         content_x = insets.left();
    432       break;
    433     case ALIGN_CENTER:
    434       content_x = insets.left() + std::max(0,
    435           (available_width - content_width) / 2);
    436       break;
    437   }
    438   content_width = std::min(content_width,
    439                            width() - insets.right() - content_x);
    440   int available_height = height() - insets.height();
    441   int content_y = (available_height - text_size_.height()) / 2 + insets.top();
    442 
    443   gfx::Rect bounds(content_x, content_y, content_width, text_size_.height());
    444   return bounds;
    445 }
    446 
    447 gfx::Rect TextButtonBase::GetTextBounds() const {
    448   return GetContentBounds(0);
    449 }
    450 
    451 void TextButtonBase::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
    452   if (mode == PB_NORMAL) {
    453     OnPaintBackground(canvas);
    454     OnPaintBorder(canvas);
    455     OnPaintFocusBorder(canvas);
    456   }
    457 
    458   gfx::Rect text_bounds(GetTextBounds());
    459   if (text_bounds.width() > 0) {
    460     // Because the text button can (at times) draw multiple elements on the
    461     // canvas, we can not mirror the button by simply flipping the canvas as
    462     // doing this will mirror the text itself. Flipping the canvas will also
    463     // make the icons look wrong because icons are almost always represented as
    464     // direction-insensitive images and such images should never be flipped
    465     // horizontally.
    466     //
    467     // Due to the above, we must perform the flipping manually for RTL UIs.
    468     text_bounds.set_x(GetMirroredXForRect(text_bounds));
    469 
    470     SkColor text_color = (show_multiple_icon_states_ &&
    471         (state() == STATE_HOVERED || state() == STATE_PRESSED)) ?
    472             color_hover_ : color_;
    473 
    474     int draw_string_flags = gfx::Canvas::DefaultCanvasTextAlignment() |
    475         ComputeCanvasStringFlags();
    476 
    477     if (mode == PB_FOR_DRAG) {
    478       // Disable sub-pixel rendering as background is transparent.
    479       draw_string_flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING;
    480 
    481 #if defined(OS_WIN)
    482       // TODO(erg): Either port DrawStringWithHalo to linux or find an
    483       // alternative here.
    484       canvas->DrawStringWithHalo(text_, font_, SK_ColorBLACK, SK_ColorWHITE,
    485           text_bounds.x(), text_bounds.y(), text_bounds.width(),
    486           text_bounds.height(), draw_string_flags);
    487 #else
    488       canvas->DrawStringInt(text_,
    489                             font_,
    490                             text_color,
    491                             text_bounds.x(),
    492                             text_bounds.y(),
    493                             text_bounds.width(),
    494                             text_bounds.height(),
    495                             draw_string_flags);
    496 #endif
    497     } else {
    498       gfx::ShadowValues shadows;
    499       if (has_text_shadow_) {
    500         SkColor color = GetWidget()->IsActive() ? active_text_shadow_color_ :
    501                                                   inactive_text_shadow_color_;
    502         shadows.push_back(gfx::ShadowValue(text_shadow_offset_, 0, color));
    503       }
    504       canvas->DrawStringWithShadows(text_, font_, text_color, text_bounds,
    505                                     0, draw_string_flags, shadows);
    506     }
    507   }
    508 }
    509 
    510 ////////////////////////////////////////////////////////////////////////////////
    511 // TextButtonBase, View overrides:
    512 
    513 gfx::Size TextButtonBase::GetMinimumSize() {
    514   return max_text_size_;
    515 }
    516 
    517 void TextButtonBase::OnEnabledChanged() {
    518   // We should always call UpdateColor() since the state of the button might be
    519   // changed by other functions like CustomButton::SetState().
    520   UpdateColor();
    521   CustomButton::OnEnabledChanged();
    522 }
    523 
    524 const char* TextButtonBase::GetClassName() const {
    525   return kViewClassName;
    526 }
    527 
    528 void TextButtonBase::OnNativeThemeChanged(const ui::NativeTheme* theme) {
    529   if (use_enabled_color_from_theme_) {
    530     color_enabled_ = theme->GetSystemColor(
    531         ui::NativeTheme::kColorId_ButtonEnabledColor);
    532   }
    533   if (use_disabled_color_from_theme_) {
    534     color_disabled_ = theme->GetSystemColor(
    535         ui::NativeTheme::kColorId_ButtonDisabledColor);
    536   }
    537   if (use_highlight_color_from_theme_) {
    538     color_highlight_ = theme->GetSystemColor(
    539         ui::NativeTheme::kColorId_ButtonHighlightColor);
    540   }
    541   if (use_hover_color_from_theme_) {
    542     color_hover_ = theme->GetSystemColor(
    543         ui::NativeTheme::kColorId_ButtonHoverColor);
    544   }
    545   UpdateColor();
    546 }
    547 
    548 ////////////////////////////////////////////////////////////////////////////////
    549 // TextButtonBase, NativeThemeDelegate overrides:
    550 
    551 gfx::Rect TextButtonBase::GetThemePaintRect() const {
    552   return GetLocalBounds();
    553 }
    554 
    555 ui::NativeTheme::State TextButtonBase::GetThemeState(
    556     ui::NativeTheme::ExtraParams* params) const {
    557   GetExtraParams(params);
    558   switch(state()) {
    559     case STATE_DISABLED:
    560       return ui::NativeTheme::kDisabled;
    561     case STATE_NORMAL:
    562       return ui::NativeTheme::kNormal;
    563     case STATE_HOVERED:
    564       return ui::NativeTheme::kHovered;
    565     case STATE_PRESSED:
    566       return ui::NativeTheme::kPressed;
    567     default:
    568       NOTREACHED() << "Unknown state: " << state();
    569       return ui::NativeTheme::kNormal;
    570   }
    571 }
    572 
    573 const ui::Animation* TextButtonBase::GetThemeAnimation() const {
    574 #if defined(OS_WIN)
    575   if (GetNativeTheme() == ui::NativeThemeWin::instance()) {
    576     return ui::NativeThemeWin::instance()->IsThemingActive() ?
    577         hover_animation_.get() : NULL;
    578   }
    579 #endif
    580   return hover_animation_.get();
    581 }
    582 
    583 ui::NativeTheme::State TextButtonBase::GetBackgroundThemeState(
    584   ui::NativeTheme::ExtraParams* params) const {
    585   GetExtraParams(params);
    586   return ui::NativeTheme::kNormal;
    587 }
    588 
    589 ui::NativeTheme::State TextButtonBase::GetForegroundThemeState(
    590   ui::NativeTheme::ExtraParams* params) const {
    591   GetExtraParams(params);
    592   return ui::NativeTheme::kHovered;
    593 }
    594 
    595 ////////////////////////////////////////////////////////////////////////////////
    596 //
    597 // TextButton
    598 //
    599 ////////////////////////////////////////////////////////////////////////////////
    600 
    601 TextButton::TextButton(ButtonListener* listener, const string16& text)
    602     : TextButtonBase(listener, text),
    603       icon_placement_(ICON_ON_LEFT),
    604       has_hover_icon_(false),
    605       has_pushed_icon_(false),
    606       icon_text_spacing_(kDefaultIconTextSpacing),
    607       ignore_minimum_size_(true) {
    608   set_border(new TextButtonDefaultBorder);
    609   set_focus_border(FocusBorder::CreateDashedFocusBorder(kFocusRectInset,
    610                                                         kFocusRectInset,
    611                                                         kFocusRectInset,
    612                                                         kFocusRectInset));
    613 }
    614 
    615 TextButton::~TextButton() {
    616 }
    617 
    618 void TextButton::SetIcon(const gfx::ImageSkia& icon) {
    619   icon_ = icon;
    620   SchedulePaint();
    621 }
    622 
    623 void TextButton::SetHoverIcon(const gfx::ImageSkia& icon) {
    624   icon_hover_ = icon;
    625   has_hover_icon_ = true;
    626   SchedulePaint();
    627 }
    628 
    629 void TextButton::SetPushedIcon(const gfx::ImageSkia& icon) {
    630   icon_pushed_ = icon;
    631   has_pushed_icon_ = true;
    632   SchedulePaint();
    633 }
    634 
    635 gfx::Size TextButton::GetPreferredSize() {
    636   gfx::Size prefsize(TextButtonBase::GetPreferredSize());
    637   prefsize.Enlarge(icon_.width(), 0);
    638   prefsize.set_height(std::max(prefsize.height(), icon_.height()));
    639 
    640   // Use the max size to set the button boundaries.
    641   if (icon_.width() > 0 && !text_.empty())
    642     prefsize.Enlarge(icon_text_spacing_, 0);
    643 
    644   if (max_width_ > 0)
    645     prefsize.set_width(std::min(max_width_, prefsize.width()));
    646 
    647 #if defined(OS_WIN)
    648   // Clamp the size returned to at least the minimum size.
    649   if (!ignore_minimum_size_) {
    650     gfx::PlatformFontWin* platform_font =
    651         static_cast<gfx::PlatformFontWin*>(font_.platform_font());
    652     prefsize.set_width(std::max(
    653         prefsize.width(),
    654         platform_font->horizontal_dlus_to_pixels(kMinWidthDLUs)));
    655     prefsize.set_height(std::max(
    656         prefsize.height(),
    657         platform_font->vertical_dlus_to_pixels(kMinHeightDLUs)));
    658   }
    659 #endif
    660 
    661   prefsize.set_width(std::max(prefsize.width(), min_width_));
    662   prefsize.set_height(std::max(prefsize.height(), min_height_));
    663 
    664   return prefsize;
    665 }
    666 
    667 void TextButton::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
    668   TextButtonBase::PaintButton(canvas, mode);
    669 
    670   const gfx::ImageSkia& icon = GetImageToPaint();
    671 
    672   if (icon.width() > 0) {
    673     gfx::Rect text_bounds = GetTextBounds();
    674     int icon_x;
    675     int spacing = text_.empty() ? 0 : icon_text_spacing_;
    676     gfx::Insets insets = GetInsets();
    677     if (icon_placement_ == ICON_ON_LEFT) {
    678       icon_x = text_bounds.x() - icon.width() - spacing;
    679     } else if (icon_placement_ == ICON_ON_RIGHT) {
    680       icon_x = text_bounds.right() + spacing;
    681     } else {  // ICON_CENTERED
    682       DCHECK(text_.empty());
    683       icon_x = (width() - insets.width() - icon.width()) / 2 + insets.left();
    684     }
    685 
    686     int available_height = height() - insets.height();
    687     int icon_y = (available_height - icon.height()) / 2 + insets.top();
    688 
    689     // Mirroring the icon position if necessary.
    690     gfx::Rect icon_bounds(icon_x, icon_y, icon.width(), icon.height());
    691     icon_bounds.set_x(GetMirroredXForRect(icon_bounds));
    692     canvas->DrawImageInt(icon, icon_bounds.x(), icon_bounds.y());
    693   }
    694 }
    695 
    696 void TextButton::set_ignore_minimum_size(bool ignore_minimum_size) {
    697   ignore_minimum_size_ = ignore_minimum_size;
    698 }
    699 
    700 const char* TextButton::GetClassName() const {
    701   return kViewClassName;
    702 }
    703 
    704 ui::NativeTheme::Part TextButton::GetThemePart() const {
    705   return ui::NativeTheme::kPushButton;
    706 }
    707 
    708 void TextButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
    709   TextButtonBase::GetExtraParams(params);
    710   params->button.is_default = is_default_;
    711 }
    712 
    713 gfx::Rect TextButton::GetTextBounds() const {
    714   int extra_width = 0;
    715 
    716   const gfx::ImageSkia& icon = GetImageToPaint();
    717   if (icon.width() > 0)
    718     extra_width = icon.width() + (text_.empty() ? 0 : icon_text_spacing_);
    719 
    720   gfx::Rect bounds(GetContentBounds(extra_width));
    721 
    722   if (extra_width > 0) {
    723     // Make sure the icon is always fully visible.
    724     if (icon_placement_ == ICON_ON_LEFT) {
    725       bounds.Inset(extra_width, 0, 0, 0);
    726     } else if (icon_placement_ == ICON_ON_RIGHT) {
    727       bounds.Inset(0, 0, extra_width, 0);
    728     }
    729   }
    730 
    731   return bounds;
    732 }
    733 
    734 const gfx::ImageSkia& TextButton::GetImageToPaint() const {
    735   if (show_multiple_icon_states_) {
    736     if (has_hover_icon_ && (state() == STATE_HOVERED))
    737       return icon_hover_;
    738     if (has_pushed_icon_ && (state() == STATE_PRESSED))
    739       return icon_pushed_;
    740   }
    741   return icon_;
    742 }
    743 
    744 }  // namespace views
    745