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