accessible_pane_view.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
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
90void AccessiblePaneView::RemovePaneFocus() {
91  focus_manager_->RemoveFocusChangeListener(this);
92  pane_has_focus_ = false;
93
94  focus_manager_->UnregisterAccelerator(home_key_, this);
95  focus_manager_->UnregisterAccelerator(end_key_, this);
96  focus_manager_->UnregisterAccelerator(escape_key_, this);
97  focus_manager_->UnregisterAccelerator(left_key_, this);
98  focus_manager_->UnregisterAccelerator(right_key_, this);
99}
100
101void AccessiblePaneView::LocationBarSelectAll() {
102  views::View* focused_view = GetFocusManager()->GetFocusedView();
103  if (focused_view &&
104      focused_view->GetClassName() == LocationBarView::kViewClassName) {
105    static_cast<LocationBarView*>(focused_view)->SelectAll();
106  }
107}
108
109void AccessiblePaneView::RestoreLastFocusedView() {
110  views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
111  views::View* last_focused_view =
112      view_storage->RetrieveView(last_focused_view_storage_id_);
113  if (last_focused_view) {
114    focus_manager_->SetFocusedViewWithReason(
115        last_focused_view, views::FocusManager::kReasonFocusRestore);
116  } else {
117    // Focus the location bar
118    views::View* view = GetAncestorWithClassName(BrowserView::kViewClassName);
119    if (view) {
120      BrowserView* browser_view = static_cast<BrowserView*>(view);
121      browser_view->SetFocusToLocationBar(false);
122    }
123  }
124}
125
126views::View* AccessiblePaneView::GetFirstFocusableChild() {
127  FocusTraversable* dummy_focus_traversable;
128  views::View* dummy_focus_traversable_view;
129  return focus_search_->FindNextFocusableView(
130      NULL, false, views::FocusSearch::DOWN, false,
131      &dummy_focus_traversable, &dummy_focus_traversable_view);
132}
133
134views::View* AccessiblePaneView::GetLastFocusableChild() {
135  FocusTraversable* dummy_focus_traversable;
136  views::View* dummy_focus_traversable_view;
137  return focus_search_->FindNextFocusableView(
138      this, true, views::FocusSearch::DOWN, false,
139      &dummy_focus_traversable, &dummy_focus_traversable_view);
140}
141
142////////////////////////////////////////////////////////////////////////////////
143// View overrides:
144
145views::FocusTraversable* AccessiblePaneView::GetPaneFocusTraversable() {
146  if (pane_has_focus_)
147    return this;
148  else
149    return NULL;
150}
151
152bool AccessiblePaneView::AcceleratorPressed(
153    const views::Accelerator& accelerator) {
154  // Special case: don't handle any accelerators for the location bar,
155  // so that it behaves exactly the same whether you focus it with Ctrl+L
156  // or F6 or Alt+D or Alt+Shift+T.
157  views::View* focused_view = focus_manager_->GetFocusedView();
158  if ((focused_view->GetClassName() == LocationBarView::kViewClassName ||
159       focused_view->GetClassName() == views::NativeViewHost::kViewClassName)) {
160    return false;
161  }
162
163  switch (accelerator.GetKeyCode()) {
164    case ui::VKEY_ESCAPE:
165      RemovePaneFocus();
166      RestoreLastFocusedView();
167      return true;
168    case ui::VKEY_LEFT:
169      focus_manager_->AdvanceFocus(true);
170      return true;
171    case ui::VKEY_RIGHT:
172      focus_manager_->AdvanceFocus(false);
173      return true;
174    case ui::VKEY_HOME:
175      focus_manager_->SetFocusedViewWithReason(
176          GetFirstFocusableChild(), views::FocusManager::kReasonFocusTraversal);
177      return true;
178    case ui::VKEY_END:
179      focus_manager_->SetFocusedViewWithReason(
180          GetLastFocusableChild(), views::FocusManager::kReasonFocusTraversal);
181      return true;
182    default:
183      return false;
184  }
185}
186
187void AccessiblePaneView::SetVisible(bool flag) {
188  if (IsVisible() && !flag && pane_has_focus_) {
189    RemovePaneFocus();
190    RestoreLastFocusedView();
191  }
192  View::SetVisible(flag);
193}
194
195AccessibilityTypes::Role AccessiblePaneView::GetAccessibleRole() {
196  return AccessibilityTypes::ROLE_PANE;
197}
198
199////////////////////////////////////////////////////////////////////////////////
200// FocusChangeListener overrides:
201
202void AccessiblePaneView::FocusWillChange(views::View* focused_before,
203                                         views::View* focused_now) {
204  if (!focused_now)
205    return;
206
207  views::FocusManager::FocusChangeReason reason =
208      focus_manager_->focus_change_reason();
209
210  if (focused_now->GetClassName() == LocationBarView::kViewClassName &&
211      reason == views::FocusManager::kReasonFocusTraversal) {
212    // Tabbing to the location bar should select all. Defer so that it happens
213    // after the focus.
214    MessageLoop::current()->PostTask(
215        FROM_HERE, method_factory_.NewRunnableMethod(
216            &AccessiblePaneView::LocationBarSelectAll));
217  }
218
219  if (!Contains(focused_now) ||
220      reason == views::FocusManager::kReasonDirectFocusChange) {
221    // We should remove pane focus (i.e. make most of the controls
222    // not focusable again) either because the focus is leaving the pane,
223    // or because the focus changed within the pane due to the user
224    // directly focusing to a specific view (e.g., clicking on it).
225    //
226    // Defer rather than calling RemovePaneFocus right away, because we can't
227    // remove |this| as a focus change listener while FocusManager is in the
228    // middle of iterating over the list of listeners.
229    MessageLoop::current()->PostTask(
230        FROM_HERE, method_factory_.NewRunnableMethod(
231            &AccessiblePaneView::RemovePaneFocus));
232  }
233}
234
235////////////////////////////////////////////////////////////////////////////////
236// FocusTraversable overrides:
237
238views::FocusSearch* AccessiblePaneView::GetFocusSearch() {
239  DCHECK(pane_has_focus_);
240  return focus_search_.get();
241}
242
243views::FocusTraversable* AccessiblePaneView::GetFocusTraversableParent() {
244  DCHECK(pane_has_focus_);
245  return NULL;
246}
247
248views::View* AccessiblePaneView::GetFocusTraversableParentView() {
249  DCHECK(pane_has_focus_);
250  return NULL;
251}
252