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