Skip to content

Commit

Permalink
move windows when disconnecting secondary monitors
Browse files Browse the repository at this point in the history
BUG=123160
TEST=root_window_controller_unittest.cc

Review URL: https://chromiumcodereview.appspot.com/10659006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@144467 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
oshima@chromium.org committed Jun 27, 2012
1 parent ba2118a commit f185312
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 11 deletions.
1 change: 1 addition & 0 deletions ash/ash.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@
'launcher/launcher_unittest.cc',
'launcher/launcher_view_unittest.cc',
'monitor/multi_monitor_manager_unittest.cc',
'root_window_controller_unittest.cc',
'screensaver/screensaver_view_unittest.cc',
'shell_unittest.cc',
'system/tray/system_tray_unittest.cc',
Expand Down
6 changes: 4 additions & 2 deletions ash/monitor/monitor_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,12 @@ void MonitorController::OnDisplayRemoved(const gfx::Display& display) {
root_windows_.erase(display.id());
internal::RootWindowController* controller =
wm::GetRootWindowController(root);
if (controller)
if (controller) {
controller->MoveWindowsTo(Shell::GetPrimaryRootWindow());
delete controller;
else
} else {
delete root;
}
}
}

Expand Down
102 changes: 102 additions & 0 deletions ash/root_window_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include "ash/root_window_controller.h"

#include <vector>

#include "ash/shell.h"
#include "ash/shell_factory.h"
#include "ash/shell_window_ids.h"
Expand All @@ -17,12 +19,55 @@
#include "ash/wm/visibility_controller.h"
#include "ash/wm/workspace/workspace_manager.h"
#include "ash/wm/workspace_controller.h"
#include "ui/aura/client/activation_client.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/tooltip_client.h"
#include "ui/aura/focus_manager.h"
#include "ui/aura/root_window.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"

namespace ash {
namespace {

// This class keeps track of whether or not an object has been deleted.
class WindowLifeTracker : public aura::WindowObserver {
public:
WindowLifeTracker() {}
virtual ~WindowLifeTracker() {
for (aura::Window::Windows::iterator iter = tracking_windows_.begin();
iter != tracking_windows_.end(); ++iter) {
(*iter)->RemoveObserver(this);
}
}

// aura::WindowObserver overrides:
virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
aura::Window::Windows::iterator iter =
std::find(tracking_windows_.begin(), tracking_windows_.end(), window);
DCHECK(iter != tracking_windows_.end());
tracking_windows_.erase(iter);
window->RemoveObserver(this);
}

void TrackWindow(aura::Window* window) {
window->AddObserver(this);
tracking_windows_.push_back(window);
}

bool IsWindowAlive(aura::Window* window) {
aura::Window::Windows::iterator iter =
std::find(tracking_windows_.begin(), tracking_windows_.end(), window);
return iter != tracking_windows_.end();
}

private:
std::vector<aura::Window*> tracking_windows_;

DISALLOW_COPY_AND_ASSIGN(WindowLifeTracker);
};

// Creates a new window for use as a container.
aura::Window* CreateContainer(int window_id,
const char* name,
Expand All @@ -37,6 +82,37 @@ aura::Window* CreateContainer(int window_id,
return container;
}

void MoveAllWindows(aura::RootWindow* src,
aura::RootWindow* dst) {
// Windows move only from secondary displays to the primary
// display, so no need to move windows in the containers that are
// available only in the primary display (launcher, panels etc)
const int kContainerIdsToMove[] = {
internal::kShellWindowId_DefaultContainer,
internal::kShellWindowId_AlwaysOnTopContainer,
internal::kShellWindowId_SystemModalContainer,
internal::kShellWindowId_LockSystemModalContainer,
};

for (size_t i = 0; i < arraysize(kContainerIdsToMove); i++) {
int id = kContainerIdsToMove[i];
aura::Window* src_container = Shell::GetContainer(src, id);
aura::Window* dst_container = Shell::GetContainer(dst, id);
aura::Window::Windows children = src_container->children();
for (aura::Window::Windows::iterator iter = children.begin();
iter != children.end(); ++iter) {
aura::Window* window = *iter;
// Don't move modal screen.
if ((id == internal::kShellWindowId_SystemModalContainer ||
id == internal::kShellWindowId_LockSystemModalContainer) &&
window->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_NONE) {
continue;
}
dst_container->AddChild(window);
}
}
}

// Creates each of the special window containers that holds windows of various
// types in the shell UI.
void CreateContainersInRootWindow(aura::RootWindow* root_window) {
Expand Down Expand Up @@ -218,5 +294,31 @@ bool RootWindowController::IsInMaximizedMode() const {
return workspace_controller_->workspace_manager()->IsInMaximizedMode();
}

void RootWindowController::MoveWindowsTo(aura::RootWindow* dst) {
aura::Window* focused = dst->GetFocusManager()->GetFocusedWindow();
aura::client::ActivationClient* activation_client =
aura::client::GetActivationClient(dst);
aura::Window* active = activation_client->GetActiveWindow();
// Deactivate the window to close menu / bubble windows.
activation_client->DeactivateWindow(active);
// Release capture if any.
aura::client::GetCaptureClient(root_window_.get())->
SetCapture(NULL);
WindowLifeTracker tracker;
if (focused)
tracker.TrackWindow(focused);
if (active && focused != active)
tracker.TrackWindow(active);

MoveAllWindows(root_window_.get(), dst);

// Restore focused or active window if it's still alive.
if (focused && tracker.IsWindowAlive(focused) && dst->Contains(focused)) {
dst->GetFocusManager()->SetFocusedWindow(focused, NULL);
} else if (active && tracker.IsWindowAlive(active) && dst->Contains(active)) {
activation_client->ActivateWindow(active);
}
}

} // namespace internal
} // namespace ash
3 changes: 3 additions & 0 deletions ash/root_window_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ class RootWindowController {
// Returns true if the workspace has a maximized or fullscreen window.
bool IsInMaximizedMode() const;

// Moves child windows to |dest|.
void MoveWindowsTo(aura::RootWindow* dest);

private:
scoped_ptr<aura::RootWindow> root_window_;
internal::RootWindowLayoutManager* root_window_layout_;
Expand Down
165 changes: 165 additions & 0 deletions ash/root_window_controller_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// 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/monitor/monitor_controller.h"
#include "ash/monitor/multi_monitor_manager.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/window_util.h"
#include "ui/aura/env.h"
#include "ui/aura/root_window.h"
#include "ui/aura/test/event_generator.h"
#include "ui/aura/window.h"
#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"

namespace ash {
namespace {

class TestDelegate : public views::WidgetDelegateView {
public:
explicit TestDelegate(bool system_modal) : system_modal_(system_modal) {}
virtual ~TestDelegate() {}

// Overridden from views::WidgetDelegate:
virtual views::View* GetContentsView() OVERRIDE {
return this;
}

virtual ui::ModalType GetModalType() const OVERRIDE {
return system_modal_ ? ui::MODAL_TYPE_SYSTEM : ui::MODAL_TYPE_NONE;
}

private:
bool system_modal_;
DISALLOW_COPY_AND_ASSIGN(TestDelegate);
};

views::Widget* CreateTestWidget(const gfx::Rect& bounds) {
views::Widget* widget =
views::Widget::CreateWindowWithBounds(NULL, bounds);
widget->Show();
return widget;
}

views::Widget* CreateModalWidget(const gfx::Rect& bounds) {
views::Widget* widget =
views::Widget::CreateWindowWithBounds(new TestDelegate(true), bounds);
widget->Show();
return widget;
}

aura::Window* GetModalContainer(aura::RootWindow* root_window) {
return Shell::GetContainer(
root_window,
ash::internal::kShellWindowId_SystemModalContainer);
}

} // namespace

namespace test {
class RootWindowControllerTest : public test::AshTestBase {
public:
RootWindowControllerTest() {}
virtual ~RootWindowControllerTest() {}

virtual void SetUp() OVERRIDE {
internal::MonitorController::SetExtendedDesktopEnabled(true);
internal::MonitorController::SetVirtualScreenCoordinatesEnabled(true);
AshTestBase::SetUp();
}

virtual void TearDown() OVERRIDE {
AshTestBase::TearDown();
internal::MonitorController::SetExtendedDesktopEnabled(false);
internal::MonitorController::SetVirtualScreenCoordinatesEnabled(false);
}

private:
DISALLOW_COPY_AND_ASSIGN(RootWindowControllerTest);
};

TEST_F(RootWindowControllerTest, MoveWindows_Basic) {
UpdateMonitor("0+0-600x600,600+0-500x500");
Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
// Emulate virtual screen coordinate system.
root_windows[0]->SetBounds(gfx::Rect(0, 0, 600, 600));
root_windows[1]->SetBounds(gfx::Rect(600, 0, 500, 500));

views::Widget* normal = CreateTestWidget(gfx::Rect(650, 10, 100, 100));
EXPECT_EQ(root_windows[1], normal->GetNativeView()->GetRootWindow());
EXPECT_EQ("100x100", normal->GetWindowScreenBounds().size().ToString());

views::Widget* maximized = CreateTestWidget(gfx::Rect(700, 10, 100, 100));
maximized->Maximize();
EXPECT_EQ(root_windows[1], maximized->GetNativeView()->GetRootWindow());
#if !defined(OS_WIN)
// TODO(oshima): Window reports smaller screen size. Investigate why.
EXPECT_EQ("500x500", maximized->GetWindowScreenBounds().size().ToString());
#endif

views::Widget* minimized = CreateTestWidget(gfx::Rect(800, 10, 100, 100));
minimized->Minimize();
EXPECT_EQ(root_windows[1], minimized->GetNativeView()->GetRootWindow());
EXPECT_EQ("100x100", minimized->GetWindowScreenBounds().size().ToString());

views::Widget* fullscreen = CreateTestWidget(gfx::Rect(900, 10, 100, 100));
fullscreen->SetFullscreen(true);
EXPECT_EQ(root_windows[1], fullscreen->GetNativeView()->GetRootWindow());
#if !defined(OS_WIN)
// TODO(oshima): Window reports smaller screen size. Investigate why.
EXPECT_EQ("500x500", fullscreen->GetWindowScreenBounds().size().ToString());
#endif

UpdateMonitor("0+0-600x600");

EXPECT_EQ(root_windows[0], normal->GetNativeView()->GetRootWindow());
EXPECT_EQ("100x100", normal->GetWindowScreenBounds().size().ToString());

// Maximized area on primary monitor has 2px (given as
// kAutoHideSize in shelf_layout_manager.cc) inset at the bottom.
EXPECT_EQ(root_windows[0], maximized->GetNativeView()->GetRootWindow());
EXPECT_EQ("600x598", maximized->GetWindowScreenBounds().size().ToString());

EXPECT_EQ(root_windows[0], minimized->GetNativeView()->GetRootWindow());
EXPECT_EQ("100x100", minimized->GetWindowScreenBounds().size().ToString());

EXPECT_EQ(root_windows[0], fullscreen->GetNativeView()->GetRootWindow());
EXPECT_TRUE(fullscreen->IsFullscreen());
EXPECT_EQ("600x600", fullscreen->GetWindowScreenBounds().size().ToString());
}

TEST_F(RootWindowControllerTest, MoveWindows_Modal) {
UpdateMonitor("0+0-500x500,500+0-500x500");

Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
// Emulate virtual screen coordinate system.
root_windows[0]->SetBounds(gfx::Rect(0, 0, 500, 500));
root_windows[1]->SetBounds(gfx::Rect(500, 0, 500, 500));

views::Widget* normal = CreateTestWidget(gfx::Rect(300, 10, 100, 100));
EXPECT_EQ(root_windows[0], normal->GetNativeView()->GetRootWindow());
EXPECT_TRUE(wm::IsActiveWindow(normal->GetNativeView()));

views::Widget* modal = CreateModalWidget(gfx::Rect(650, 10, 100, 100));
EXPECT_EQ(root_windows[1], modal->GetNativeView()->GetRootWindow());
EXPECT_TRUE(GetModalContainer(root_windows[1])->Contains(
modal->GetNativeView()));
EXPECT_TRUE(wm::IsActiveWindow(modal->GetNativeView()));

aura::test::EventGenerator generator_1st(root_windows[0]);
generator_1st.ClickLeftButton();
EXPECT_TRUE(wm::IsActiveWindow(modal->GetNativeView()));

UpdateMonitor("0+0-500x500");
EXPECT_EQ(root_windows[0], modal->GetNativeView()->GetRootWindow());
EXPECT_TRUE(wm::IsActiveWindow(modal->GetNativeView()));
generator_1st.ClickLeftButton();
EXPECT_TRUE(wm::IsActiveWindow(modal->GetNativeView()));
}

} // namespace test
} // namespace ash
18 changes: 11 additions & 7 deletions ash/wm/system_modal_container_layout_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -204,13 +204,17 @@ void SystemModalContainerLayoutManager::CreateModalScreen() {

void SystemModalContainerLayoutManager::DestroyModalScreen() {
Shell::GetInstance()->RemoveEnvEventFilter(modality_filter_.get());
ui::ScopedLayerAnimationSettings settings(
modal_screen_->GetNativeView()->layer()->GetAnimator());
modal_screen_->Close();
settings.AddObserver(
CreateHidingWindowAnimationObserver(modal_screen_->GetNativeView()));
modal_screen_->GetNativeView()->layer()->SetOpacity(0.0f);
modal_screen_ = NULL;
// modal_screen_ can be NULL when a root window is shutting down
// and OnWindowDestroying is called first.
if (modal_screen_) {
ui::ScopedLayerAnimationSettings settings(
modal_screen_->GetNativeView()->layer()->GetAnimator());
modal_screen_->Close();
settings.AddObserver(
CreateHidingWindowAnimationObserver(modal_screen_->GetNativeView()));
modal_screen_->GetNativeView()->layer()->SetOpacity(0.0f);
modal_screen_ = NULL;
}
}

} // namespace internal
Expand Down
14 changes: 12 additions & 2 deletions ash/wm/window_animations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ namespace ash {
namespace internal {
namespace {
const float kWindowAnimation_Vertical_TranslateY = 15.f;

bool delayed_old_layer_deletion_in_cross_fade_for_test_ = false;
}

DEFINE_WINDOW_PROPERTY_KEY(WindowVisibilityAnimationType,
Expand Down Expand Up @@ -583,8 +585,12 @@ class CrossFadeObserver : public ui::CompositorObserver,

// ui::ImplicitAnimationObserver overrides:
virtual void OnImplicitAnimationsCompleted() OVERRIDE {
// ImplicitAnimationObserver's base class uses the object after calling
// this function, so we cannot delete |this|.
// ImplicitAnimationObserver's base class uses the object after
// calling this function, so we cannot delete |this|. The |layer_|
// may be gone by the next message loop run when shutting down, so
// clean them up now.
if (!delayed_old_layer_deletion_in_cross_fade_for_test_)
Cleanup();
MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}

Expand Down Expand Up @@ -766,5 +772,9 @@ bool AnimateOnChildWindowVisibilityChanged(aura::Window* window, bool visible) {
}
}

void SetDelayedOldLayerDeletionInCrossFadeForTest(bool value) {
delayed_old_layer_deletion_in_cross_fade_for_test_ = value;
}

} // namespace internal
} // namespace ash
Loading

0 comments on commit f185312

Please sign in to comment.