blob: 48735e6f31096d8e6a544e975c6437fa8fe62c7a [file] [log] [blame]
// Copyright (c) 2010 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/stacking_manager.h"
#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,
Window* win_to_stack_above,
bool constrain_onscreen,
EventConsumer* event_consumer)
: owner_win_(owner_win),
win_to_stack_above_(win_to_stack_above ? win_to_stack_above : owner_win),
event_consumer_(event_consumer),
stacked_transients_(new Stacker<TransientWindow*>),
transient_to_focus_(NULL),
is_hidden_(false),
constrain_onscreen_(constrain_onscreen) {
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, bool stack_directly_above_owner) {
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->SaveOffsetsRelativeToWindow(owner_win_);
transient->centered = false;
} else {
transient->UpdateOffsetsToCenterOverWindow(
owner_win_,
Rect(0, 0, wm()->width(), wm()->height()),
constrain_onscreen_);
transient->centered = true;
}
// 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.
TransientWindow* transient_to_stack_above = NULL;
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()) {
transient_to_stack_above = (*it);
break;
}
}
if (transient_to_stack_above)
stacked_transients_->AddAbove(transient.get(), transient_to_stack_above);
else
stacked_transients_->AddOnBottom(transient.get());
SetPreferredWindowToFocus(transient_win);
ConfigureTransientWindow(transient.get(), 0);
ApplyStackingForTransientWindow(
transient.get(),
transient_to_stack_above ?
transient_to_stack_above->win :
(stack_directly_above_owner ? win_to_stack_above_ : NULL));
if (is_hidden_)
transient_win->HideComposited();
else
transient_win->ShowComposited();
wm()->focus_manager()->UseClickToFocusForWindow(transient_win);
}
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->HideComposited();
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::ConfigureAllWindowsRelativeToOwner(
int anim_ms) {
for (TransientWindowMap::iterator it = transients_.begin();
it != transients_.end(); ++it) {
ConfigureTransientWindow(it->second.get(), anim_ms);
}
}
void TransientWindowCollection::ApplyStackingForAllWindows(
bool stack_directly_above_owner) {
Window* prev_win = stack_directly_above_owner ? win_to_stack_above_ : 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);
prev_win = transient->win;
}
}
void TransientWindowCollection::HandleConfigureRequest(
Window* transient_win,
int req_x, int req_y,
int req_width, int req_height) {
if (is_hidden_) {
DLOG(INFO) << "Ignoring configure request for offscreen transient window "
<< transient_win->xid_str();
transient_win->SendSyntheticConfigureNotify();
return;
}
CHECK(transient_win);
TransientWindow* transient = GetTransientWindow(*transient_win);
CHECK(transient);
// Move and resize the transient window as requested (only let info bubbles
// move themselves).
bool moved = false;
if ((req_x != transient_win->client_x() ||
req_y != transient_win->client_y()) &&
transient_win->type() == chromeos::WM_IPC_WINDOW_CHROME_INFO_BUBBLE) {
transient_win->MoveClient(req_x, req_y);
transient->SaveOffsetsRelativeToWindow(owner_win_);
transient->centered = false;
moved = true;
}
if (req_width != transient_win->client_width() ||
req_height != transient_win->client_height()) {
transient_win->ResizeClient(req_width, req_height, GRAVITY_NORTHWEST);
if (transient->centered) {
transient->UpdateOffsetsToCenterOverWindow(
owner_win_,
Rect(0, 0, wm()->width(), wm()->height()),
constrain_onscreen_);
moved = true;
}
}
if (moved)
ConfigureTransientWindow(transient, 0);
}
void TransientWindowCollection::CloseAllWindows() {
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::Hide() {
is_hidden_ = true;
for (TransientWindowMap::const_iterator it = transients_.begin();
it != transients_.end(); ++it) {
TransientWindow* transient = it->second.get();
transient->win->MoveClientOffscreen();
transient->win->HideComposited();
}
}
void TransientWindowCollection::Restore() {
is_hidden_ = false;
for (TransientWindowMap::const_iterator it = transients_.begin();
it != transients_.end(); ++it) {
TransientWindow* transient = it->second.get();
transient->win->MoveClientToComposited();
transient->win->ShowComposited();
}
}
void
TransientWindowCollection::TransientWindow::UpdateOffsetsToCenterOverWindow(
Window* base_win, const Rect& bounding_rect, bool force_constrain) {
x_offset = (base_win->client_width() - win->client_width()) / 2;
y_offset = (base_win->client_height() - win->client_height()) / 2;
const bool base_within_bounding_rect =
!bounding_rect.empty() &&
base_win->client_x() >= bounding_rect.left() &&
base_win->client_y() >= bounding_rect.top() &&
base_win->client_x() + base_win->client_width() <=
bounding_rect.right() &&
base_win->client_y() + base_win->client_height() <=
bounding_rect.bottom();
// Only honor the bounding rectangle if the base window already falls
// completely inside of it or if we've been told to do so.
if (base_within_bounding_rect || force_constrain) {
if (base_win->client_x() + x_offset + win->client_width() >
bounding_rect.x + bounding_rect.width) {
x_offset = bounding_rect.x + bounding_rect.width -
win->client_width() - base_win->client_x();
}
if (base_win->client_x() + x_offset < bounding_rect.x) {
x_offset = bounding_rect.x - base_win->client_x();
}
if (base_win->client_y() + y_offset + win->client_height() >
bounding_rect.y + bounding_rect.height) {
y_offset = bounding_rect.y + bounding_rect.height -
win->client_height() - base_win->client_y();
}
if (base_win->client_y() + y_offset < bounding_rect.y) {
y_offset = bounding_rect.y - base_win->client_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 (!is_hidden_) {
transient->win->MoveClient(
owner_win_->client_x() + transient->x_offset,
owner_win_->client_y() + transient->y_offset);
}
transient->win->MoveComposited(
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);
}
void TransientWindowCollection::ApplyStackingForTransientWindow(
TransientWindow* transient, Window* other_win) {
DCHECK(transient);
if (other_win) {
transient->win->StackClientAbove(other_win->xid());
transient->win->StackCompositedAbove(other_win->actor(), NULL, false);
} else {
wm()->stacking_manager()->StackWindowAtTopOfLayer(
transient->win, 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);
}
} // namespace window_manager