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