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/radio_button.h"
6
7#include "base/logging.h"
8#include "grit/ui_resources.h"
9#include "ui/base/accessibility/accessible_view_state.h"
10#include "ui/base/resource/resource_bundle.h"
11#include "ui/views/widget/widget.h"
12
13namespace views {
14
15// static
16const char RadioButton::kViewClassName[] = "RadioButton";
17
18RadioButton::RadioButton(const string16& label, int group_id)
19    : Checkbox(label) {
20  SetGroup(group_id);
21
22  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
23
24  // Unchecked/Unfocused images.
25  SetCustomImage(false, false, STATE_NORMAL,
26                 *rb.GetImageSkiaNamed(IDR_RADIO));
27  SetCustomImage(false, false, STATE_HOVERED,
28                 *rb.GetImageSkiaNamed(IDR_RADIO_HOVER));
29  SetCustomImage(false, false, STATE_PRESSED,
30                 *rb.GetImageSkiaNamed(IDR_RADIO_PRESSED));
31  SetCustomImage(false, false, STATE_DISABLED,
32                 *rb.GetImageSkiaNamed(IDR_RADIO_DISABLED));
33
34  // Checked/Unfocused images.
35  SetCustomImage(true, false, STATE_NORMAL,
36                 *rb.GetImageSkiaNamed(IDR_RADIO_CHECKED));
37  SetCustomImage(true, false, STATE_HOVERED,
38                 *rb.GetImageSkiaNamed(IDR_RADIO_CHECKED_HOVER));
39  SetCustomImage(true, false, STATE_PRESSED,
40                 *rb.GetImageSkiaNamed(IDR_RADIO_CHECKED_PRESSED));
41  SetCustomImage(true, false, STATE_DISABLED,
42                 *rb.GetImageSkiaNamed(IDR_RADIO_CHECKED_DISABLED));
43
44  // Unchecked/Focused images.
45  SetCustomImage(false, true, STATE_NORMAL,
46                 *rb.GetImageSkiaNamed(IDR_RADIO_FOCUSED));
47  SetCustomImage(false, true, STATE_HOVERED,
48                 *rb.GetImageSkiaNamed(IDR_RADIO_FOCUSED_HOVER));
49  SetCustomImage(false, true, STATE_PRESSED,
50                 *rb.GetImageSkiaNamed(IDR_RADIO_FOCUSED_PRESSED));
51
52  // Checked/Focused images.
53  SetCustomImage(true, true, STATE_NORMAL,
54                 *rb.GetImageSkiaNamed(IDR_RADIO_FOCUSED_CHECKED));
55  SetCustomImage(true, true, STATE_HOVERED,
56                 *rb.GetImageSkiaNamed(IDR_RADIO_FOCUSED_CHECKED_HOVER));
57  SetCustomImage(true, true, STATE_PRESSED,
58                 *rb.GetImageSkiaNamed(IDR_RADIO_FOCUSED_CHECKED_PRESSED));
59}
60
61RadioButton::~RadioButton() {
62}
63
64void RadioButton::SetChecked(bool checked) {
65  if (checked == RadioButton::checked())
66    return;
67  if (checked) {
68    // We can't just get the root view here because sometimes the radio
69    // button isn't attached to a root view (e.g., if it's part of a tab page
70    // that is currently not active).
71    View* container = parent();
72    while (container && container->parent())
73      container = container->parent();
74    if (container) {
75      Views other;
76      container->GetViewsInGroup(GetGroup(), &other);
77      for (Views::iterator i(other.begin()); i != other.end(); ++i) {
78        if (*i != this) {
79          if (strcmp((*i)->GetClassName(), kViewClassName)) {
80            NOTREACHED() << "radio-button-nt has same group as other non "
81                            "radio-button-nt views.";
82            continue;
83          }
84          RadioButton* peer = static_cast<RadioButton*>(*i);
85          peer->SetChecked(false);
86        }
87      }
88    }
89  }
90  Checkbox::SetChecked(checked);
91}
92
93const char* RadioButton::GetClassName() const {
94  return kViewClassName;
95}
96
97void RadioButton::GetAccessibleState(ui::AccessibleViewState* state) {
98  Checkbox::GetAccessibleState(state);
99  state->role = ui::AccessibilityTypes::ROLE_RADIOBUTTON;
100}
101
102View* RadioButton::GetSelectedViewForGroup(int group) {
103  Views views;
104  GetWidget()->GetRootView()->GetViewsInGroup(group, &views);
105  if (views.empty())
106    return NULL;
107
108  for (Views::const_iterator i(views.begin()); i != views.end(); ++i) {
109    // REVIEW: why don't we check the runtime type like is done above?
110    RadioButton* radio_button = static_cast<RadioButton*>(*i);
111    if (radio_button->checked())
112      return radio_button;
113  }
114  return NULL;
115}
116
117bool RadioButton::IsGroupFocusTraversable() const {
118  // When focusing a radio button with tab/shift+tab, only the selected button
119  // from the group should be focused.
120  return false;
121}
122
123void RadioButton::OnFocus() {
124  Checkbox::OnFocus();
125  SetChecked(true);
126  ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), 0);
127  LabelButton::NotifyClick(event);
128}
129
130void RadioButton::NotifyClick(const ui::Event& event) {
131  // Set the checked state to true only if we are unchecked, since we can't
132  // be toggled on and off like a checkbox.
133  if (!checked())
134    SetChecked(true);
135  RequestFocus();
136  LabelButton::NotifyClick(event);
137}
138
139ui::NativeTheme::Part RadioButton::GetThemePart() const {
140  return ui::NativeTheme::kRadio;
141}
142
143}  // namespace views
144