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
91TextButtonBorder* TextButtonBorder::AsTextButtonBorder() {
92  return this;
93}
94
95const TextButtonBorder* TextButtonBorder::AsTextButtonBorder() const {
96  return this;
97}
98
99
100// TextButtonDefaultBorder ----------------------------------------------------
101
102TextButtonDefaultBorder::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
110TextButtonDefaultBorder::~TextButtonDefaultBorder() {
111}
112
113void 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
144gfx::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
158TextButtonNativeThemeBorder::TextButtonNativeThemeBorder(
159    NativeThemeDelegate* delegate)
160    : delegate_(delegate) {
161  SetInsets(gfx::Insets(kPreferredNativeThemePaddingVertical,
162                        kPreferredNativeThemePaddingHorizontal,
163                        kPreferredNativeThemePaddingVertical,
164                        kPreferredNativeThemePaddingHorizontal));
165}
166
167TextButtonNativeThemeBorder::~TextButtonNativeThemeBorder() {
168}
169
170void 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
202TextButtonBase::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
228TextButtonBase::~TextButtonBase() {
229}
230
231void 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
242void TextButtonBase::SetText(const string16& text) {
243  if (text == text_)
244    return;
245  text_ = text;
246  SetAccessibleName(text);
247  UpdateTextSize();
248}
249
250void TextButtonBase::SetFont(const gfx::Font& font) {
251  font_ = font;
252  UpdateTextSize();
253}
254
255void TextButtonBase::SetEnabledColor(SkColor color) {
256  color_enabled_ = color;
257  use_enabled_color_from_theme_ = false;
258  UpdateColor();
259}
260
261void TextButtonBase::SetDisabledColor(SkColor color) {
262  color_disabled_ = color;
263  use_disabled_color_from_theme_ = false;
264  UpdateColor();
265}
266
267void TextButtonBase::SetHighlightColor(SkColor color) {
268  color_highlight_ = color;
269  use_highlight_color_from_theme_ = false;
270}
271
272void TextButtonBase::SetHoverColor(SkColor color) {
273  color_hover_ = color;
274  use_hover_color_from_theme_ = false;
275}
276
277void 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
284void TextButtonBase::SetTextShadowOffset(int x, int y) {
285  text_shadow_offset_.SetPoint(x, y);
286}
287
288void TextButtonBase::ClearEmbellishing() {
289  has_text_shadow_ = false;
290}
291
292void TextButtonBase::ClearMaxTextSize() {
293  max_text_size_ = text_size_;
294}
295
296void TextButtonBase::SetShowMultipleIconStates(bool show_multiple_icon_states) {
297  show_multiple_icon_states_ = show_multiple_icon_states;
298}
299
300void 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
309gfx::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
329int 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
343void TextButtonBase::OnPaint(gfx::Canvas* canvas) {
344  PaintButton(canvas, PB_NORMAL);
345}
346
347void TextButtonBase::OnBoundsChanged(const gfx::Rect& previous_bounds) {
348  if (multi_line_)
349    UpdateTextSize();
350}
351
352const gfx::Animation* TextButtonBase::GetAnimation() const {
353  return hover_animation_.get();
354}
355
356void TextButtonBase::UpdateColor() {
357  color_ = enabled() ? color_enabled_ : color_disabled_;
358}
359
360void 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
380void 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
391int 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
410void TextButtonBase::OnFocus() {
411  View::OnFocus();
412  if (focus_painter_)
413    SchedulePaint();
414}
415
416void TextButtonBase::OnBlur() {
417  View::OnBlur();
418  if (focus_painter_)
419    SchedulePaint();
420}
421
422void 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
435gfx::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
463gfx::Rect TextButtonBase::GetTextBounds() const {
464  return GetContentBounds(0);
465}
466
467void TextButtonBase::SetFocusPainter(scoped_ptr<Painter> focus_painter) {
468  focus_painter_ = focus_painter.Pass();
469}
470
471void 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
530gfx::Size TextButtonBase::GetMinimumSize() {
531  return max_text_size_;
532}
533
534void 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
541const char* TextButtonBase::GetClassName() const {
542  return kViewClassName;
543}
544
545void 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
565gfx::Rect TextButtonBase::GetThemePaintRect() const {
566  return GetLocalBounds();
567}
568
569ui::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
587const 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
597ui::NativeTheme::State TextButtonBase::GetBackgroundThemeState(
598  ui::NativeTheme::ExtraParams* params) const {
599  GetExtraParams(params);
600  return ui::NativeTheme::kNormal;
601}
602
603ui::NativeTheme::State TextButtonBase::GetForegroundThemeState(
604  ui::NativeTheme::ExtraParams* params) const {
605  GetExtraParams(params);
606  return ui::NativeTheme::kHovered;
607}
608
609
610// TextButton -----------------------------------------------------------------
611
612TextButton::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
625TextButton::~TextButton() {
626}
627
628void TextButton::SetIcon(const gfx::ImageSkia& icon) {
629  icon_ = icon;
630  SchedulePaint();
631}
632
633void TextButton::SetHoverIcon(const gfx::ImageSkia& icon) {
634  icon_hover_ = icon;
635  has_hover_icon_ = true;
636  SchedulePaint();
637}
638
639void TextButton::SetPushedIcon(const gfx::ImageSkia& icon) {
640  icon_pushed_ = icon;
641  has_pushed_icon_ = true;
642  SchedulePaint();
643}
644
645gfx::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
677void 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
706void TextButton::set_ignore_minimum_size(bool ignore_minimum_size) {
707  ignore_minimum_size_ = ignore_minimum_size;
708}
709
710const char* TextButton::GetClassName() const {
711  return kViewClassName;
712}
713
714ui::NativeTheme::Part TextButton::GetThemePart() const {
715  return ui::NativeTheme::kPushButton;
716}
717
718void TextButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
719  TextButtonBase::GetExtraParams(params);
720  params->button.is_default = is_default_;
721}
722
723gfx::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
744const 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