fa358c1c9d3d78e5370eb7be9573ac91cc8c9dfb
[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/canvas.h"
12 #include "ui/gfx/font_list.h"
13 #include "ui/gfx/sys_color_change_listener.h"
14 #include "ui/native_theme/native_theme.h"
15 #include "ui/views/background.h"
16 #include "ui/views/controls/button/label_button_border.h"
17 #include "ui/views/painter.h"
18 #include "ui/views/window/dialog_delegate.h"
19
20 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
21 #include "ui/views/linux_ui/linux_ui.h"
22 #endif
23
24 namespace {
25
26 // The spacing between the icon and text.
27 const int kSpacing = 5;
28
29 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
30 // Default text and shadow colors for STYLE_BUTTON.
31 const SkColor kStyleButtonTextColor = SK_ColorBLACK;
32 const SkColor kStyleButtonShadowColor = SK_ColorWHITE;
33 #endif
34
35 }  // namespace
36
37 namespace views {
38
39 // static
40 const int LabelButton::kHoverAnimationDurationMs = 170;
41
42 // static
43 const char LabelButton::kViewClassName[] = "LabelButton";
44
45 LabelButton::LabelButton(ButtonListener* listener, const base::string16& text)
46     : CustomButton(listener),
47       image_(new ImageView()),
48       label_(new Label()),
49       button_state_images_(),
50       button_state_colors_(),
51       explicitly_set_colors_(),
52       is_default_(false),
53       style_(STYLE_TEXTBUTTON),
54       border_is_themed_border_(true) {
55   SetAnimationDuration(kHoverAnimationDurationMs);
56   SetText(text);
57   SetFontList(gfx::FontList());
58
59   AddChildView(image_);
60   image_->set_interactive(false);
61
62   AddChildView(label_);
63   label_->SetAutoColorReadabilityEnabled(false);
64   label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
65
66   // Initialize the colors, border, and layout.
67   SetStyle(style_);
68
69   SetAccessibleName(text);
70 }
71
72 LabelButton::~LabelButton() {}
73
74 const gfx::ImageSkia& LabelButton::GetImage(ButtonState for_state) {
75   if (for_state != STATE_NORMAL && button_state_images_[for_state].isNull())
76     return button_state_images_[STATE_NORMAL];
77   return button_state_images_[for_state];
78 }
79
80 void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
81   button_state_images_[for_state] = image;
82   UpdateImage();
83 }
84
85 const base::string16& LabelButton::GetText() const {
86   return label_->text();
87 }
88
89 void LabelButton::SetText(const base::string16& text) {
90   SetAccessibleName(text);
91   label_->SetText(text);
92 }
93
94 void LabelButton::SetTextColor(ButtonState for_state, SkColor color) {
95   button_state_colors_[for_state] = color;
96   if (for_state == STATE_DISABLED)
97     label_->SetDisabledColor(color);
98   else if (for_state == state())
99     label_->SetEnabledColor(color);
100   explicitly_set_colors_[for_state] = true;
101 }
102
103 void LabelButton::SetTextShadows(const gfx::ShadowValues& shadows) {
104   label_->set_shadows(shadows);
105 }
106
107 void LabelButton::SetTextSubpixelRenderingEnabled(bool enabled) {
108   label_->set_subpixel_rendering_enabled(enabled);
109 }
110
111 bool LabelButton::GetTextMultiLine() const {
112   return label_->is_multi_line();
113 }
114
115 void LabelButton::SetTextMultiLine(bool text_multi_line) {
116   label_->SetMultiLine(text_multi_line);
117 }
118
119 const gfx::FontList& LabelButton::GetFontList() const {
120   return label_->font_list();
121 }
122
123 void LabelButton::SetFontList(const gfx::FontList& font_list) {
124   cached_normal_font_list_ = font_list;
125   cached_bold_font_list_ = font_list.DeriveWithStyle(
126       font_list.GetFontStyle() | gfx::Font::BOLD);
127
128   // STYLE_BUTTON uses bold text to indicate default buttons.
129   label_->SetFontList(
130       style_ == STYLE_BUTTON && is_default_ ?
131       cached_bold_font_list_ : cached_normal_font_list_);
132 }
133
134 void LabelButton::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
135   label_->SetElideBehavior(elide_behavior);
136 }
137
138 gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const {
139   return label_->GetHorizontalAlignment();
140 }
141
142 void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
143   label_->SetHorizontalAlignment(alignment);
144   InvalidateLayout();
145 }
146
147 void LabelButton::SetDirectionalityMode(gfx::DirectionalityMode mode) {
148   label_->set_directionality_mode(mode);
149 }
150
151 void LabelButton::SetIsDefault(bool is_default) {
152   if (is_default == is_default_)
153     return;
154   is_default_ = is_default;
155   ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE);
156   is_default_ ? AddAccelerator(accel) : RemoveAccelerator(accel);
157
158   // STYLE_BUTTON uses bold text to indicate default buttons.
159   if (style_ == STYLE_BUTTON) {
160     label_->SetFontList(
161         is_default ? cached_bold_font_list_ : cached_normal_font_list_);
162   }
163 }
164
165 void LabelButton::SetStyle(ButtonStyle style) {
166   style_ = style;
167   // Inset the button focus rect from the actual border; roughly match Windows.
168   if (style == STYLE_BUTTON) {
169     SetFocusPainter(scoped_ptr<Painter>());
170   } else {
171     SetFocusPainter(Painter::CreateDashedFocusPainterWithInsets(
172                         gfx::Insets(3, 3, 3, 3)));
173   }
174   if (style == STYLE_BUTTON) {
175     label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
176     SetFocusable(true);
177   }
178   if (style == STYLE_BUTTON)
179     set_min_size(gfx::Size(70, 33));
180
181   OnNativeThemeChanged(GetNativeTheme());
182 }
183
184 void LabelButton::SetFocusPainter(scoped_ptr<Painter> focus_painter) {
185   focus_painter_ = focus_painter.Pass();
186 }
187
188 gfx::Size LabelButton::GetPreferredSize() const {
189   // Use a temporary label copy for sizing to avoid calculation side-effects.
190   Label label(GetText(), cached_normal_font_list_);
191   label.set_shadows(label_->shadows());
192   label.SetMultiLine(GetTextMultiLine());
193
194   if (style() == STYLE_BUTTON) {
195     // Some text appears wider when rendered normally than when rendered bold.
196     // Accommodate the widest, as buttons may show bold and shouldn't resize.
197     const int current_width = label.GetPreferredSize().width();
198     label.SetFontList(cached_bold_font_list_);
199     if (label.GetPreferredSize().width() < current_width)
200       label.SetFontList(cached_normal_font_list_);
201   }
202
203   // Resize multi-line labels given the current limited available width.
204   const gfx::Size image_size(image_->GetPreferredSize());
205   const int image_width = image_size.width();
206   if (GetTextMultiLine() && (width() > image_width + kSpacing))
207     label.SizeToFit(width() - image_width - (image_width > 0 ? kSpacing : 0));
208
209   // Calculate the required size.
210   gfx::Size size(label.GetPreferredSize());
211   if (image_width > 0 && size.width() > 0)
212     size.Enlarge(kSpacing, 0);
213   size.SetToMax(gfx::Size(0, image_size.height()));
214   const gfx::Insets insets(GetInsets());
215   size.Enlarge(image_size.width() + insets.width(), insets.height());
216
217   // Make the size at least as large as the minimum size needed by the border.
218   size.SetToMax(border() ? border()->GetMinimumSize() : gfx::Size());
219
220   // Increase the minimum size monotonically with the preferred size.
221   size.SetToMax(min_size_);
222   min_size_ = size;
223
224   // Return the largest known size clamped to the maximum size (if valid).
225   if (max_size_.width() > 0)
226     size.set_width(std::min(max_size_.width(), size.width()));
227   if (max_size_.height() > 0)
228     size.set_height(std::min(max_size_.height(), size.height()));
229   return size;
230 }
231
232 void LabelButton::Layout() {
233   gfx::HorizontalAlignment adjusted_alignment = GetHorizontalAlignment();
234   if (base::i18n::IsRTL() && adjusted_alignment != gfx::ALIGN_CENTER)
235     adjusted_alignment = (adjusted_alignment == gfx::ALIGN_LEFT) ?
236         gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
237
238   gfx::Rect child_area(GetChildAreaBounds());
239   child_area.Inset(GetInsets());
240
241   gfx::Size image_size(image_->GetPreferredSize());
242   image_size.SetToMin(child_area.size());
243
244   // The label takes any remaining width after sizing the image, unless both
245   // views are centered. In that case, using the tighter preferred label width
246   // avoids wasted space within the label that would look like awkward padding.
247   gfx::Size label_size(child_area.size());
248   if (!image_size.IsEmpty() && !label_size.IsEmpty()) {
249     label_size.set_width(
250         std::max(child_area.width() - image_size.width() - kSpacing, 0));
251     if (adjusted_alignment == gfx::ALIGN_CENTER) {
252       // Ensure multi-line labels paired with images use their available width.
253       if (GetTextMultiLine())
254         label_->SizeToFit(label_size.width());
255       label_size.set_width(
256           std::min(label_size.width(), label_->GetPreferredSize().width()));
257     }
258   }
259
260   gfx::Point image_origin(child_area.origin());
261   image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
262   if (adjusted_alignment == gfx::ALIGN_CENTER) {
263     const int total_width = image_size.width() + label_size.width() +
264         ((image_size.width() > 0 && label_size.width() > 0) ? kSpacing : 0);
265     image_origin.Offset((child_area.width() - total_width) / 2, 0);
266   } else if (adjusted_alignment == gfx::ALIGN_RIGHT) {
267     image_origin.Offset(child_area.width() - image_size.width(), 0);
268   }
269
270   gfx::Point label_origin(child_area.origin());
271   if (!image_size.IsEmpty() && adjusted_alignment != gfx::ALIGN_RIGHT)
272     label_origin.set_x(image_origin.x() + image_size.width() + kSpacing);
273
274   image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
275   label_->SetBoundsRect(gfx::Rect(label_origin, label_size));
276 }
277
278 const char* LabelButton::GetClassName() const {
279   return kViewClassName;
280 }
281
282 scoped_ptr<LabelButtonBorder> LabelButton::CreateDefaultBorder() const {
283   return scoped_ptr<LabelButtonBorder>(new LabelButtonBorder(style_));
284 }
285
286 void LabelButton::SetBorder(scoped_ptr<Border> border) {
287   border_is_themed_border_ = false;
288   View::SetBorder(border.Pass());
289 }
290
291 gfx::Rect LabelButton::GetChildAreaBounds() {
292   return GetLocalBounds();
293 }
294
295 void LabelButton::OnPaint(gfx::Canvas* canvas) {
296   View::OnPaint(canvas);
297   Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
298 }
299
300 void LabelButton::OnFocus() {
301   View::OnFocus();
302   // Typically the border renders differently when focused.
303   SchedulePaint();
304 }
305
306 void LabelButton::OnBlur() {
307   View::OnBlur();
308   // Typically the border renders differently when focused.
309   SchedulePaint();
310 }
311
312 void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
313   params->button.checked = false;
314   params->button.indeterminate = false;
315   params->button.is_default = is_default_;
316   params->button.is_focused = HasFocus() && IsAccessibilityFocusable();
317   params->button.has_border = false;
318   params->button.classic_state = 0;
319   params->button.background_color = label_->background_color();
320 }
321
322 void LabelButton::ResetColorsFromNativeTheme() {
323   const ui::NativeTheme* theme = GetNativeTheme();
324   SkColor colors[STATE_COUNT] = {
325     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonEnabledColor),
326     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
327     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
328     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonDisabledColor),
329   };
330
331   // Certain styles do not change text color when hovered or pressed.
332   bool constant_text_color = false;
333   // Use hardcoded colors for inverted color scheme support and STYLE_BUTTON.
334   if (gfx::IsInvertedColorScheme()) {
335     constant_text_color = true;
336     colors[STATE_NORMAL] = SK_ColorWHITE;
337     label_->SetBackgroundColor(SK_ColorBLACK);
338     label_->set_background(Background::CreateSolidBackground(SK_ColorBLACK));
339     label_->SetAutoColorReadabilityEnabled(true);
340     label_->set_shadows(gfx::ShadowValues());
341   } else if (style() == STYLE_BUTTON) {
342     // TODO(erg): This is disabled on desktop linux because of the binary asset
343     // confusion. These details should either be pushed into ui::NativeThemeWin
344     // or should be obsoleted by rendering buttons with paint calls instead of
345     // with static assets. http://crbug.com/350498
346 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
347     constant_text_color = true;
348     colors[STATE_NORMAL] = kStyleButtonTextColor;
349     label_->SetBackgroundColor(theme->GetSystemColor(
350         ui::NativeTheme::kColorId_ButtonBackgroundColor));
351     label_->SetAutoColorReadabilityEnabled(false);
352     label_->set_shadows(gfx::ShadowValues(1,
353         gfx::ShadowValue(gfx::Point(0, 1), 0, kStyleButtonShadowColor)));
354 #endif
355     label_->set_background(NULL);
356   } else {
357     label_->set_background(NULL);
358   }
359
360   if (constant_text_color)
361     colors[STATE_HOVERED] = colors[STATE_PRESSED] = colors[STATE_NORMAL];
362
363   for (size_t state = STATE_NORMAL; state < STATE_COUNT; ++state) {
364     if (!explicitly_set_colors_[state]) {
365       SetTextColor(static_cast<ButtonState>(state), colors[state]);
366       explicitly_set_colors_[state] = false;
367     }
368   }
369 }
370
371 void LabelButton::UpdateImage() {
372   image_->SetImage(GetImage(state()));
373 }
374
375 void LabelButton::UpdateThemedBorder() {
376   // Don't override borders set by others.
377   if (!border_is_themed_border_)
378     return;
379
380   scoped_ptr<LabelButtonBorder> label_button_border = CreateDefaultBorder();
381
382 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
383   views::LinuxUI* linux_ui = views::LinuxUI::instance();
384   if (linux_ui) {
385     SetBorder(linux_ui->CreateNativeBorder(
386         this, label_button_border.Pass()));
387   } else
388 #endif
389   {
390     SetBorder(label_button_border.PassAs<Border>());
391   }
392
393   border_is_themed_border_ = true;
394 }
395
396 void LabelButton::StateChanged() {
397   const gfx::Size previous_image_size(image_->GetPreferredSize());
398   UpdateImage();
399   const SkColor color = button_state_colors_[state()];
400   if (state() != STATE_DISABLED && label_->enabled_color() != color)
401     label_->SetEnabledColor(color);
402   label_->SetEnabled(state() != STATE_DISABLED);
403   if (image_->GetPreferredSize() != previous_image_size)
404     Layout();
405 }
406
407 void LabelButton::ChildPreferredSizeChanged(View* child) {
408   PreferredSizeChanged();
409 }
410
411 void LabelButton::OnNativeThemeChanged(const ui::NativeTheme* theme) {
412   ResetColorsFromNativeTheme();
413   UpdateThemedBorder();
414   // Invalidate the layout to pickup the new insets from the border.
415   InvalidateLayout();
416 }
417
418 ui::NativeTheme::Part LabelButton::GetThemePart() const {
419   return ui::NativeTheme::kPushButton;
420 }
421
422 gfx::Rect LabelButton::GetThemePaintRect() const {
423   return GetLocalBounds();
424 }
425
426 ui::NativeTheme::State LabelButton::GetThemeState(
427     ui::NativeTheme::ExtraParams* params) const {
428   GetExtraParams(params);
429   switch (state()) {
430     case STATE_NORMAL:   return ui::NativeTheme::kNormal;
431     case STATE_HOVERED:  return ui::NativeTheme::kHovered;
432     case STATE_PRESSED:  return ui::NativeTheme::kPressed;
433     case STATE_DISABLED: return ui::NativeTheme::kDisabled;
434     case STATE_COUNT:    NOTREACHED() << "Unknown state: " << state();
435   }
436   return ui::NativeTheme::kNormal;
437 }
438
439 const gfx::Animation* LabelButton::GetThemeAnimation() const {
440   return hover_animation_.get();
441 }
442
443 ui::NativeTheme::State LabelButton::GetBackgroundThemeState(
444     ui::NativeTheme::ExtraParams* params) const {
445   GetExtraParams(params);
446   return ui::NativeTheme::kNormal;
447 }
448
449 ui::NativeTheme::State LabelButton::GetForegroundThemeState(
450     ui::NativeTheme::ExtraParams* params) const {
451   GetExtraParams(params);
452   return ui::NativeTheme::kHovered;
453 }
454
455 }  // namespace views