blob: bd01316738683c89892057cf38cfed1bde34ff03 [file] [log] [blame]
// Copyright (c) 2011 The Chromium OS 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 "window_manager/transient_window_collection.h"
#include <list>
#include "window_manager/window_manager.h"
using std::list;
using std::map;
using std::tr1::shared_ptr;
namespace window_manager {
TransientWindowCollection::TransientWindowCollection(
Window* owner_win,
StackingPolicy stacking_policy,
CenterPolicy center_policy,
KeepOnscreenPolicy keep_onscreen_policy,
EventConsumer* event_consumer)
: owner_win_(owner_win),
event_consumer_(event_consumer),
stacked_transients_(new Stacker<TransientWindow*>),
transient_to_focus_(NULL),
shown_(true),
stacking_policy_(stacking_policy),
center_policy_(center_policy),
keep_onscreen_policy_(keep_onscreen_policy) {
DCHECK(owner_win_);
DCHECK(event_consumer);
}
TransientWindowCollection::~TransientWindowCollection() {
while (!transients_.empty())
RemoveWindow(transients_.begin()->second->win);
transient_to_focus_ = NULL;
}
bool TransientWindowCollection::ContainsWindow(const Window& win) const {
return transients_.find(win.xid()) != transients_.end();
}
bool TransientWindowCollection::HasFocusedWindow() const {
for (TransientWindowMap::const_iterator it = transients_.begin();
it != transients_.end(); ++it) {
if (it->second->win->IsFocused())
return true;
}
return false;
}
bool TransientWindowCollection::TakeFocus(XTime timestamp) {
if (!transient_to_focus_)
return false;
RestackTransientWindowOnTop(transient_to_focus_);
wm()->FocusWindow(transient_to_focus_->win, timestamp);
return true;
}
void TransientWindowCollection::SetPreferredWindowToFocus(
Window* transient_win) {
if (!transient_win) {
if (transient_to_focus_ && !transient_to_focus_->win->wm_state_modal())
transient_to_focus_ = NULL;
return;
}
TransientWindow* transient = GetTransientWindow(*transient_win);
if (!transient) {
LOG(ERROR) << "Got request to prefer focusing " << transient_win->xid_str()
<< ", which isn't transient for " << owner_win_->xid_str();
return;
}
if (transient == transient_to_focus_)
return;
if (!transient_to_focus_ ||
!transient_to_focus_->win->wm_state_modal() ||
transient_win->wm_state_modal())
transient_to_focus_ = transient;
}
void TransientWindowCollection::AddWindow(Window* transient_win) {
CHECK(transient_win);
if (ContainsWindow(*transient_win)) {
LOG(ERROR) << "Got request to add already-present transient window "
<< transient_win->xid_str() << " to " << owner_win_->xid_str();
return;
}
wm()->RegisterEventConsumerForWindowEvents(transient_win->xid(),
event_consumer_);
shared_ptr<TransientWindow> transient(new TransientWindow(transient_win));
transients_[transient_win->xid()] = transient;
// Info bubbles always keep their initial positions.
if (transient_win->type() == chromeos::WM_IPC_WINDOW_CHROME_INFO_BUBBLE) {
transient->SaveOffsetsRelativeToRect(
owner_win_->client_bounds(),
Point(transient_win->composited_x(), transient_win->composited_y()));
transient->centered = false;
} else {
transient->UpdateOffsetsToCenterOverRect(
owner_win_->client_bounds(),
wm()->root_bounds(),
keep_onscreen_policy_ == KEEP_ONSCREEN_ALWAYS);
transient->centered = true;
}
Window* sibling_window = NULL;
StackingManager::SiblingPolicy sibling_policy =
StackingManager::ABOVE_SIBLING;
// If the new transient is non-modal, stack it above the top non-modal
// transient that we have. If it's modal, just put it on top of all
// other transients.
for (list<TransientWindow*>::const_iterator it =
stacked_transients_->items().begin();
it != stacked_transients_->items().end(); ++it) {
if (transient_win->wm_state_modal() || !(*it)->win->wm_state_modal()) {
stacked_transients_->AddAbove(transient.get(), (*it));
sibling_window = (*it)->win;
sibling_policy = StackingManager::ABOVE_SIBLING;
break;
}
}
if (!sibling_window) {
if (!stacked_transients_->items().empty()) {
// If we have other transients already but they should all be above this
// one, then stack it directly underneath the bottom one.
sibling_window = stacked_transients_->items().back()->win;
sibling_policy = StackingManager::BELOW_SIBLING;
} else {
// Okay, this is the only transient. Stack it above the owner or at the
// top of the transient window layer, depending on the policy.
sibling_window =
(stacking_policy_ == STACK_ABOVE_OWNER) ? owner_win_ : NULL;
sibling_policy = StackingManager::ABOVE_SIBLING;
}
stacked_transients_->AddOnBottom(transient.get());
}
SetPreferredWindowToFocus(transient_win);
wm()->focus_manager()->UseClickToFocusForWindow(
transient_win, FocusManager::PASS_CLICKS_THROUGH);
transient_win->SetVisibility(shown_ ?
Window::VISIBILITY_SHOWN :
Window::VISIBILITY_HIDDEN);
ConfigureTransientWindow(transient.get(), 0);
ApplyStackingForTransientWindow(
transient.get(), sibling_window, sibling_policy);
}
void TransientWindowCollection::RemoveWindow(Window* transient_win) {
CHECK(transient_win);
TransientWindow* transient = GetTransientWindow(*transient_win);
if (!transient) {
LOG(ERROR) << "Got request to remove not-present transient window "
<< transient_win->xid_str() << " from " << owner_win_->xid_str();
return;
}
transient_win->SetVisibility(Window::VISIBILITY_HIDDEN);
wm()->UnregisterEventConsumerForWindowEvents(transient_win->xid(),
event_consumer_);
stacked_transients_->Remove(transient);
CHECK(transients_.erase(transient_win->xid()) == 1);
if (transient_to_focus_ == transient) {
transient_to_focus_ = NULL;
TransientWindow* new_transient = FindTransientWindowToFocus();
SetPreferredWindowToFocus(
new_transient ? new_transient->win : NULL);
}
}
void TransientWindowCollection::HandleWindowModalityChange(
Window* transient_win) {
if (transient_win->wm_state_modal()) {
// If this window just became modal, focus it.
transient_to_focus_ = GetTransientWindow(*transient_win);
} else {
// Otherwise, if it's not modal but we were previously preferring it, make
// sure that there aren't any other transient windows that we should focus
// instead.
if (transient_to_focus_ && transient_to_focus_->win == transient_win) {
TransientWindow* preferred_transient = FindTransientWindowToFocus();
if (preferred_transient && preferred_transient->win->wm_state_modal())
transient_to_focus_ = preferred_transient;
}
}
}
void TransientWindowCollection::ConfigureAllWindowsRelativeToOwner(
int anim_ms) {
for (TransientWindowMap::iterator it = transients_.begin();
it != transients_.end(); ++it) {
ConfigureTransientWindow(it->second.get(), anim_ms);
}
}
void TransientWindowCollection::ApplyStackingForAllWindows() {
Window* prev_win =
(stacking_policy_ == STACK_ABOVE_OWNER) ? owner_win_ : NULL;
for (list<TransientWindow*>::const_reverse_iterator it =
stacked_transients_->items().rbegin();
it != stacked_transients_->items().rend();
++it) {
TransientWindow* transient = *it;
ApplyStackingForTransientWindow(
transient, prev_win, StackingManager::ABOVE_SIBLING);
prev_win = transient->win;
}
}
void TransientWindowCollection::HandleConfigureRequest(
Window* transient_win, const Rect& requested_bounds) {
CHECK(transient_win);
TransientWindow* transient = GetTransientWindow(*transient_win);
CHECK(transient);
const Rect orig_client_bounds = transient_win->client_bounds();
// Move and resize the transient window as requested (only let info bubbles
// move themselves).
if (transient_win->type() == chromeos::WM_IPC_WINDOW_CHROME_INFO_BUBBLE) {
transient->SaveOffsetsRelativeToRect(
owner_win_->client_bounds(), requested_bounds.position());
transient->centered = false;
}
if (requested_bounds.size() != transient_win->client_size()) {
transient_win->Resize(requested_bounds.size(), GRAVITY_NORTHWEST);
if (transient->centered) {
transient->UpdateOffsetsToCenterOverRect(
owner_win_->client_bounds(),
wm()->root_bounds(),
keep_onscreen_policy_ == KEEP_ONSCREEN_ALWAYS);
}
}
ConfigureTransientWindow(transient, 0);
// If the window didn't change, send a fake ConfigureNotify to the
// client to let it know that we at least considered its request.
if (transient_win->client_bounds() == orig_client_bounds)
transient_win->SendSyntheticConfigureNotify();
}
void TransientWindowCollection::CloseAllWindows() {
if (transients_.empty())
return;
XTime timestamp = wm()->GetCurrentTimeFromServer();
for (TransientWindowMap::const_iterator it = transients_.begin();
it != transients_.end(); ++it) {
Window* win = it->second->win;
if (!win->SendDeleteRequest(timestamp))
LOG(WARNING) << "Unable to close transient window " << win->xid_str();
}
}
void TransientWindowCollection::Show() {
shown_ = true;
for (TransientWindowMap::const_iterator it = transients_.begin();
it != transients_.end(); ++it) {
TransientWindow* transient = it->second.get();
transient->win->SetVisibility(Window::VISIBILITY_SHOWN);
}
}
void TransientWindowCollection::Hide() {
shown_ = false;
for (TransientWindowMap::const_iterator it = transients_.begin();
it != transients_.end(); ++it) {
TransientWindow* transient = it->second.get();
transient->win->SetVisibility(Window::VISIBILITY_HIDDEN);
}
}
void
TransientWindowCollection::TransientWindow::UpdateOffsetsToCenterOverRect(
const Rect& center_rect, const Rect& bounding_rect, bool force_constrain) {
x_offset = (center_rect.width - win->client_width()) / 2;
y_offset = (center_rect.height - win->client_height()) / 2;
// Only honor the bounding rectangle if the center rect already falls
// completely inside of it or if we've been told to do so.
if (bounding_rect.contains_rect(center_rect) || force_constrain) {
if (center_rect.x + x_offset + win->client_width() >
bounding_rect.x + bounding_rect.width) {
x_offset = bounding_rect.x + bounding_rect.width -
win->client_width() - center_rect.x;
}
if (center_rect.x + x_offset < bounding_rect.x) {
x_offset = bounding_rect.x - center_rect.x;
}
if (center_rect.y + y_offset + win->client_height() >
bounding_rect.y + bounding_rect.height) {
y_offset = bounding_rect.y + bounding_rect.height -
win->client_height() - center_rect.y;
}
if (center_rect.y + y_offset < bounding_rect.y) {
y_offset = bounding_rect.y - center_rect.y;
}
}
}
TransientWindowCollection::TransientWindow*
TransientWindowCollection::GetTransientWindow(const Window& win) {
TransientWindowMap::iterator it = transients_.find(win.xid());
if (it == transients_.end())
return NULL;
return it->second.get();
}
void TransientWindowCollection::ConfigureTransientWindow(
TransientWindow* transient, int anim_ms) {
if (center_policy_ == CENTER_OVER_OWNER ||
transient->win->type() == chromeos::WM_IPC_WINDOW_CHROME_INFO_BUBBLE) {
transient->win->Move(
Point(owner_win_->composited_x() +
owner_win_->composited_scale_x() * transient->x_offset,
owner_win_->composited_y() +
owner_win_->composited_scale_y() * transient->y_offset),
anim_ms);
transient->win->ScaleComposited(
owner_win_->composited_scale_x(),
owner_win_->composited_scale_y(),
anim_ms);
transient->win->SetCompositedOpacity(
owner_win_->composited_opacity(), anim_ms);
} else if (center_policy_ == CENTER_ONSCREEN) {
transient->win->Move(
Point((wm()->width() - transient->win->client_width()) / 2,
(wm()->height() - transient->win->client_height()) / 2),
anim_ms);
} else {
NOTREACHED() << "Unknown center policy " << center_policy_;
}
}
void TransientWindowCollection::ApplyStackingForTransientWindow(
TransientWindow* transient,
Window* sibling_win,
StackingManager::SiblingPolicy sibling_policy) {
DCHECK(transient);
if (sibling_win) {
wm()->stacking_manager()->StackWindowRelativeToOtherWindow(
transient->win,
sibling_win,
sibling_policy,
StackingManager::SHADOW_DIRECTLY_BELOW_ACTOR,
StackingManager::LAYER_ACTIVE_TRANSIENT_WINDOW);
} else {
wm()->stacking_manager()->StackWindowAtTopOfLayer(
transient->win,
StackingManager::LAYER_ACTIVE_TRANSIENT_WINDOW,
StackingManager::SHADOW_DIRECTLY_BELOW_ACTOR,
StackingManager::LAYER_ACTIVE_TRANSIENT_WINDOW);
}
}
TransientWindowCollection::TransientWindow*
TransientWindowCollection::FindTransientWindowToFocus() const {
if (stacked_transients_->items().empty())
return NULL;
for (list<TransientWindow*>::const_iterator it =
stacked_transients_->items().begin();
it != stacked_transients_->items().end();
++it) {
if ((*it)->win->wm_state_modal())
return *it;
}
return stacked_transients_->items().front();
}
void TransientWindowCollection::RestackTransientWindowOnTop(
TransientWindow* transient) {
if (transient == stacked_transients_->items().front())
return;
DCHECK(stacked_transients_->Contains(transient));
DCHECK_GT(stacked_transients_->items().size(), 1U);
TransientWindow* transient_to_stack_above =
stacked_transients_->items().front();
stacked_transients_->Remove(transient);
stacked_transients_->AddOnTop(transient);
ApplyStackingForTransientWindow(
transient, transient_to_stack_above->win, StackingManager::ABOVE_SIBLING);
}
} // namespace window_manager