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