// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/logging.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/accessible_pane_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "ui/base/accessibility/accessible_view_state.h"
#include "views/controls/button/menu_button.h"
#include "views/controls/native/native_view_host.h"
#include "views/focus/focus_search.h"
#include "views/focus/view_storage.h"
#include "views/widget/tooltip_manager.h"
#include "views/widget/widget.h"
AccessiblePaneView::AccessiblePaneView()
: pane_has_focus_(false),
ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
focus_manager_(NULL),
home_key_(ui::VKEY_HOME, false, false, false),
end_key_(ui::VKEY_END, false, false, false),
escape_key_(ui::VKEY_ESCAPE, false, false, false),
left_key_(ui::VKEY_LEFT, false, false, false),
right_key_(ui::VKEY_RIGHT, false, false, false),
last_focused_view_storage_id_(-1) {
focus_search_.reset(new views::FocusSearch(this, true, true));
}
AccessiblePaneView::~AccessiblePaneView() {
if (pane_has_focus_) {
focus_manager_->RemoveFocusChangeListener(this);
}
}
bool AccessiblePaneView::SetPaneFocus(int view_storage_id,
views::View* initial_focus) {
if (!IsVisible())
return false;
// Save the storage id to the last focused view. This would be used to request
// focus to the view when the traversal is ended.
last_focused_view_storage_id_ = view_storage_id;
if (!focus_manager_)
focus_manager_ = GetFocusManager();
// Use the provided initial focus if it's visible and enabled, otherwise
// use the first focusable child.
if (!initial_focus ||
!Contains(initial_focus) ||
!initial_focus->IsVisible() ||
!initial_focus->IsEnabled()) {
initial_focus = GetFirstFocusableChild();
}
// Return false if there are no focusable children.
if (!initial_focus)
return false;
// Set focus to the initial view. If it's a location bar, use a special
// method that tells it to select all, also.
if (initial_focus->GetClassName() == LocationBarView::kViewClassName) {
static_cast<LocationBarView*>(initial_focus)->FocusLocation(true);
} else {
focus_manager_->SetFocusedView(initial_focus);
}
// If we already have pane focus, we're done.
if (pane_has_focus_)
return true;
// Otherwise, set accelerators and start listening for focus change events.
pane_has_focus_ = true;
focus_manager_->RegisterAccelerator(home_key_, this);
focus_manager_->RegisterAccelerator(end_key_, this);
focus_manager_->RegisterAccelerator(escape_key_, this);
focus_manager_->RegisterAccelerator(left_key_, this);
focus_manager_->RegisterAccelerator(right_key_, this);
focus_manager_->AddFocusChangeListener(this);
return true;
}
bool AccessiblePaneView::SetPaneFocusAndFocusDefault(
int view_storage_id) {
return SetPaneFocus(view_storage_id, GetDefaultFocusableChild());
}
views::View* AccessiblePaneView::GetDefaultFocusableChild() {
return NULL;
}
void AccessiblePaneView::RemovePaneFocus() {
focus_manager_->RemoveFocusChangeListener(this);
pane_has_focus_ = false;
focus_manager_->UnregisterAccelerator(home_key_, this);
focus_manager_->UnregisterAccelerator(end_key_, this);
focus_manager_->UnregisterAccelerator(escape_key_, this);
focus_manager_->UnregisterAccelerator(left_key_, this);
focus_manager_->UnregisterAccelerator(right_key_, this);
}
void AccessiblePaneView::LocationBarSelectAll() {
views::View* focused_view = GetFocusManager()->GetFocusedView();
if (focused_view &&
focused_view->GetClassName() == LocationBarView::kViewClassName) {
static_cast<LocationBarView*>(focused_view)->SelectAll();
}
}
void AccessiblePaneView::RestoreLastFocusedView() {
views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
views::View* last_focused_view =
view_storage->RetrieveView(last_focused_view_storage_id_);
if (last_focused_view) {
focus_manager_->SetFocusedViewWithReason(
last_focused_view, views::FocusManager::kReasonFocusRestore);
} else {
// Focus the location bar
views::View* view = GetAncestorWithClassName(BrowserView::kViewClassName);
if (view) {
BrowserView* browser_view = static_cast<BrowserView*>(view);
browser_view->SetFocusToLocationBar(false);
}
}
}
views::View* AccessiblePaneView::GetFirstFocusableChild() {
FocusTraversable* dummy_focus_traversable;
views::View* dummy_focus_traversable_view;
return focus_search_->FindNextFocusableView(
NULL, false, views::FocusSearch::DOWN, false,
&dummy_focus_traversable, &dummy_focus_traversable_view);
}
views::View* AccessiblePaneView::GetLastFocusableChild() {
FocusTraversable* dummy_focus_traversable;
views::View* dummy_focus_traversable_view;
return focus_search_->FindNextFocusableView(
this, true, views::FocusSearch::DOWN, false,
&dummy_focus_traversable, &dummy_focus_traversable_view);
}
////////////////////////////////////////////////////////////////////////////////
// View overrides:
views::FocusTraversable* AccessiblePaneView::GetPaneFocusTraversable() {
if (pane_has_focus_)
return this;
else
return NULL;
}
bool AccessiblePaneView::AcceleratorPressed(
const views::Accelerator& accelerator) {
// Special case: don't handle any accelerators for the location bar,
// so that it behaves exactly the same whether you focus it with Ctrl+L
// or F6 or Alt+D or Alt+Shift+T.
views::View* focused_view = focus_manager_->GetFocusedView();
if ((focused_view->GetClassName() == LocationBarView::kViewClassName ||
focused_view->GetClassName() == views::NativeViewHost::kViewClassName)) {
return false;
}
switch (accelerator.GetKeyCode()) {
case ui::VKEY_ESCAPE:
RemovePaneFocus();
RestoreLastFocusedView();
return true;
case ui::VKEY_LEFT:
focus_manager_->AdvanceFocus(true);
return true;
case ui::VKEY_RIGHT:
focus_manager_->AdvanceFocus(false);
return true;
case ui::VKEY_HOME:
focus_manager_->SetFocusedViewWithReason(
GetFirstFocusableChild(), views::FocusManager::kReasonFocusTraversal);
return true;
case ui::VKEY_END:
focus_manager_->SetFocusedViewWithReason(
GetLastFocusableChild(), views::FocusManager::kReasonFocusTraversal);
return true;
default:
return false;
}
}
void AccessiblePaneView::SetVisible(bool flag) {
if (IsVisible() && !flag && pane_has_focus_) {
RemovePaneFocus();
RestoreLastFocusedView();
}
View::SetVisible(flag);
}
void AccessiblePaneView::GetAccessibleState(ui::AccessibleViewState* state) {
state->role = ui::AccessibilityTypes::ROLE_PANE;
}
////////////////////////////////////////////////////////////////////////////////
// FocusChangeListener overrides:
void AccessiblePaneView::FocusWillChange(views::View* focused_before,
views::View* focused_now) {
if (!focused_now)
return;
views::FocusManager::FocusChangeReason reason =
focus_manager_->focus_change_reason();
if (focused_now->GetClassName() == LocationBarView::kViewClassName &&
reason == views::FocusManager::kReasonFocusTraversal) {
// Tabbing to the location bar should select all. Defer so that it happens
// after the focus.
MessageLoop::current()->PostTask(
FROM_HERE, method_factory_.NewRunnableMethod(
&AccessiblePaneView::LocationBarSelectAll));
}
if (!Contains(focused_now) ||
reason == views::FocusManager::kReasonDirectFocusChange) {
// We should remove pane focus (i.e. make most of the controls
// not focusable again) either because the focus is leaving the pane,
// or because the focus changed within the pane due to the user
// directly focusing to a specific view (e.g., clicking on it).
//
// Defer rather than calling RemovePaneFocus right away, because we can't
// remove |this| as a focus change listener while FocusManager is in the
// middle of iterating over the list of listeners.
MessageLoop::current()->PostTask(
FROM_HERE, method_factory_.NewRunnableMethod(
&AccessiblePaneView::RemovePaneFocus));
}
}
////////////////////////////////////////////////////////////////////////////////
// FocusTraversable overrides:
views::FocusSearch* AccessiblePaneView::GetFocusSearch() {
DCHECK(pane_has_focus_);
return focus_search_.get();
}
views::FocusTraversable* AccessiblePaneView::GetFocusTraversableParent() {
DCHECK(pane_has_focus_);
return NULL;
}
views::View* AccessiblePaneView::GetFocusTraversableParentView() {
DCHECK(pane_has_focus_);
return NULL;
}