- add sources.
[platform/framework/web/crosswalk.git] / src / ui / views / controls / button / label_button.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/views/controls/button/label_button.h"
6
7 #include "base/logging.h"
8 #include "grit/ui_resources.h"
9 #include "ui/base/resource/resource_bundle.h"
10 #include "ui/gfx/animation/throb_animation.h"
11 #include "ui/gfx/sys_color_change_listener.h"
12 #include "ui/native_theme/native_theme.h"
13 #include "ui/views/controls/button/label_button_border.h"
14 #include "ui/views/focus_border.h"
15 #include "ui/views/window/dialog_delegate.h"
16
17 #if defined(OS_WIN)
18 #include "ui/native_theme/native_theme_win.h"
19 #endif
20
21 namespace {
22
23 // The spacing between the icon and text.
24 const int kSpacing = 5;
25
26 // The length of the hover fade animation.
27 const int kHoverAnimationDurationMs = 170;
28
29 // Default text and shadow colors for STYLE_BUTTON.
30 const SkColor kStyleButtonTextColor = SK_ColorBLACK;
31 const SkColor kStyleButtonShadowColor = SK_ColorWHITE;
32
33 }  // namespace
34
35 namespace views {
36
37 // static
38 const char LabelButton::kViewClassName[] = "LabelButton";
39
40 LabelButton::LabelButton(ButtonListener* listener, const string16& text)
41     : CustomButton(listener),
42       image_(new ImageView()),
43       label_(new Label()),
44       button_state_images_(),
45       button_state_colors_(),
46       explicitly_set_colors_(),
47       is_default_(false),
48       style_(STYLE_TEXTBUTTON) {
49   SetAnimationDuration(kHoverAnimationDurationMs);
50   SetText(text);
51
52   AddChildView(image_);
53   image_->set_interactive(false);
54
55   AddChildView(label_);
56   label_->SetAutoColorReadabilityEnabled(false);
57   label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
58
59   // Initialize the colors, border, and layout.
60   SetStyle(style_);
61
62   SetAccessibleName(text);
63 }
64
65 LabelButton::~LabelButton() {}
66
67 const gfx::ImageSkia& LabelButton::GetImage(ButtonState for_state) {
68   if (for_state != STATE_NORMAL && button_state_images_[for_state].isNull())
69     return button_state_images_[STATE_NORMAL];
70   return button_state_images_[for_state];
71 }
72
73 void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
74   button_state_images_[for_state] = image;
75   UpdateImage();
76 }
77
78 const string16& LabelButton::GetText() const {
79   return label_->text();
80 }
81
82 void LabelButton::SetText(const string16& text) {
83   SetAccessibleName(text);
84   label_->SetText(text);
85 }
86
87 void LabelButton::SetTextColor(ButtonState for_state, SkColor color) {
88   button_state_colors_[for_state] = color;
89   if (for_state == STATE_DISABLED)
90     label_->SetDisabledColor(color);
91   else if (for_state == state())
92     label_->SetEnabledColor(color);
93   explicitly_set_colors_[for_state] = true;
94 }
95
96 bool LabelButton::GetTextMultiLine() const {
97   return label_->is_multi_line();
98 }
99
100 void LabelButton::SetTextMultiLine(bool text_multi_line) {
101   label_->SetMultiLine(text_multi_line);
102 }
103
104 const gfx::Font& LabelButton::GetFont() const {
105   return label_->font();
106 }
107
108 void LabelButton::SetFont(const gfx::Font& font) {
109   label_->SetFont(font);
110 }
111
112 void LabelButton::SetElideBehavior(Label::ElideBehavior elide_behavior) {
113   label_->SetElideBehavior(elide_behavior);
114 }
115
116 gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const {
117   return label_->horizontal_alignment();
118 }
119
120 void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
121   label_->SetHorizontalAlignment(alignment);
122   InvalidateLayout();
123 }
124
125 void LabelButton::SetIsDefault(bool is_default) {
126   if (is_default == is_default_)
127     return;
128   is_default_ = is_default;
129   ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE);
130   is_default_ ? AddAccelerator(accel) : RemoveAccelerator(accel);
131
132   // STYLE_BUTTON uses bold text to indicate default buttons.
133   if (style_ == STYLE_BUTTON) {
134     int style = label_->font().GetStyle();
135     style = is_default ? style | gfx::Font::BOLD : style & ~gfx::Font::BOLD;
136     label_->SetFont(label_->font().DeriveFont(0, style));
137   }
138 }
139
140 void LabelButton::SetStyle(ButtonStyle style) {
141   // Use the new button style instead of the native button style.
142   // TODO(msw): Officialy deprecate and remove STYLE_NATIVE_TEXTBUTTON.
143   if (DialogDelegate::UseNewStyle() && style == STYLE_NATIVE_TEXTBUTTON)
144     style = STYLE_BUTTON;
145
146   style_ = style;
147   set_border(new LabelButtonBorder(style));
148   // Inset the button focus rect from the actual border; roughly match Windows.
149   set_focus_border(style == STYLE_BUTTON ?
150       NULL : FocusBorder::CreateDashedFocusBorder(3, 3, 3, 3));
151   if (style == STYLE_BUTTON || style == STYLE_NATIVE_TEXTBUTTON) {
152     label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
153     set_focusable(true);
154   }
155   if (style == STYLE_BUTTON)
156     set_min_size(gfx::Size(70, 33));
157   // Invalidate the layout to pickup the new insets from the border.
158   InvalidateLayout();
159   ResetColorsFromNativeTheme();
160 }
161
162 gfx::Size LabelButton::GetPreferredSize() {
163   // Use a temporary label copy for sizing to avoid calculation side-effects.
164   gfx::Font font = GetFont();
165   Label label(GetText(), font);
166   label.SetMultiLine(GetTextMultiLine());
167
168   if (style() == STYLE_BUTTON) {
169     // Some text appears wider when rendered normally than when rendered bold.
170     // Accommodate the widest, as buttons may show bold and shouldn't resize.
171     const int current_width = label.GetPreferredSize().width();
172     label.SetFont(font.DeriveFont(0, font.GetStyle() ^ gfx::Font::BOLD));
173     if (label.GetPreferredSize().width() < current_width)
174       label.SetFont(font);
175   }
176
177   // Resize multi-line labels given the current limited available width.
178   const gfx::Size image_size(image_->GetPreferredSize());
179   const int image_width = image_size.width();
180   if (GetTextMultiLine() && (width() > image_width + kSpacing))
181     label.SizeToFit(width() - image_width - (image_width > 0 ? kSpacing : 0));
182
183   // Calculate the required size.
184   gfx::Size size(label.GetPreferredSize());
185   if (image_width > 0 && size.width() > 0)
186     size.Enlarge(kSpacing, 0);
187   size.SetToMax(gfx::Size(0, image_size.height()));
188   const gfx::Insets insets(GetInsets());
189   size.Enlarge(image_size.width() + insets.width(), insets.height());
190
191   // Increase the minimum size monotonically with the preferred size.
192   size.SetToMax(min_size_);
193   min_size_ = size;
194
195   // Return the largest known size clamped to the maximum size (if valid).
196   if (max_size_.width() > 0)
197     size.set_width(std::min(max_size_.width(), size.width()));
198   if (max_size_.height() > 0)
199     size.set_height(std::min(max_size_.height(), size.height()));
200   return size;
201 }
202
203 void LabelButton::Layout() {
204   gfx::HorizontalAlignment adjusted_alignment = GetHorizontalAlignment();
205   if (base::i18n::IsRTL() && adjusted_alignment != gfx::ALIGN_CENTER)
206     adjusted_alignment = (adjusted_alignment == gfx::ALIGN_LEFT) ?
207         gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
208
209   gfx::Rect child_area(GetLocalBounds());
210   child_area.Inset(GetInsets());
211
212   gfx::Size image_size(image_->GetPreferredSize());
213   image_size.set_width(std::min(image_size.width(), child_area.width()));
214   image_size.set_height(std::min(image_size.height(), child_area.height()));
215
216   // The label takes any remaining width after sizing the image, unless both
217   // views are centered. In that case, using the tighter preferred label width
218   // avoids wasted space within the label that would look like awkward padding.
219   gfx::Size label_size(child_area.size());
220   if (!image_size.IsEmpty() && !label_size.IsEmpty()) {
221     label_size.set_width(
222         std::max(child_area.width() - image_size.width() - kSpacing, 0));
223     if (adjusted_alignment == gfx::ALIGN_CENTER) {
224       // Ensure multi-line labels paired with images use their available width.
225       if (GetTextMultiLine())
226         label_->SizeToFit(label_size.width());
227       label_size.set_width(
228           std::min(label_size.width(), label_->GetPreferredSize().width()));
229     }
230   }
231
232   gfx::Point image_origin(child_area.origin());
233   image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
234   if (adjusted_alignment == gfx::ALIGN_CENTER) {
235     const int total_width = image_size.width() + label_size.width() +
236         ((image_size.width() > 0 && label_size.width() > 0) ? kSpacing : 0);
237     image_origin.Offset((child_area.width() - total_width) / 2, 0);
238   } else if (adjusted_alignment == gfx::ALIGN_RIGHT) {
239     image_origin.Offset(child_area.width() - image_size.width(), 0);
240   }
241
242   gfx::Point label_origin(child_area.origin());
243   if (!image_size.IsEmpty() &&adjusted_alignment != gfx::ALIGN_RIGHT)
244     label_origin.set_x(image_origin.x() + image_size.width() + kSpacing);
245
246   image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
247   label_->SetBoundsRect(gfx::Rect(label_origin, label_size));
248 }
249
250 const char* LabelButton::GetClassName() const {
251   return kViewClassName;
252 }
253
254 void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
255   params->button.checked = false;
256   params->button.indeterminate = false;
257   params->button.is_default = is_default_;
258   params->button.is_focused = HasFocus() && IsAccessibilityFocusable();
259   params->button.has_border = style() == STYLE_NATIVE_TEXTBUTTON;
260   params->button.classic_state = 0;
261   params->button.background_color = label()->background_color();
262 }
263
264 void LabelButton::ResetColorsFromNativeTheme() {
265   const ui::NativeTheme* theme = GetNativeTheme();
266   SkColor colors[STATE_COUNT] = {
267     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonEnabledColor),
268     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
269     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
270     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonDisabledColor),
271   };
272
273   // Certain styles do not change text color when hovered or pressed.
274   bool constant_text_color = false;
275 #if defined(OS_WIN)
276   constant_text_color |= (style() == STYLE_NATIVE_TEXTBUTTON &&
277                           theme == ui::NativeThemeWin::instance());
278 #endif
279
280   // Use hardcoded colors for inverted color scheme support and STYLE_BUTTON.
281   if (gfx::IsInvertedColorScheme()) {
282     constant_text_color = true;
283     colors[STATE_NORMAL] = SK_ColorWHITE;
284     label_->SetBackgroundColor(SK_ColorBLACK);
285     label_->SetAutoColorReadabilityEnabled(true);
286     label_->ClearEmbellishing();
287   } else if (style() == STYLE_BUTTON) {
288     constant_text_color = true;
289     colors[STATE_NORMAL] = kStyleButtonTextColor;
290     label_->SetBackgroundColor(theme->GetSystemColor(
291         ui::NativeTheme::kColorId_ButtonBackgroundColor));
292     label_->SetAutoColorReadabilityEnabled(false);
293     label_->SetShadowColors(kStyleButtonShadowColor, kStyleButtonShadowColor);
294     label_->SetShadowOffset(0, 1);
295   }
296
297   if (constant_text_color)
298     colors[STATE_HOVERED] = colors[STATE_PRESSED] = colors[STATE_NORMAL];
299
300   for (size_t state = STATE_NORMAL; state < STATE_COUNT; ++state) {
301     if (!explicitly_set_colors_[state]) {
302       SetTextColor(static_cast<ButtonState>(state), colors[state]);
303       explicitly_set_colors_[state] = false;
304     }
305   }
306 }
307
308 void LabelButton::UpdateImage() {
309   image_->SetImage(GetImage(state()));
310 }
311
312 void LabelButton::StateChanged() {
313   const gfx::Size previous_image_size(image_->GetPreferredSize());
314   UpdateImage();
315   const SkColor color = button_state_colors_[state()];
316   if (state() != STATE_DISABLED && label_->enabled_color() != color)
317     label_->SetEnabledColor(color);
318   label_->SetEnabled(state() != STATE_DISABLED);
319   if (image_->GetPreferredSize() != previous_image_size)
320     Layout();
321 }
322
323 void LabelButton::ChildPreferredSizeChanged(View* child) {
324   PreferredSizeChanged();
325 }
326
327 void LabelButton::OnNativeThemeChanged(const ui::NativeTheme* theme) {
328   ResetColorsFromNativeTheme();
329 }
330
331 ui::NativeTheme::Part LabelButton::GetThemePart() const {
332   return ui::NativeTheme::kPushButton;
333 }
334
335 gfx::Rect LabelButton::GetThemePaintRect() const {
336   return GetLocalBounds();
337 }
338
339 ui::NativeTheme::State LabelButton::GetThemeState(
340     ui::NativeTheme::ExtraParams* params) const {
341   GetExtraParams(params);
342   switch (state()) {
343     case STATE_NORMAL:   return ui::NativeTheme::kNormal;
344     case STATE_HOVERED:  return ui::NativeTheme::kHovered;
345     case STATE_PRESSED:  return ui::NativeTheme::kPressed;
346     case STATE_DISABLED: return ui::NativeTheme::kDisabled;
347     case STATE_COUNT:    NOTREACHED() << "Unknown state: " << state();
348   }
349   return ui::NativeTheme::kNormal;
350 }
351
352 const gfx::Animation* LabelButton::GetThemeAnimation() const {
353 #if defined(OS_WIN)
354   if (style() == STYLE_NATIVE_TEXTBUTTON &&
355       GetNativeTheme() == ui::NativeThemeWin::instance()) {
356     return ui::NativeThemeWin::instance()->IsThemingActive() ?
357         hover_animation_.get() : NULL;
358   }
359 #endif
360   return hover_animation_.get();
361 }
362
363 ui::NativeTheme::State LabelButton::GetBackgroundThemeState(
364     ui::NativeTheme::ExtraParams* params) const {
365   GetExtraParams(params);
366   return ui::NativeTheme::kNormal;
367 }
368
369 ui::NativeTheme::State LabelButton::GetForegroundThemeState(
370     ui::NativeTheme::ExtraParams* params) const {
371   GetExtraParams(params);
372   return ui::NativeTheme::kHovered;
373 }
374
375 }  // namespace views