| // Copyright (c) 2012 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 "ash/wm/activation_controller.h" |
| |
| #include "ash/root_window_controller.h" |
| #include "ash/shell.h" |
| #include "ash/shell_window_ids.h" |
| #include "ash/wm/property_util.h" |
| #include "ash/wm/window_modality_controller.h" |
| #include "ash/wm/window_util.h" |
| #include "ash/wm/workspace_controller.h" |
| #include "base/auto_reset.h" |
| #include "ui/aura/client/activation_change_observer.h" |
| #include "ui/aura/client/activation_delegate.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/env.h" |
| #include "ui/aura/focus_manager.h" |
| #include "ui/aura/root_window.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_delegate.h" |
| #include "ui/base/ui_base_types.h" |
| #include "ui/compositor/layer.h" |
| |
| namespace ash { |
| namespace internal { |
| namespace { |
| |
| // These are the list of container ids of containers which may contain windows |
| // that need to be activated in the order that they should be activated. |
| const int kWindowContainerIds[] = { |
| kShellWindowId_LockSystemModalContainer, |
| kShellWindowId_SettingBubbleContainer, |
| kShellWindowId_LockScreenContainer, |
| kShellWindowId_SystemModalContainer, |
| kShellWindowId_AlwaysOnTopContainer, |
| kShellWindowId_AppListContainer, |
| // TODO(sky): defaultcontainer shouldn't be in the list with workspace2. |
| kShellWindowId_DefaultContainer, |
| |
| // Panel, launcher and status are intentionally checked after other |
| // containers even though these layers are higher. The user expects their |
| // windows to be focused before these elements. |
| kShellWindowId_PanelContainer, |
| kShellWindowId_LauncherContainer, |
| kShellWindowId_StatusContainer, |
| }; |
| |
| // Returns true if children of |window| can be activated. |
| // These are the only containers in which windows can receive focus. |
| bool SupportsChildActivation(aura::Window* window) { |
| // TODO(sky): straighten this out when workspace2 is the default. |
| // kShellWindowId_WorkspaceContainer isn't in |kWindowContainerIds| since it |
| // needs to be special cased in GetTopmostWindowToActivate(). |
| if (window->id() == kShellWindowId_WorkspaceContainer) |
| return true; |
| |
| for (size_t i = 0; i < arraysize(kWindowContainerIds); i++) { |
| if (window->id() == kWindowContainerIds[i] && |
| (window->id() != kShellWindowId_DefaultContainer || |
| !WorkspaceController::IsWorkspace2Enabled())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool HasModalTransientChild(aura::Window* window) { |
| aura::Window::Windows::const_iterator it; |
| for (it = window->transient_children().begin(); |
| it != window->transient_children().end(); |
| ++it) { |
| if ((*it)->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_WINDOW) |
| return true; |
| } |
| return false; |
| } |
| |
| // See description in VisibilityMatches. |
| enum ActivateVisibilityType { |
| TARGET_VISIBILITY, |
| CURRENT_VISIBILITY, |
| }; |
| |
| // Used by CanActivateWindowWithEvent() to test the visibility of a window. |
| // This is used by two distinct code paths: |
| // . when activating from an event we only care about the actual visibility. |
| // . when activating because of a keyboard accelerator, in which case we |
| // care about the TargetVisibility. |
| bool VisibilityMatches(aura::Window* window, ActivateVisibilityType type) { |
| bool visible = (type == CURRENT_VISIBILITY) ? window->IsVisible() : |
| window->TargetVisibility(); |
| return visible || wm::IsWindowMinimized(window) || |
| (window->TargetVisibility() && |
| window->parent()->id() == kShellWindowId_WorkspaceContainer); |
| } |
| |
| // Returns true if |window| can be activated or deactivated. |
| // A window manager typically defines some notion of "top level window" that |
| // supports activation/deactivation. |
| bool CanActivateWindowWithEvent(aura::Window* window, |
| const ui::Event* event, |
| ActivateVisibilityType visibility_type) { |
| return window && |
| VisibilityMatches(window, visibility_type) && |
| (!aura::client::GetActivationDelegate(window) || |
| aura::client::GetActivationDelegate(window)->ShouldActivate(event)) && |
| SupportsChildActivation(window->parent()); |
| } |
| |
| // When a modal window is activated, we bring its entire transient parent chain |
| // to the front. This function must be called before the modal transient is |
| // stacked at the top to ensure correct stacking order. |
| void StackTransientParentsBelowModalWindow(aura::Window* window) { |
| if (window->GetProperty(aura::client::kModalKey) != ui::MODAL_TYPE_WINDOW) |
| return; |
| |
| aura::Window* transient_parent = window->transient_parent(); |
| while (transient_parent) { |
| transient_parent->parent()->StackChildAtTop(transient_parent); |
| transient_parent = transient_parent->transient_parent(); |
| } |
| } |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ActivationController, public: |
| |
| ActivationController::ActivationController(aura::FocusManager* focus_manager) |
| : focus_manager_(focus_manager), |
| updating_activation_(false), |
| active_window_(NULL), |
| ALLOW_THIS_IN_INITIALIZER_LIST(observer_manager_(this)) { |
| aura::Env::GetInstance()->AddObserver(this); |
| focus_manager_->AddObserver(this); |
| } |
| |
| ActivationController::~ActivationController() { |
| aura::Env::GetInstance()->RemoveObserver(this); |
| focus_manager_->RemoveObserver(this); |
| } |
| |
| // static |
| aura::Window* ActivationController::GetActivatableWindow( |
| aura::Window* window, |
| const ui::Event* event) { |
| aura::Window* parent = window->parent(); |
| aura::Window* child = window; |
| while (parent) { |
| if (CanActivateWindowWithEvent(child, event, CURRENT_VISIBILITY)) |
| return child; |
| // If |child| isn't activatable, but has transient parent, trace |
| // that path instead. |
| if (child->transient_parent()) |
| return GetActivatableWindow(child->transient_parent(), event); |
| parent = parent->parent(); |
| child = child->parent(); |
| } |
| return NULL; |
| } |
| |
| bool ActivationController::CanActivateWindow(aura::Window* window) const { |
| return CanActivateWindowWithEvent(window, NULL, TARGET_VISIBILITY) && |
| !HasModalTransientChild(window); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ActivationController, aura::client::ActivationClient implementation: |
| |
| void ActivationController::AddObserver( |
| aura::client::ActivationChangeObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void ActivationController::RemoveObserver( |
| aura::client::ActivationChangeObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void ActivationController::ActivateWindow(aura::Window* window) { |
| ActivateWindowWithEvent(window, NULL); |
| } |
| |
| void ActivationController::DeactivateWindow(aura::Window* window) { |
| if (window) |
| ActivateNextWindow(window); |
| } |
| |
| aura::Window* ActivationController::GetActiveWindow() { |
| return active_window_; |
| } |
| |
| bool ActivationController::OnWillFocusWindow(aura::Window* window, |
| const ui::Event* event) { |
| return CanActivateWindowWithEvent( |
| GetActivatableWindow(window, event), event, CURRENT_VISIBILITY); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ActivationController, aura::WindowObserver implementation: |
| |
| void ActivationController::OnWindowVisibilityChanged(aura::Window* window, |
| bool visible) { |
| if (!visible) { |
| aura::Window* next_window = ActivateNextWindow(window); |
| if (next_window && next_window->parent() == window->parent()) { |
| // Despite the activation change, we need to keep the window being hidden |
| // stacked above the new window so it stays on top as it animates away. |
| window->layer()->parent()->StackAbove(window->layer(), |
| next_window->layer()); |
| } |
| } |
| } |
| |
| void ActivationController::OnWindowDestroying(aura::Window* window) { |
| // Don't use wm::IsActiveWidnow in case the |window| has already been |
| // removed from the root tree. |
| if (active_window_ == window) { |
| active_window_ = NULL; |
| FOR_EACH_OBSERVER(aura::client::ActivationChangeObserver, |
| observers_, |
| OnWindowActivated(NULL, window)); |
| ActivateWindow(GetTopmostWindowToActivate(window)); |
| } |
| observer_manager_.Remove(window); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ActivationController, aura::EnvObserver implementation: |
| |
| void ActivationController::OnWindowInitialized(aura::Window* window) { |
| observer_manager_.Add(window); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ActivationController, aura::RootWindowObserver implementation: |
| |
| void ActivationController::OnWindowFocused(aura::Window* window) { |
| ActivateWindow(GetActivatableWindow(window, NULL)); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ActivationController, private: |
| |
| void ActivationController::ActivateWindowWithEvent(aura::Window* window, |
| const ui::Event* event) { |
| aura::Window* window_modal_transient = wm::GetWindowModalTransient(window); |
| if (window_modal_transient) { |
| ActivateWindow(window_modal_transient); |
| return; |
| } |
| |
| // Prevent recursion when called from focus. |
| if (updating_activation_) |
| return; |
| |
| AutoReset<bool> in_activate_window(&updating_activation_, true); |
| // Nothing may actually have changed. |
| if (active_window_ == window) |
| return; |
| // The stacking client may impose rules on what window configurations can be |
| // activated or deactivated. |
| if (window && !CanActivateWindowWithEvent(window, event, CURRENT_VISIBILITY)) |
| return; |
| |
| // Make sure the workspace manager switches to the workspace for window. |
| // Without this CanReceiveEvents() below returns false and activation never |
| // changes. CanReceiveEvents() returns false if |window| isn't in the active |
| // workspace, in which case its parent is not visible. |
| // TODO(sky): if I instead change the opacity of the parent this isn't an |
| // issue, but will make animations trickier... Consider which one is better. |
| if (window) { |
| internal::RootWindowController* root_window_controller = |
| GetRootWindowController(window->GetRootWindow()); |
| root_window_controller->workspace_controller()-> |
| SetActiveWorkspaceByWindow(window); |
| } |
| |
| // Restore minimized window. This needs to be done before CanReceiveEvents() |
| // is called as that function checks window visibility. |
| if (window && wm::IsWindowMinimized(window)) |
| window->Show(); |
| |
| // If the screen is locked, just bring the window to top so that |
| // it will be activated when the lock window is destroyed. |
| if (window && !window->CanReceiveEvents()) { |
| StackTransientParentsBelowModalWindow(window); |
| window->parent()->StackChildAtTop(window); |
| return; |
| } |
| if (window && |
| !window->Contains(window->GetFocusManager()->GetFocusedWindow())) { |
| window->GetFocusManager()->SetFocusedWindow(window, event); |
| } |
| |
| aura::Window* old_active = active_window_; |
| active_window_ = window; |
| if (window) { |
| DCHECK(window->GetRootWindow()); |
| Shell::GetInstance()->set_active_root_window(window->GetRootWindow()); |
| } |
| |
| FOR_EACH_OBSERVER(aura::client::ActivationChangeObserver, |
| observers_, |
| OnWindowActivated(window, old_active)); |
| |
| // Invoke OnLostActive after we've changed the active window. That way if the |
| // delegate queries for active state it doesn't think the window is still |
| // active. |
| if (old_active && aura::client::GetActivationDelegate(old_active)) |
| aura::client::GetActivationDelegate(old_active)->OnLostActive(); |
| |
| if (window) { |
| StackTransientParentsBelowModalWindow(window); |
| window->parent()->StackChildAtTop(window); |
| if (aura::client::GetActivationDelegate(window)) |
| aura::client::GetActivationDelegate(window)->OnActivated(); |
| } |
| } |
| |
| aura::Window* ActivationController::ActivateNextWindow(aura::Window* window) { |
| aura::Window* next_window = NULL; |
| if (wm::IsActiveWindow(window)) { |
| next_window = GetTopmostWindowToActivate(window); |
| ActivateWindow(next_window); |
| } |
| return next_window; |
| } |
| |
| aura::Window* ActivationController::GetTopmostWindowToActivate( |
| aura::Window* ignore) const { |
| size_t current_container_index = 0; |
| // If the container of the window losing focus is in the list, start from that |
| // container. |
| aura::RootWindow* root = ignore->GetRootWindow(); |
| if (!root) |
| root = Shell::GetActiveRootWindow(); |
| for (size_t i = 0; ignore && i < arraysize(kWindowContainerIds); i++) { |
| aura::Window* container = Shell::GetContainer(root, kWindowContainerIds[i]); |
| if (container && container->Contains(ignore)) { |
| current_container_index = i; |
| break; |
| } |
| } |
| |
| // Look for windows to focus in that container and below. |
| aura::Window* window = NULL; |
| for (; !window && current_container_index < arraysize(kWindowContainerIds); |
| current_container_index++) { |
| aura::Window::Windows containers = |
| Shell::GetAllContainers(kWindowContainerIds[current_container_index]); |
| for (aura::Window::Windows::const_iterator iter = containers.begin(); |
| iter != containers.end() && !window; ++iter) { |
| window = GetTopmostWindowToActivateInContainer((*iter), ignore); |
| } |
| } |
| return window; |
| } |
| |
| aura::Window* ActivationController::GetTopmostWindowToActivateInContainer( |
| aura::Window* container, |
| aura::Window* ignore) const { |
| // Workspace2 has an extra level of windows that needs to be special cased. |
| if (container->id() == kShellWindowId_DefaultContainer && |
| WorkspaceController::IsWorkspace2Enabled()) { |
| for (aura::Window::Windows::const_reverse_iterator i = |
| container->children().rbegin(); |
| i != container->children().rend(); ++i) { |
| if ((*i)->IsVisible()) { |
| aura::Window* window = GetTopmostWindowToActivateInContainer( |
| *i, ignore); |
| if (window) |
| return window; |
| } |
| } |
| return NULL; |
| } |
| for (aura::Window::Windows::const_reverse_iterator i = |
| container->children().rbegin(); |
| i != container->children().rend(); |
| ++i) { |
| if (*i != ignore && |
| CanActivateWindowWithEvent(*i, NULL, CURRENT_VISIBILITY) && |
| !wm::IsWindowMinimized(*i)) |
| return *i; |
| } |
| return NULL; |
| } |
| |
| } // namespace internal |
| } // namespace ash |