blob: 956d35853afb152337f3e213fe1f67ca26f9623d [file] [log] [blame]
// Copyright 2014 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 "ui/wm/core/transient_window_manager.h"
#include <algorithm>
#include <functional>
#include "base/auto_reset.h"
#include "base/stl_util.h"
#include "ui/aura/window.h"
#include "ui/aura/window_property.h"
#include "ui/aura/window_tracker.h"
#include "ui/wm/core/transient_window_observer.h"
#include "ui/wm/core/transient_window_stacking_client.h"
#include "ui/wm/core/window_util.h"
using aura::Window;
DECLARE_WINDOW_PROPERTY_TYPE(wm::TransientWindowManager*);
namespace wm {
namespace {
DEFINE_OWNED_WINDOW_PROPERTY_KEY(TransientWindowManager, kPropertyKey, NULL);
} // namespace
TransientWindowManager::~TransientWindowManager() {
}
// static
TransientWindowManager* TransientWindowManager::Get(Window* window) {
TransientWindowManager* manager = window->GetProperty(kPropertyKey);
if (!manager) {
manager = new TransientWindowManager(window);
window->SetProperty(kPropertyKey, manager);
}
return manager;
}
// static
const TransientWindowManager* TransientWindowManager::Get(
const Window* window) {
return window->GetProperty(kPropertyKey);
}
void TransientWindowManager::AddObserver(TransientWindowObserver* observer) {
observers_.AddObserver(observer);
}
void TransientWindowManager::RemoveObserver(TransientWindowObserver* observer) {
observers_.RemoveObserver(observer);
}
void TransientWindowManager::AddTransientChild(Window* child) {
// TransientWindowStackingClient does the stacking of transient windows. If it
// isn't installed stacking is going to be wrong.
DCHECK(TransientWindowStackingClient::instance_);
TransientWindowManager* child_manager = Get(child);
if (child_manager->transient_parent_)
Get(child_manager->transient_parent_)->RemoveTransientChild(child);
DCHECK(std::find(transient_children_.begin(), transient_children_.end(),
child) == transient_children_.end());
transient_children_.push_back(child);
child_manager->transient_parent_ = window_;
// Restack |child| properly above its transient parent, if they share the same
// parent.
if (child->parent() == window_->parent())
RestackTransientDescendants();
FOR_EACH_OBSERVER(TransientWindowObserver, observers_,
OnTransientChildAdded(window_, child));
}
void TransientWindowManager::RemoveTransientChild(Window* child) {
Windows::iterator i =
std::find(transient_children_.begin(), transient_children_.end(), child);
DCHECK(i != transient_children_.end());
transient_children_.erase(i);
TransientWindowManager* child_manager = Get(child);
DCHECK_EQ(window_, child_manager->transient_parent_);
child_manager->transient_parent_ = NULL;
// If |child| and its former transient parent share the same parent, |child|
// should be restacked properly so it is not among transient children of its
// former parent, anymore.
if (window_->parent() == child->parent())
RestackTransientDescendants();
FOR_EACH_OBSERVER(TransientWindowObserver, observers_,
OnTransientChildRemoved(window_, child));
}
bool TransientWindowManager::IsStackingTransient(
const aura::Window* target) const {
return stacking_target_ == target;
}
TransientWindowManager::TransientWindowManager(Window* window)
: window_(window),
transient_parent_(NULL),
stacking_target_(NULL),
parent_controls_visibility_(false),
show_on_parent_visible_(false),
ignore_visibility_changed_event_(false) {
window_->AddObserver(this);
}
void TransientWindowManager::RestackTransientDescendants() {
Window* parent = window_->parent();
if (!parent)
return;
// Stack any transient children that share the same parent to be in front of
// |window_|. The existing stacking order is preserved by iterating backwards
// and always stacking on top.
Window::Windows children(parent->children());
for (Window::Windows::reverse_iterator it = children.rbegin();
it != children.rend(); ++it) {
if ((*it) != window_ && HasTransientAncestor(*it, window_)) {
TransientWindowManager* descendant_manager = Get(*it);
base::AutoReset<Window*> resetter(
&descendant_manager->stacking_target_,
window_);
parent->StackChildAbove((*it), window_);
}
}
}
void TransientWindowManager::OnWindowParentChanged(aura::Window* window,
aura::Window* parent) {
DCHECK_EQ(window_, window);
// Stack |window| properly if it is transient child of a sibling.
Window* transient_parent = wm::GetTransientParent(window);
if (transient_parent && transient_parent->parent() == parent) {
TransientWindowManager* transient_parent_manager =
Get(transient_parent);
transient_parent_manager->RestackTransientDescendants();
}
}
void TransientWindowManager::UpdateTransientChildVisibility(
bool parent_visible) {
base::AutoReset<bool> reset(&ignore_visibility_changed_event_, true);
if (!parent_visible) {
show_on_parent_visible_ = window_->TargetVisibility();
window_->Hide();
} else {
if (show_on_parent_visible_ && parent_controls_visibility_)
window_->Show();
show_on_parent_visible_ = false;
}
}
void TransientWindowManager::OnWindowVisibilityChanged(Window* window,
bool visible) {
if (window_ != window)
return;
// If the window has transient children, updates the transient children's
// visiblity as well.
// WindowTracker is used because child window
// could be deleted inside UpdateTransientChildVisibility call.
aura::WindowTracker tracker(transient_children_);
while (!tracker.windows().empty())
Get(tracker.Pop())->UpdateTransientChildVisibility(visible);
// Remember the show request in |show_on_parent_visible_| and hide it again
// if the following conditions are met
// - |parent_controls_visibility| is set to true.
// - the window is hidden while the transient parent is not visible.
// - Show/Hide was NOT called from TransientWindowManager.
if (ignore_visibility_changed_event_ ||
!transient_parent_ || !parent_controls_visibility_) {
return;
}
if (!transient_parent_->TargetVisibility() && visible) {
base::AutoReset<bool> reset(&ignore_visibility_changed_event_, true);
show_on_parent_visible_ = true;
window_->Hide();
} else if (!visible) {
DCHECK(!show_on_parent_visible_);
}
}
void TransientWindowManager::OnWindowStackingChanged(Window* window) {
DCHECK_EQ(window_, window);
// Do nothing if we initiated the stacking change.
const TransientWindowManager* transient_manager =
Get(static_cast<const Window*>(window));
if (transient_manager && transient_manager->stacking_target_) {
Windows::const_iterator window_i = std::find(
window->parent()->children().begin(),
window->parent()->children().end(),
window);
DCHECK(window_i != window->parent()->children().end());
if (window_i != window->parent()->children().begin() &&
(*(window_i - 1) == transient_manager->stacking_target_))
return;
}
RestackTransientDescendants();
}
void TransientWindowManager::OnWindowDestroying(Window* window) {
// Removes ourselves from our transient parent (if it hasn't been done by the
// RootWindow).
if (transient_parent_) {
TransientWindowManager::Get(transient_parent_)->RemoveTransientChild(
window_);
}
// Destroy transient children, only after we've removed ourselves from our
// parent, as destroying an active transient child may otherwise attempt to
// refocus us.
Windows transient_children(transient_children_);
STLDeleteElements(&transient_children);
DCHECK(transient_children_.empty());
}
} // namespace wm