blob: 328c34cafa7175c6c65920a1cec47fd70a8be64e [file] [log] [blame]
// 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/activation_controller_delegate.h"
#include "ash/wm/property_util.h"
#include "ash/wm/window_util.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/client/focus_client.h"
#include "ui/aura/env.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"
#include "ui/views/corewm/window_modality_controller.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,
kShellWindowId_DefaultContainer,
// Docked, 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_DockedContainer,
kShellWindowId_PanelContainer,
kShellWindowId_ShelfContainer,
kShellWindowId_StatusContainer,
};
bool BelongsToContainerWithEqualOrGreaterId(const aura::Window* window,
int container_id) {
for (; window; window = window->parent()) {
if (window->id() >= container_id)
return true;
}
return false;
}
// 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) {
for (size_t i = 0; i < arraysize(kWindowContainerIds); i++) {
if (window->id() == kWindowContainerIds[i])
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_DefaultContainer ||
window->parent()->id() == kShellWindowId_LockScreenContainer));
}
// 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()) &&
SupportsChildActivation(window->parent()) &&
(BelongsToContainerWithEqualOrGreaterId(
window, kShellWindowId_SystemModalContainer) ||
!Shell::GetInstance()->IsSystemModalWindowOpen());
}
// 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();
}
}
aura::Window* FindFocusableWindowFor(aura::Window* window) {
while (window && !window->CanFocus())
window = window->parent();
return window;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// ActivationController, public:
ActivationController::ActivationController(
aura::client::FocusClient* focus_client,
ActivationControllerDelegate* delegate)
: focus_client_(focus_client),
updating_activation_(false),
active_window_(NULL),
observer_manager_(this),
delegate_(delegate) {
aura::Env::GetInstance()->AddObserver(this);
focus_client_->AddObserver(this);
}
ActivationController::~ActivationController() {
aura::Env::GetInstance()->RemoveObserver(this);
focus_client_->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_;
}
aura::Window* ActivationController::GetActivatableWindow(aura::Window* window) {
return GetActivatableWindow(window, NULL);
}
aura::Window* ActivationController::GetToplevelWindow(aura::Window* window) {
return GetActivatableWindow(window, NULL);
}
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* gained_focus,
aura::Window* lost_focus) {
if (gained_focus)
ActivateWindow(GetActivatableWindow(gained_focus, NULL));
}
////////////////////////////////////////////////////////////////////////////////
// ActivationController, ui::EventHandler implementation:
void ActivationController::OnKeyEvent(ui::KeyEvent* event) {
}
void ActivationController::OnMouseEvent(ui::MouseEvent* event) {
if (event->type() == ui::ET_MOUSE_PRESSED)
FocusWindowWithEvent(event);
}
void ActivationController::OnScrollEvent(ui::ScrollEvent* event) {
}
void ActivationController::OnTouchEvent(ui::TouchEvent* event) {
if (event->type() == ui::ET_TOUCH_PRESSED)
FocusWindowWithEvent(event);
}
void ActivationController::OnGestureEvent(ui::GestureEvent* event) {
if (event->type() == ui::ET_GESTURE_BEGIN &&
event->details().touch_points() == 1) {
FocusWindowWithEvent(event);
}
}
////////////////////////////////////////////////////////////////////////////////
// ActivationController, private:
void ActivationController::ActivateWindowWithEvent(aura::Window* window,
const ui::Event* event) {
// Prevent recursion when called from focus.
if (updating_activation_)
return;
base::AutoReset<bool> in_activate_window(&updating_activation_, true);
// We allow the delegate to change which window gets activated, or to prevent
// activation changes.
aura::Window* original_active_window = window;
window = delegate_->WillActivateWindow(window);
// TODO(beng): note that this breaks the previous behavior where an activation
// attempt by a window behind the lock screen would at least
// restack that window frontmost within its container. fix this.
if (!window && original_active_window != window)
return;
// TODO(beng): This encapsulates additional Ash-specific restrictions on
// whether activation can change. Should move to the delegate.
if (window && !CanActivateWindowWithEvent(window, event, CURRENT_VISIBILITY))
return;
if (active_window_ == window)
return;
aura::Window* old_active = active_window_;
active_window_ = window;
if (window &&
!window->Contains(aura::client::GetFocusClient(window)->
GetFocusedWindow())) {
aura::client::GetFocusClient(window)->FocusWindow(window);
}
if (window) {
StackTransientParentsBelowModalWindow(window);
window->parent()->StackChildAtTop(window);
}
FOR_EACH_OBSERVER(aura::client::ActivationChangeObserver,
observers_,
OnWindowActivated(window, old_active));
if (aura::client::GetActivationChangeObserver(old_active)) {
aura::client::GetActivationChangeObserver(old_active)->OnWindowActivated(
window, old_active);
}
if (aura::client::GetActivationChangeObserver(window)) {
aura::client::GetActivationChangeObserver(window)->OnWindowActivated(
window, old_active);
}
}
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::GetContainersFromAllRootWindows(
kWindowContainerIds[current_container_index],
root);
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 {
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;
}
void ActivationController::FocusWindowWithEvent(const ui::Event* event) {
aura::Window* window = static_cast<aura::Window*>(event->target());
window = delegate_->WillFocusWindow(window);
if (GetActiveWindow() != window) {
aura::client::GetFocusClient(window)->FocusWindow(
FindFocusableWindowFor(window));
}
}
} // namespace internal
} // namespace ash