#include "ui/views/focus/focus_manager.h"
#include <algorithm>
#include <utility>
#include <vector>
#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/i18n/rtl.h"
#include "base/observer_list.h"
#include "build/build_config.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/focus/focus_manager_delegate.h"
#include "ui/views/focus/focus_search.h"
#include "ui/views/focus/native_view_focus_manager.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_tracker.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
namespace views {
FocusManager::FocusManager(Widget* widget,
std::unique_ptr<FocusManagerDelegate> delegate)
: widget_(widget),
delegate_(std::move(delegate)),
view_tracker_for_stored_view_(std::make_unique<ViewTracker>()) {
DCHECK(widget_);
}
FocusManager::~FocusManager() {
if (focused_view_) {
focused_view_->RemoveObserver(this);
}
focus_change_listeners_.Notify(&FocusChangeListener::OnFocusManagerDestroying,
this);
}
bool FocusManager::OnKeyEvent(const ui::KeyEvent& event) {
const ui::KeyboardCode key_code = event.key_code();
if (event.type() != ui::EventType::kKeyPressed &&
event.type() != ui::EventType::kKeyReleased) {
return false;
}
if (shortcut_handling_suspended()) {
return true;
}
ui::Accelerator accelerator(event);
if (focused_view_ && focused_view_->SkipDefaultKeyEventProcessing(event) &&
!accelerator_manager_.HasPriorityHandler(accelerator)) {
return true;
}
if (event.type() == ui::EventType::kKeyPressed) {
if (IsTabTraversalKeyEvent(event)) {
AdvanceFocus(event.IsShiftDown());
return false;
}
if (IsArrowKeyTraversalEnabledForWidget() &&
ProcessArrowKeyTraversal(event)) {
return false;
}
bool is_left = key_code == ui::VKEY_LEFT || key_code == ui::VKEY_UP;
bool is_right = key_code == ui::VKEY_RIGHT || key_code == ui::VKEY_DOWN;
if (focused_view_ && focused_view_->GetGroup() != -1 &&
(is_left || is_right)) {
bool next = is_right;
View::Views views;
View* group_owner = focused_view_->parent();
for (View* potential_owner = focused_view_->parent();
potential_owner != nullptr;
potential_owner = potential_owner->parent()) {
if (potential_owner->GetOwnedGroup() == focused_view_->GetGroup()) {
group_owner = potential_owner;
break;
}
}
group_owner->GetViewsInGroup(focused_view_->GetGroup(), &views);
std::erase_if(views, [this](View* v) {
return v != focused_view_ &&
!v->GetViewAccessibility().IsAccessibilityFocusable();
});
View::Views::const_iterator i = std::ranges::find(views, focused_view_);
DCHECK(i != views.end());
auto index = static_cast<size_t>(i - views.begin());
if (next && index == views.size() - 1) {
index = 0;
} else if (!next && index == 0) {
index = views.size() - 1;
} else {
index = next ? (index + 1) : (index - 1);
}
SetFocusedViewWithReason(views[index],
FocusChangeReason::kFocusTraversal);
return false;
}
}
if (ProcessAccelerator(accelerator)) {
return false;
}
return true;
}
bool FocusManager::ContainsView(View* view) {
Widget* widget = view->GetWidget();
return widget && widget->GetFocusManager() == this;
}
void FocusManager::AdvanceFocus(bool reverse) {
View* v = GetNextFocusableView(focused_view_, nullptr, reverse, false);
if (v) {
views::View* focused_view = focused_view_;
v->AboutToRequestFocusFromTabTraversal(reverse);
if (focused_view != focused_view_) {
return;
}
DCHECK(v->GetWidget());
v->GetWidget()->GetFocusManager()->SetFocusedViewWithReason(
v, FocusChangeReason::kFocusTraversal);
if (v->GetWidget()->GetFocusManager() != this) {
v->GetWidget()->Activate();
}
}
}
void FocusManager::ClearNativeFocus() {
widget_->ClearNativeFocus();
}
bool FocusManager::RotatePaneFocus(Direction direction,
FocusCycleWrapping wrapping) {
return widget_->widget_delegate()->RotatePaneFocusFromView(
GetFocusedView(), direction == Direction::kForward,
wrapping == FocusCycleWrapping::kEnabled);
}
View* FocusManager::GetNextFocusableView(View* original_starting_view,
Widget* starting_widget,
bool reverse,
bool dont_loop) {
DCHECK(!focused_view_ || ContainsView(focused_view_))
<< " focus_view=" << focused_view_;
FocusTraversable* focus_traversable = nullptr;
View* starting_view = nullptr;
if (original_starting_view) {
View* pane_search = original_starting_view;
while (pane_search) {
focus_traversable = pane_search->GetPaneFocusTraversable();
if (focus_traversable) {
starting_view = original_starting_view;
break;
}
pane_search = pane_search->parent();
}
if (!focus_traversable) {
if (!reverse) {
focus_traversable = original_starting_view->GetFocusTraversable();
if (!focus_traversable) {
focus_traversable =
original_starting_view->GetWidget()->GetFocusTraversable();
starting_view = original_starting_view;
}
} else {
focus_traversable =
original_starting_view->GetWidget()->GetFocusTraversable();
starting_view = original_starting_view;
}
}
} else {
Widget* widget = starting_widget ? starting_widget : widget_.get();
focus_traversable = widget->GetFocusTraversable();
}
View* v = FindFocusableView(focus_traversable, starting_view, reverse);
if (v) {
return v;
}
FocusTraversable* parent_focus_traversable =
focus_traversable->GetFocusTraversableParent();
starting_view = focus_traversable->GetFocusTraversableParentView();
while (parent_focus_traversable) {
FocusTraversable* new_focus_traversable = nullptr;
View* new_starting_view = nullptr;
auto check_starting_view =
reverse ? FocusSearch::StartingViewPolicy::kCheckStartingView
: FocusSearch::StartingViewPolicy::kSkipStartingView;
v = parent_focus_traversable->GetFocusSearch()->FindNextFocusableView(
starting_view,
reverse ? FocusSearch::SearchDirection::kBackwards
: FocusSearch::SearchDirection::kForwards,
FocusSearch::TraversalDirection::kUp, check_starting_view,
FocusSearch::AnchoredDialogPolicy::kSkipAnchoredDialog,
&new_focus_traversable, &new_starting_view);
if (new_focus_traversable) {
DCHECK(!v);
v = FindFocusableView(new_focus_traversable, nullptr, reverse);
}
if (v) {
return v;
}
starting_view = focus_traversable->GetFocusTraversableParentView();
parent_focus_traversable =
parent_focus_traversable->GetFocusTraversableParent();
}
if (dont_loop || !original_starting_view) {
return nullptr;
}
Widget* widget = starting_view ? starting_view->GetWidget()
: original_starting_view->GetWidget();
if (widget->widget_delegate()->focus_traverses_out()) {
widget = widget_;
}
return GetNextFocusableView(nullptr, widget, reverse, true);
}
void FocusManager::SetKeyboardAccessible(bool keyboard_accessible) {
if (keyboard_accessible == keyboard_accessible_) {
return;
}
keyboard_accessible_ = keyboard_accessible;
AdvanceFocusIfNecessary();
}
bool FocusManager::IsSettingFocusedView() const {
return setting_focused_view_entrance_count_ > 0;
}
void FocusManager::SetFocusedViewWithReason(View* view,
FocusChangeReason reason) {
if (focused_view_ == view) {
return;
}
CHECK(!view || ContainsView(view));
#if !BUILDFLAG(IS_MAC)
if (view && !widget_->IsActive()) {
SetStoredFocusView(view);
widget_->Activate();
return;
}
#endif
focus_change_reason_ = reason;
focus_change_listeners_.Notify(&FocusChangeListener::OnWillChangeFocus,
focused_view_, view);
View* old_focused_view = focused_view_;
focused_view_ = view;
base::AutoReset<int> entrance_count_resetter(
&setting_focused_view_entrance_count_,
setting_focused_view_entrance_count_ + 1);
if (old_focused_view) {
old_focused_view->RemoveObserver(this);
old_focused_view->Blur();
}
SetStoredFocusView(focused_view_);
if (focused_view_) {
if (!focused_view_->HasObserver(this)) {
focused_view_->AddObserver(this);
}
focused_view_->Focus();
}
focus_change_listeners_.Notify(&FocusChangeListener::OnDidChangeFocus,
old_focused_view, focused_view_);
}
void FocusManager::SetFocusedView(View* view) {
FocusChangeReason reason = FocusChangeReason::kDirectFocusChange;
if (in_restoring_focused_view_) {
reason = FocusChangeReason::kFocusRestore;
}
SetFocusedViewWithReason(view, reason);
}
void FocusManager::ClearFocus() {
views::View* focused_view = GetStoredFocusView();
SetFocusedView(nullptr);
ClearNativeFocus();
SetStoredFocusView(focused_view);
}
void FocusManager::AdvanceFocusIfNecessary() {
if (!widget_->IsActive()) {
return;
}
if (focused_view_ && !IsFocusable(focused_view_)) {
AdvanceFocus(false);
if (focused_view_ && !IsFocusable(focused_view_)) {
ClearFocus();
}
}
}
void FocusManager::StoreFocusedView(bool clear_native_focus) {
View* focused_view = focused_view_;
if (!focused_view_) {
return;
}
View* v = focused_view_;
if (clear_native_focus) {
AutoNativeNotificationDisabler local_notification_disabler;
ClearFocus();
} else {
SetFocusedView(nullptr);
SetStoredFocusView(focused_view);
}
if (v) {
v->SchedulePaint();
}
}
bool FocusManager::RestoreFocusedView() {
View* view = GetStoredFocusView();
if (view) {
if (ContainsView(view)) {
if (!view->IsFocusable() &&
view->GetViewAccessibility().IsAccessibilityFocusable()) {
SetFocusedViewWithReason(view, FocusChangeReason::kFocusRestore);
} else {
base::AutoReset<bool> in_restore_bit(&in_restoring_focused_view_, true);
view->RequestFocus();
}
}
AdvanceFocusIfNecessary();
}
return view && view == focused_view_;
}
void FocusManager::SetStoredFocusView(View* focus_view) {
view_tracker_for_stored_view_->SetView(focus_view);
}
View* FocusManager::GetStoredFocusView() {
return view_tracker_for_stored_view_->view();
}
View* FocusManager::FindFocusableView(FocusTraversable* focus_traversable,
View* starting_view,
bool reverse) {
FocusTraversable* new_focus_traversable = nullptr;
View* new_starting_view = nullptr;
const FocusSearch::AnchoredDialogPolicy can_go_into_anchored_dialog =
FocusSearch::AnchoredDialogPolicy::kCanGoIntoAnchoredDialog;
const FocusSearch::SearchDirection search_direction =
reverse ? FocusSearch::SearchDirection::kBackwards
: FocusSearch::SearchDirection::kForwards;
View* v = nullptr;
do {
v = focus_traversable->GetFocusSearch()->FindNextFocusableView(
starting_view, search_direction, FocusSearch::TraversalDirection::kDown,
FocusSearch::StartingViewPolicy::kSkipStartingView,
can_go_into_anchored_dialog, &new_focus_traversable,
&new_starting_view);
DCHECK(!new_focus_traversable || !v);
focus_traversable = std::exchange(new_focus_traversable, nullptr);
starting_view = nullptr;
} while (focus_traversable);
return v;
}
void FocusManager::RegisterAccelerator(
const ui::Accelerator& accelerator,
ui::AcceleratorManager::HandlerPriority priority,
ui::AcceleratorTarget* target) {
accelerator_manager_.Register({accelerator}, priority, target);
}
void FocusManager::UnregisterAccelerator(const ui::Accelerator& accelerator,
ui::AcceleratorTarget* target) {
accelerator_manager_.Unregister(accelerator, target);
}
void FocusManager::UnregisterAccelerators(ui::AcceleratorTarget* target) {
accelerator_manager_.UnregisterAll(target);
}
bool FocusManager::ProcessAccelerator(const ui::Accelerator& accelerator) {
if (accelerator_manager_.Process(accelerator)) {
return true;
}
if (delegate_ && delegate_->ProcessAccelerator(accelerator)) {
return true;
}
#if BUILDFLAG(IS_MAC)
return false;
#else
return RedirectAcceleratorToBubbleAnchorWidget(accelerator);
#endif
}
bool FocusManager::IsAcceleratorRegistered(
const ui::Accelerator& accelerator) const {
return accelerator_manager_.IsRegistered(accelerator);
}
bool FocusManager::HasPriorityHandler(
const ui::Accelerator& accelerator) const {
return accelerator_manager_.HasPriorityHandler(accelerator);
}
bool FocusManager::IsTabTraversalKeyEvent(const ui::KeyEvent& key_event) {
return key_event.key_code() == ui::VKEY_TAB &&
(!key_event.IsControlDown() && !key_event.IsAltDown());
}
void FocusManager::ViewRemoved(View* removed) {
DCHECK(removed);
if (removed->Contains(focused_view_)) {
SetFocusedView(nullptr);
}
}
void FocusManager::AddFocusChangeListener(FocusChangeListener* listener) {
focus_change_listeners_.AddObserver(listener);
}
void FocusManager::RemoveFocusChangeListener(FocusChangeListener* listener) {
focus_change_listeners_.RemoveObserver(listener);
}
bool FocusManager::ProcessArrowKeyTraversal(const ui::KeyEvent& event) {
if (event.IsShiftDown() || event.IsControlDown() || event.IsAltDown() ||
event.IsAltGrDown()) {
return false;
}
const ui::KeyboardCode key = event.key_code();
if (key != ui::VKEY_UP && key != ui::VKEY_DOWN && key != ui::VKEY_LEFT &&
key != ui::VKEY_RIGHT) {
return false;
}
const ui::KeyboardCode reverse =
base::i18n::IsRTL() ? ui::VKEY_RIGHT : ui::VKEY_LEFT;
AdvanceFocus(key == reverse || key == ui::VKEY_UP);
return true;
}
bool FocusManager::IsFocusable(View* view) const {
DCHECK(view);
#if BUILDFLAG(IS_MAC)
return keyboard_accessible_
? view->GetViewAccessibility().IsAccessibilityFocusable()
: view->IsFocusable();
#else
return view->GetViewAccessibility().IsAccessibilityFocusable();
#endif
}
void FocusManager::OnViewIsDeleting(View* view) {
CHECK_EQ(view, focused_view_);
SetFocusedView(nullptr);
}
bool FocusManager::RedirectAcceleratorToBubbleAnchorWidget(
const ui::Accelerator& accelerator) {
if (!widget_->widget_delegate()) {
return false;
}
views::BubbleDialogDelegate* widget_delegate =
widget_->widget_delegate()->AsBubbleDialogDelegate();
Widget* anchor_widget =
widget_delegate ? widget_delegate->anchor_widget() : nullptr;
if (!anchor_widget) {
return false;
}
FocusManager* focus_manager = anchor_widget->GetFocusManager();
if (!focus_manager->IsAcceleratorRegistered(accelerator)) {
return false;
}
#if BUILDFLAG(IS_LINUX)
base::WeakPtr<Widget> widget_weak_ptr = widget_->GetWeakPtr();
const bool close_widget_on_deactivate =
widget_delegate->ShouldCloseOnDeactivate();
#endif
focus_manager->SetFocusedView(anchor_widget->GetRootView());
const bool accelerator_processed =
focus_manager->ProcessAccelerator(accelerator);
#if BUILDFLAG(IS_LINUX)
if (accelerator_processed && close_widget_on_deactivate) {
widget_weak_ptr->CloseWithReason(views::Widget::ClosedReason::kLostFocus);
}
#endif
return accelerator_processed;
}
bool FocusManager::IsArrowKeyTraversalEnabledForWidget() const {
if (delegate_ && delegate_->IsArrowKeyTraversalEnabled()) {
return true;
}
Widget* const widget = (focused_view_ && focused_view_->GetWidget())
? focused_view_->GetWidget()
: widget_.get();
return widget && widget->widget_delegate() &&
widget->widget_delegate()->enable_arrow_key_traversal();
}
}