accessible_pane_view.cc revision dc0f95d653279beabeb9817299e2902918ba123e
1// Copyright (c) 2011 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 "base/logging.h"
6#include "chrome/browser/ui/view_ids.h"
7#include "chrome/browser/ui/views/accessible_pane_view.h"
8#include "chrome/browser/ui/views/frame/browser_view.h"
9#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
10#include "views/controls/button/menu_button.h"
11#include "views/controls/native/native_view_host.h"
12#include "views/focus/focus_search.h"
13#include "views/focus/view_storage.h"
14#include "views/widget/tooltip_manager.h"
15#include "views/widget/widget.h"
16
17AccessiblePaneView::AccessiblePaneView()
18    : pane_has_focus_(false),
19      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
20      focus_manager_(NULL),
21      home_key_(ui::VKEY_HOME, false, false, false),
22      end_key_(ui::VKEY_END, false, false, false),
23      escape_key_(ui::VKEY_ESCAPE, false, false, false),
24      left_key_(ui::VKEY_LEFT, false, false, false),
25      right_key_(ui::VKEY_RIGHT, false, false, false),
26      last_focused_view_storage_id_(-1) {
27  focus_search_.reset(new views::FocusSearch(this, true, true));
28}
29
30AccessiblePaneView::~AccessiblePaneView() {
31  if (pane_has_focus_) {
32    focus_manager_->RemoveFocusChangeListener(this);
33  }
34}
35
36bool AccessiblePaneView::SetPaneFocus(int view_storage_id,
37                                      views::View* initial_focus) {
38  if (!IsVisible())
39    return false;
40
41  // Save the storage id to the last focused view. This would be used to request
42  // focus to the view when the traversal is ended.
43  last_focused_view_storage_id_ = view_storage_id;
44
45  if (!focus_manager_)
46    focus_manager_ = GetFocusManager();
47
48  // Use the provided initial focus if it's visible and enabled, otherwise
49  // use the first focusable child.
50  if (!initial_focus ||
51      !Contains(initial_focus) ||
52      !initial_focus->IsVisible() ||
53      !initial_focus->IsEnabled()) {
54    initial_focus = GetFirstFocusableChild();
55  }
56
57  // Return false if there are no focusable children.
58  if (!initial_focus)
59    return false;
60
61  // Set focus to the initial view. If it's a location bar, use a special
62  // method that tells it to select all, also.
63  if (initial_focus->GetClassName() == LocationBarView::kViewClassName) {
64    static_cast<LocationBarView*>(initial_focus)->FocusLocation(true);
65  } else {
66    focus_manager_->SetFocusedView(initial_focus);
67  }
68
69  // If we already have pane focus, we're done.
70  if (pane_has_focus_)
71    return true;
72
73  // Otherwise, set accelerators and start listening for focus change events.
74  pane_has_focus_ = true;
75  focus_manager_->RegisterAccelerator(home_key_, this);
76  focus_manager_->RegisterAccelerator(end_key_, this);
77  focus_manager_->RegisterAccelerator(escape_key_, this);
78  focus_manager_->RegisterAccelerator(left_key_, this);
79  focus_manager_->RegisterAccelerator(right_key_, this);
80  focus_manager_->AddFocusChangeListener(this);
81
82  return true;
83}
84
85bool AccessiblePaneView::SetPaneFocusAndFocusDefault(
86    int view_storage_id) {
87  return SetPaneFocus(view_storage_id, GetDefaultFocusableChild());
88}
89
90views::View* AccessiblePaneView::GetDefaultFocusableChild() {
91  return NULL;
92}
93
94void AccessiblePaneView::RemovePaneFocus() {
95  focus_manager_->RemoveFocusChangeListener(this);
96  pane_has_focus_ = false;
97
98  focus_manager_->UnregisterAccelerator(home_key_, this);
99  focus_manager_->UnregisterAccelerator(end_key_, this);
100  focus_manager_->UnregisterAccelerator(escape_key_, this);
101  focus_manager_->UnregisterAccelerator(left_key_, this);
102  focus_manager_->UnregisterAccelerator(right_key_, this);
103}
104
105void AccessiblePaneView::LocationBarSelectAll() {
106  views::View* focused_view = GetFocusManager()->GetFocusedView();
107  if (focused_view &&
108      focused_view->GetClassName() == LocationBarView::kViewClassName) {
109    static_cast<LocationBarView*>(focused_view)->SelectAll();
110  }
111}
112
113void AccessiblePaneView::RestoreLastFocusedView() {
114  views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
115  views::View* last_focused_view =
116      view_storage->RetrieveView(last_focused_view_storage_id_);
117  if (last_focused_view) {
118    focus_manager_->SetFocusedViewWithReason(
119        last_focused_view, views::FocusManager::kReasonFocusRestore);
120  } else {
121    // Focus the location bar
122    views::View* view = GetAncestorWithClassName(BrowserView::kViewClassName);
123    if (view) {
124      BrowserView* browser_view = static_cast<BrowserView*>(view);
125      browser_view->SetFocusToLocationBar(false);
126    }
127  }
128}
129
130views::View* AccessiblePaneView::GetFirstFocusableChild() {
131  FocusTraversable* dummy_focus_traversable;
132  views::View* dummy_focus_traversable_view;
133  return focus_search_->FindNextFocusableView(
134      NULL, false, views::FocusSearch::DOWN, false,
135      &dummy_focus_traversable, &dummy_focus_traversable_view);
136}
137
138views::View* AccessiblePaneView::GetLastFocusableChild() {
139  FocusTraversable* dummy_focus_traversable;
140  views::View* dummy_focus_traversable_view;
141  return focus_search_->FindNextFocusableView(
142      this, true, views::FocusSearch::DOWN, false,
143      &dummy_focus_traversable, &dummy_focus_traversable_view);
144}
145
146////////////////////////////////////////////////////////////////////////////////
147// View overrides:
148
149views::FocusTraversable* AccessiblePaneView::GetPaneFocusTraversable() {
150  if (pane_has_focus_)
151    return this;
152  else
153    return NULL;
154}
155
156bool AccessiblePaneView::AcceleratorPressed(
157    const views::Accelerator& accelerator) {
158  // Special case: don't handle any accelerators for the location bar,
159  // so that it behaves exactly the same whether you focus it with Ctrl+L
160  // or F6 or Alt+D or Alt+Shift+T.
161  views::View* focused_view = focus_manager_->GetFocusedView();
162  if ((focused_view->GetClassName() == LocationBarView::kViewClassName ||
163       focused_view->GetClassName() == views::NativeViewHost::kViewClassName)) {
164    return false;
165  }
166
167  switch (accelerator.GetKeyCode()) {
168    case ui::VKEY_ESCAPE:
169      RemovePaneFocus();
170      RestoreLastFocusedView();
171      return true;
172    case ui::VKEY_LEFT:
173      focus_manager_->AdvanceFocus(true);
174      return true;
175    case ui::VKEY_RIGHT:
176      focus_manager_->AdvanceFocus(false);
177      return true;
178    case ui::VKEY_HOME:
179      focus_manager_->SetFocusedViewWithReason(
180          GetFirstFocusableChild(), views::FocusManager::kReasonFocusTraversal);
181      return true;
182    case ui::VKEY_END:
183      focus_manager_->SetFocusedViewWithReason(
184          GetLastFocusableChild(), views::FocusManager::kReasonFocusTraversal);
185      return true;
186    default:
187      return false;
188  }
189}
190
191void AccessiblePaneView::SetVisible(bool flag) {
192  if (IsVisible() && !flag && pane_has_focus_) {
193    RemovePaneFocus();
194    RestoreLastFocusedView();
195  }
196  View::SetVisible(flag);
197}
198
199AccessibilityTypes::Role AccessiblePaneView::GetAccessibleRole() {
200  return AccessibilityTypes::ROLE_PANE;
201}
202
203////////////////////////////////////////////////////////////////////////////////
204// FocusChangeListener overrides:
205
206void AccessiblePaneView::FocusWillChange(views::View* focused_before,
207                                         views::View* focused_now) {
208  if (!focused_now)
209    return;
210
211  views::FocusManager::FocusChangeReason reason =
212      focus_manager_->focus_change_reason();
213
214  if (focused_now->GetClassName() == LocationBarView::kViewClassName &&
215      reason == views::FocusManager::kReasonFocusTraversal) {
216    // Tabbing to the location bar should select all. Defer so that it happens
217    // after the focus.
218    MessageLoop::current()->PostTask(
219        FROM_HERE, method_factory_.NewRunnableMethod(
220            &AccessiblePaneView::LocationBarSelectAll));
221  }
222
223  if (!Contains(focused_now) ||
224      reason == views::FocusManager::kReasonDirectFocusChange) {
225    // We should remove pane focus (i.e. make most of the controls
226    // not focusable again) either because the focus is leaving the pane,
227    // or because the focus changed within the pane due to the user
228    // directly focusing to a specific view (e.g., clicking on it).
229    //
230    // Defer rather than calling RemovePaneFocus right away, because we can't
231    // remove |this| as a focus change listener while FocusManager is in the
232    // middle of iterating over the list of listeners.
233    MessageLoop::current()->PostTask(
234        FROM_HERE, method_factory_.NewRunnableMethod(
235            &AccessiblePaneView::RemovePaneFocus));
236  }
237}
238
239////////////////////////////////////////////////////////////////////////////////
240// FocusTraversable overrides:
241
242views::FocusSearch* AccessiblePaneView::GetFocusSearch() {
243  DCHECK(pane_has_focus_);
244  return focus_search_.get();
245}
246
247views::FocusTraversable* AccessiblePaneView::GetFocusTraversableParent() {
248  DCHECK(pane_has_focus_);
249  return NULL;
250}
251
252views::View* AccessiblePaneView::GetFocusTraversableParentView() {
253  DCHECK(pane_has_focus_);
254  return NULL;
255}
256