// Copyright (c) 2010 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/view_ids.h" #include "chrome/browser/views/frame/browser_view.h" #include "chrome/browser/views/location_bar/location_bar_view.h" #include "chrome/browser/views/accessible_toolbar_view.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" AccessibleToolbarView::AccessibleToolbarView() : toolbar_has_focus_(false), ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), focus_manager_(NULL), ALLOW_THIS_IN_INITIALIZER_LIST(focus_search_(this, true, true)), home_key_(base::VKEY_HOME, false, false, false), end_key_(base::VKEY_END, false, false, false), escape_key_(base::VKEY_ESCAPE, false, false, false), left_key_(base::VKEY_LEFT, false, false, false), right_key_(base::VKEY_RIGHT, false, false, false), last_focused_view_storage_id_(-1) { } AccessibleToolbarView::~AccessibleToolbarView() { if (toolbar_has_focus_) { focus_manager_->RemoveFocusChangeListener(this); } } bool AccessibleToolbarView::SetToolbarFocus(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 || !IsParentOf(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 focus_manager_->SetFocusedView(initial_focus); // If we already have toolbar focus, we're done. if (toolbar_has_focus_) return true; // Otherwise, set accelerators and start listening for focus change events. toolbar_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 AccessibleToolbarView::SetToolbarFocusAndFocusDefault( int view_storage_id) { return SetToolbarFocus(view_storage_id, GetDefaultFocusableChild()); } void AccessibleToolbarView::RemoveToolbarFocus() { focus_manager_->RemoveFocusChangeListener(this); toolbar_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 AccessibleToolbarView::RemoveToolbarFocusIfNoChildHasFocus() { views::View* focused_view = focus_manager_->GetFocusedView(); if (toolbar_has_focus_ && (!focused_view || !IsParentOf(focused_view))) RemoveToolbarFocus(); } void AccessibleToolbarView::RestoreLastFocusedView() { views::ViewStorage* view_storage = views::ViewStorage::GetSharedInstance(); views::View* last_focused_view = view_storage->RetrieveView(last_focused_view_storage_id_); if (last_focused_view) { focus_manager_->SetFocusedView(last_focused_view); } else { // Focus the location bar views::View* view = GetAncestorWithClassName(BrowserView::kViewClassName); if (view) { BrowserView* browser_view = static_cast(view); browser_view->SetFocusToLocationBar(false); } } } views::View* AccessibleToolbarView::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* AccessibleToolbarView::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* AccessibleToolbarView::GetPaneFocusTraversable() { if (toolbar_has_focus_) return this; else return NULL; } bool AccessibleToolbarView::AcceleratorPressed( const views::Accelerator& accelerator) { // Special case: don't handle arrows for certain views, like the // location bar's edit text view, which need them for text editing. views::View* focused_view = focus_manager_->GetFocusedView(); if ((focused_view->GetClassName() == LocationBarView::kViewClassName || focused_view->GetClassName() == views::NativeViewHost::kViewClassName) && (accelerator.GetKeyCode() == base::VKEY_LEFT || accelerator.GetKeyCode() == base::VKEY_RIGHT)) { return false; } switch (accelerator.GetKeyCode()) { case base::VKEY_ESCAPE: RemoveToolbarFocus(); RestoreLastFocusedView(); return true; case base::VKEY_LEFT: focus_manager_->AdvanceFocus(true); return true; case base::VKEY_RIGHT: focus_manager_->AdvanceFocus(false); return true; case base::VKEY_HOME: focus_manager_->SetFocusedView(GetFirstFocusableChild()); return true; case base::VKEY_END: focus_manager_->SetFocusedView(GetLastFocusableChild()); return true; default: return false; } } void AccessibleToolbarView::SetVisible(bool flag) { if (IsVisible() && !flag && toolbar_has_focus_) { RemoveToolbarFocus(); RestoreLastFocusedView(); } View::SetVisible(flag); } bool AccessibleToolbarView::GetAccessibleRole(AccessibilityTypes::Role* role) { DCHECK(role); *role = AccessibilityTypes::ROLE_TOOLBAR; return true; } //////////////////////////////////////////////////////////////////////////////// // FocusChangeListener overrides: void AccessibleToolbarView::FocusWillChange(views::View* focused_before, views::View* focused_now) { if (!focused_now || !IsParentOf(focused_now)) { // The focus is no longer in the toolbar, so we should remove toolbar // focus (i.e. make most of the controls not focusable again). // Defer this rather than running it right away, for two reasons: // 1. Sometimes the focus gets sets to NULL and then immediately back // to something in this toolbar. We don't want to do anything in // that case. // 2. If we do want to remove toolbar focus, 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( &AccessibleToolbarView::RemoveToolbarFocusIfNoChildHasFocus)); } } //////////////////////////////////////////////////////////////////////////////// // FocusTraversable overrides: views::FocusSearch* AccessibleToolbarView::GetFocusSearch() { DCHECK(toolbar_has_focus_); return &focus_search_; } views::FocusTraversable* AccessibleToolbarView::GetFocusTraversableParent() { DCHECK(toolbar_has_focus_); return NULL; } views::View* AccessibleToolbarView::GetFocusTraversableParentView() { DCHECK(toolbar_has_focus_); return NULL; }