diff options
26 files changed, 1908 insertions, 68 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp index 0c58a10..ceaa0a9 100644 --- a/ash/ash.gyp +++ b/ash/ash.gyp @@ -346,12 +346,16 @@ 'wm/workspace/snap_types.h', 'wm/workspace/workspace.cc', 'wm/workspace/workspace.h', + 'wm/workspace/workspace2.cc', + 'wm/workspace/workspace2.h', 'wm/workspace/workspace_event_filter.cc', 'wm/workspace/workspace_event_filter.h', 'wm/workspace/workspace_layout_manager.cc', 'wm/workspace/workspace_layout_manager.h', 'wm/workspace/workspace_manager.cc', 'wm/workspace/workspace_manager.h', + 'wm/workspace/workspace_manager2.cc', + 'wm/workspace/workspace_manager2.h', 'wm/workspace/workspace_types.h', 'wm/workspace/workspace_window_resizer.cc', 'wm/workspace/workspace_window_resizer.h', @@ -489,6 +493,7 @@ 'wm/workspace/workspace_event_filter_test_helper.h', 'wm/workspace/workspace_event_filter_unittest.cc', 'wm/workspace/workspace_manager_unittest.cc', + 'wm/workspace/workspace_manager2_unittest.cc', 'wm/workspace/workspace_window_resizer_unittest.cc', ], 'conditions': [ diff --git a/ash/ash_switches.cc b/ash/ash_switches.cc index 4ca3052..5eca1ce 100644 --- a/ash/ash_switches.cc +++ b/ash/ash_switches.cc @@ -13,7 +13,9 @@ const char kAshDebugShortcuts[] = "ash-debug-shortcuts"; // Enables the Oak tree viewer. const char kAshEnableOak[] = "ash-enable-oak"; -// Disables extended desktop. +// Enables Workspace2. +const char kAshEnableWorkspace2[] = "ash-enable-workspace2"; + const char kAshExtendedDesktopDisabled[] = "ash-extended-desktop-disabled"; // Disable using Ash notifications. diff --git a/ash/ash_switches.h b/ash/ash_switches.h index 042afc5..4480359 100644 --- a/ash/ash_switches.h +++ b/ash/ash_switches.h @@ -17,6 +17,7 @@ namespace switches { // Please keep alphabetized. ASH_EXPORT extern const char kAshDebugShortcuts[]; ASH_EXPORT extern const char kAshEnableOak[]; +ASH_EXPORT extern const char kAshEnableWorkspace2[]; ASH_EXPORT extern const char kAshExtendedDesktopDisabled[]; ASH_EXPORT extern const char kAshNotifyDisabled[]; ASH_EXPORT extern const char kAshTouchHud[]; diff --git a/ash/display/screen_position_controller.cc b/ash/display/screen_position_controller.cc index 1deb4e2..c398937 100644 --- a/ash/display/screen_position_controller.cc +++ b/ash/display/screen_position_controller.cc @@ -5,10 +5,12 @@ #include "ash/display/screen_position_controller.h" #include "ash/display/display_controller.h" +#include "ash/root_window_controller.h" #include "ash/shell.h" #include "ash/shell_window_ids.h" #include "ash/wm/system_modal_container_layout_manager.h" #include "ash/wm/window_properties.h" +#include "ash/wm/workspace_controller.h" #include "ui/aura/client/activation_client.h" #include "ui/aura/client/capture_client.h" #include "ui/aura/client/stacking_client.h" @@ -97,8 +99,7 @@ void ScreenPositionController::SetBounds(aura::Window* window, aura::Window* dst_container = NULL; if (dst_root != window->GetRootWindow()) { int container_id = window->parent()->id(); - // All containers that uses screen coordinates must have valid - // window ids. + // All containers that uses screen coordinates must have valid window ids. DCHECK_GE(container_id, 0); // Don't move modal screen. if (!SystemModalContainerLayoutManager::IsModalScreen(window)) @@ -117,6 +118,12 @@ void ScreenPositionController::SetBounds(aura::Window* window, if (active && focused != active) tracker.Add(active); + if (dst_container->id() == kShellWindowId_WorkspaceContainer) { + dst_container = + GetRootWindowController(dst_root)->workspace_controller()-> + GetParentForNewWindow(window); + } + dst_container->AddChild(window); MoveAllTransientChildrenToNewRoot(display, window); diff --git a/ash/root_window_controller.cc b/ash/root_window_controller.cc index 15fba6b..6cb6c08 100644 --- a/ash/root_window_controller.cc +++ b/ash/root_window_controller.cc @@ -50,19 +50,62 @@ 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) +// Returns all the children of the workspace windows, eg the standard top-level +// windows. +std::vector<aura::Window*> GetWorkspaceWindows(aura::RootWindow* root) { + using aura::Window; + + std::vector<Window*> windows; + Window* container = Shell::GetContainer( + root, internal::kShellWindowId_DefaultContainer); + for (Window::Windows::const_reverse_iterator i = + container->children().rbegin(); + i != container->children().rend(); ++i) { + Window* workspace_window = *i; + if (workspace_window->id() == internal::kShellWindowId_WorkspaceContainer) { + windows.insert(windows.end(), workspace_window->children().begin(), + workspace_window->children().end()); + } + } + return windows; +} + +// Reparents |window| to |new_parent|. +void ReparentWindow(aura::Window* window, aura::Window* new_parent) { + // Update the restore bounds to make it relative to the display. + gfx::Rect restore_bounds(GetRestoreBoundsInParent(window)); + new_parent->AddChild(window); + if (!restore_bounds.IsEmpty()) + SetRestoreBoundsInParent(window, restore_bounds); +} + +// Reparents the appropriate set of windows from |src| to |dst|. +void ReparentAllWindows(aura::RootWindow* src, aura::RootWindow* dst) { + // Set of windows to move. const int kContainerIdsToMove[] = { internal::kShellWindowId_DefaultContainer, internal::kShellWindowId_AlwaysOnTopContainer, internal::kShellWindowId_SystemModalContainer, internal::kShellWindowId_LockSystemModalContainer, }; + // For Workspace2 we need to manually reparent the windows. This way + // Workspace2 can move the windows to the appropriate workspace. + if (internal::WorkspaceController::IsWorkspace2Enabled()) { + std::vector<aura::Window*> windows(GetWorkspaceWindows(src)); + internal::WorkspaceController* workspace_controller = + GetRootWindowController(dst)->workspace_controller(); + for (size_t i = 0; i < windows.size(); ++i) { + aura::Window* new_parent = + workspace_controller->GetParentForNewWindow(windows[i]); + ReparentWindow(windows[i], new_parent); + } + } for (size_t i = 0; i < arraysize(kContainerIdsToMove); i++) { int id = kContainerIdsToMove[i]; + if (id == internal::kShellWindowId_DefaultContainer && + internal::WorkspaceController::IsWorkspace2Enabled()) + continue; + aura::Window* src_container = Shell::GetContainer(src, id); aura::Window* dst_container = Shell::GetContainer(dst, id); aura::Window::Windows children = src_container->children(); @@ -73,11 +116,7 @@ void MoveAllWindows(aura::RootWindow* src, if (internal::SystemModalContainerLayoutManager::IsModalScreen(window)) continue; - // Update the restore bounds to make it relative to the display. - gfx::Rect restore_bounds(GetRestoreBoundsInParent(window)); - dst_container->AddChild(window); - if (!restore_bounds.IsEmpty()) - SetRestoreBoundsInParent(window, restore_bounds); + ReparentWindow(window, dst_container); } } } @@ -319,7 +358,7 @@ void RootWindowController::MoveWindowsTo(aura::RootWindow* dst) { if (active && focused != active) tracker.Add(active); - MoveAllWindows(root_window_.get(), dst); + ReparentAllWindows(root_window_.get(), dst); // Restore focused or active window if it's still alive. if (focused && tracker.Contains(focused) && dst->Contains(focused)) { diff --git a/ash/shell/window_watcher.cc b/ash/shell/window_watcher.cc index dfd54ff..a85d8f6 100644 --- a/ash/shell/window_watcher.cc +++ b/ash/shell/window_watcher.cc @@ -13,17 +13,45 @@ namespace ash { namespace shell { +class WindowWatcher::WorkspaceWindowWatcher : public aura::WindowObserver { + public: + explicit WorkspaceWindowWatcher(WindowWatcher* watcher) : watcher_(watcher) { + watcher_->window_->AddObserver(this); + for (size_t i = 0; i < watcher_->window_->children().size(); ++i) + watcher_->window_->children()[i]->AddObserver(watcher_); + } + + virtual ~WorkspaceWindowWatcher() { + watcher_->window_->RemoveObserver(this); + for (size_t i = 0; i < watcher_->window_->children().size(); ++i) + watcher_->window_->children()[i]->RemoveObserver(watcher_); + } + + virtual void OnWindowAdded(aura::Window* new_window) OVERRIDE { + new_window->AddObserver(watcher_); + } + + virtual void OnWillRemoveWindow(aura::Window* window) OVERRIDE { + DCHECK(window->children().empty()); + window->RemoveObserver(watcher_); + } + + private: + WindowWatcher* watcher_; + + DISALLOW_COPY_AND_ASSIGN(WorkspaceWindowWatcher); +}; + WindowWatcher::WindowWatcher() : window_(ash::Shell::GetInstance()->launcher()->window_container()), panel_container_(ash::Shell::GetContainer( Shell::GetPrimaryRootWindow(), internal::kShellWindowId_PanelContainer)) { - window_->AddObserver(this); + workspace_window_watcher_.reset(new WorkspaceWindowWatcher(this)); panel_container_->AddObserver(this); } WindowWatcher::~WindowWatcher() { - window_->RemoveObserver(this); panel_container_->RemoveObserver(this); } diff --git a/ash/shell/window_watcher.h b/ash/shell/window_watcher.h index 42c7cdc..ee81b5a 100644 --- a/ash/shell/window_watcher.h +++ b/ash/shell/window_watcher.h @@ -10,6 +10,7 @@ #include "ash/launcher/launcher_types.h" #include "base/compiler_specific.h" #include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "ui/aura/window_observer.h" namespace aura { @@ -19,6 +20,8 @@ class Window; namespace ash { namespace shell { +// TODO(sky): fix this class, its a bit broke with workspace2. + // WindowWatcher is responsible for listening for newly created windows and // creating items on the Launcher for them. class WindowWatcher : public aura::WindowObserver { @@ -34,6 +37,8 @@ class WindowWatcher : public aura::WindowObserver { virtual void OnWillRemoveWindow(aura::Window* window) OVERRIDE; private: + class WorkspaceWindowWatcher; + typedef std::map<ash::LauncherID, aura::Window*> IDToWindow; // Window watching for newly created windows to be added to. @@ -44,6 +49,8 @@ class WindowWatcher : public aura::WindowObserver { // Maps from window to the id we gave it. IDToWindow id_to_window_; + scoped_ptr<WorkspaceWindowWatcher> workspace_window_watcher_; + DISALLOW_COPY_AND_ASSIGN(WindowWatcher); }; diff --git a/ash/shell_window_ids.h b/ash/shell_window_ids.h index 8ac0f18..d837725 100644 --- a/ash/shell_window_ids.h +++ b/ash/shell_window_ids.h @@ -34,54 +34,63 @@ const int kShellWindowId_UnparentedControlContainer = 3; // The desktop background window. const int kShellWindowId_DesktopBackgroundContainer = 4; +// TODO(sky): rename kShellWindowId_DefaultContainer when Workspace2 is the +// default. + // The container for standard top-level windows. +// WARNING: when Workspace2 is enabled the only children of +// kShellWindowId_DefaultContainer are kShellWindowId_WorkspaceContainer. const int kShellWindowId_DefaultContainer = 5; +// Used by Worskpace2 for each workspace. Contains standard top-level windows. +// WARNING: there may be more than one container with this id. +const int kShellWindowId_WorkspaceContainer = 6; + // The container for top-level windows with the 'always-on-top' flag set. -const int kShellWindowId_AlwaysOnTopContainer = 6; +const int kShellWindowId_AlwaysOnTopContainer = 7; // The container for panel windows. -const int kShellWindowId_PanelContainer = 7; +const int kShellWindowId_PanelContainer = 8; // The container for the launcher. -const int kShellWindowId_LauncherContainer = 8; +const int kShellWindowId_LauncherContainer = 9; // The container for the app list. -const int kShellWindowId_AppListContainer = 9; +const int kShellWindowId_AppListContainer = 10; // The container for user-specific modal windows. -const int kShellWindowId_SystemModalContainer = 10; +const int kShellWindowId_SystemModalContainer = 11; // The container for input method components such like candidate windows. They // are almost panels but have no activations/focus, and they should appear over // the AppList and SystemModal dialogs. -const int kShellWindowId_InputMethodContainer = 11; +const int kShellWindowId_InputMethodContainer = 12; // The container for the lock screen background. -const int kShellWindowId_LockScreenBackgroundContainer = 12; +const int kShellWindowId_LockScreenBackgroundContainer = 13; // The container for the lock screen. -const int kShellWindowId_LockScreenContainer = 13; +const int kShellWindowId_LockScreenContainer = 14; // The container for the lock screen modal windows. -const int kShellWindowId_LockSystemModalContainer = 14; +const int kShellWindowId_LockSystemModalContainer = 15; // The container for the status area. -const int kShellWindowId_StatusContainer = 15; +const int kShellWindowId_StatusContainer = 16; // The container for menus. -const int kShellWindowId_MenuContainer = 16; +const int kShellWindowId_MenuContainer = 17; // The container for drag/drop images and tooltips. -const int kShellWindowId_DragImageAndTooltipContainer = 17; +const int kShellWindowId_DragImageAndTooltipContainer = 18; // The container for bubbles briefly overlaid onscreen to show settings changes // (volume, brightness, etc.). -const int kShellWindowId_SettingBubbleContainer = 18; +const int kShellWindowId_SettingBubbleContainer = 19; // The container for special components overlaid onscreen, such as the // region selector for partial screenshots. -const int kShellWindowId_OverlayContainer = 19; +const int kShellWindowId_OverlayContainer = 20; } // namespace internal diff --git a/ash/wm/activation_controller.cc b/ash/wm/activation_controller.cc index 3685d79..6859dd3 100644 --- a/ash/wm/activation_controller.cc +++ b/ash/wm/activation_controller.cc @@ -4,10 +4,13 @@ #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/property_util.h" #include "ash/wm/window_modality_controller.h" #include "ash/wm/window_util.h" +#include "ash/wm/workspace_controller.h" #include "base/auto_reset.h" #include "ui/aura/client/activation_change_observer.h" #include "ui/aura/client/activation_delegate.h" @@ -33,6 +36,7 @@ const int kWindowContainerIds[] = { kShellWindowId_SystemModalContainer, kShellWindowId_AlwaysOnTopContainer, kShellWindowId_AppListContainer, + // TODO(sky): defaultcontainer shouldn't be in the list with workspace2. kShellWindowId_DefaultContainer, // Panel, launcher and status are intentionally checked after other @@ -46,9 +50,18 @@ const int kWindowContainerIds[] = { // 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) { + // TODO(sky): straighten this out when workspace2 is the default. + // kShellWindowId_WorkspaceContainer isn't in |kWindowContainerIds| since it + // needs to be special cased in GetTopmostWindowToActivate(). + if (window->id() == kShellWindowId_WorkspaceContainer) + return true; + for (size_t i = 0; i < arraysize(kWindowContainerIds); i++) { - if (window->id() == kWindowContainerIds[i]) + if (window->id() == kWindowContainerIds[i] && + (window->id() != kShellWindowId_DefaultContainer || + !WorkspaceController::IsWorkspace2Enabled())) { return true; + } } return false; } @@ -78,7 +91,9 @@ enum ActivateVisibilityType { bool VisibilityMatches(aura::Window* window, ActivateVisibilityType type) { bool visible = (type == CURRENT_VISIBILITY) ? window->IsVisible() : window->TargetVisibility(); - return visible || wm::IsWindowMinimized(window); + return visible || wm::IsWindowMinimized(window) || + (window->TargetVisibility() && + window->parent()->id() == kShellWindowId_WorkspaceContainer); } // Returns true if |window| can be activated or deactivated. @@ -250,6 +265,19 @@ void ActivationController::ActivateWindowWithEvent(aura::Window* window, if (window && !CanActivateWindowWithEvent(window, event, CURRENT_VISIBILITY)) return; + // Make sure the workspace manager switches to the workspace for window. + // Without this CanReceiveEvents() below returns false and activation never + // changes. CanReceiveEvents() returns false if |window| isn't in the active + // workspace, in which case its parent is not visible. + // TODO(sky): if I instead change the opacity of the parent this isn't an + // issue, but will make animations trickier... Consider which one is better. + if (window) { + internal::RootWindowController* root_window_controller = + GetRootWindowController(window->GetRootWindow()); + root_window_controller->workspace_controller()-> + SetActiveWorkspaceByWindow(window); + } + // Restore minimized window. This needs to be done before CanReceiveEvents() // is called as that function checks window visibility. if (window && wm::IsWindowMinimized(window)) @@ -321,12 +349,10 @@ aura::Window* ActivationController::GetTopmostWindowToActivate( aura::Window* window = NULL; for (; !window && current_container_index < arraysize(kWindowContainerIds); current_container_index++) { - aura::Window::Windows containers = Shell::GetAllContainers(kWindowContainerIds[current_container_index]); for (aura::Window::Windows::const_iterator iter = containers.begin(); - iter != containers.end(); - ++iter) { + iter != containers.end() && !window; ++iter) { window = GetTopmostWindowToActivateInContainer((*iter), ignore); } } @@ -336,6 +362,21 @@ aura::Window* ActivationController::GetTopmostWindowToActivate( aura::Window* ActivationController::GetTopmostWindowToActivateInContainer( aura::Window* container, aura::Window* ignore) const { + // Workspace2 has an extra level of windows that needs to be special cased. + if (container->id() == kShellWindowId_DefaultContainer && + WorkspaceController::IsWorkspace2Enabled()) { + for (aura::Window::Windows::const_reverse_iterator i = + container->children().rbegin(); + i != container->children().rend(); ++i) { + if ((*i)->IsVisible()) { + aura::Window* window = GetTopmostWindowToActivateInContainer( + *i, ignore); + if (window) + return window; + } + } + return NULL; + } for (aura::Window::Windows::const_reverse_iterator i = container->children().rbegin(); i != container->children().rend(); diff --git a/ash/wm/always_on_top_controller.cc b/ash/wm/always_on_top_controller.cc index 2c9e920..8d148db 100644 --- a/ash/wm/always_on_top_controller.cc +++ b/ash/wm/always_on_top_controller.cc @@ -4,6 +4,9 @@ #include "ash/wm/always_on_top_controller.h" +#include "ash/root_window_controller.h" +#include "ash/wm/property_util.h" +#include "ash/wm/workspace_controller.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" @@ -22,31 +25,40 @@ AlwaysOnTopController::~AlwaysOnTopController() { always_on_top_container_->RemoveObserver(this); } -void AlwaysOnTopController::SetContainers(aura::Window* default_container, +void AlwaysOnTopController::SetContainers( + aura::Window* default_container, aura::Window* always_on_top_container) { + if (WorkspaceController::IsWorkspace2Enabled()) + default_container = NULL; + // Both containers should have no children. - DCHECK(default_container->children().empty()); DCHECK(always_on_top_container->children().empty()); // We are not handling any containers yet. DCHECK(default_container_ == NULL && always_on_top_container_ == NULL); default_container_ = default_container; - default_container_->AddObserver(this); + if (default_container_) + default_container_->AddObserver(this); always_on_top_container_ = always_on_top_container; always_on_top_container_->AddObserver(this); } aura::Window* AlwaysOnTopController::GetContainer(aura::Window* window) const { - DCHECK(default_container_ && always_on_top_container_); - return !window->GetProperty(aura::client::kAlwaysOnTopKey) ? - default_container_ : always_on_top_container_; + DCHECK(always_on_top_container_ && + (default_container_ || WorkspaceController::IsWorkspace2Enabled())); + if (window->GetProperty(aura::client::kAlwaysOnTopKey)) + return always_on_top_container_; + if (default_container_) + return default_container_; + return GetRootWindowController(always_on_top_container_->GetRootWindow())-> + workspace_controller()->GetParentForNewWindow(window); } void AlwaysOnTopController::OnWindowAdded(aura::Window* child) { // Observe direct child of the containers. - if (child->parent() == default_container_ || + if ((default_container_ && child->parent() == default_container_) || child->parent() == always_on_top_container_) { child->AddObserver(this); } diff --git a/ash/wm/always_on_top_controller.h b/ash/wm/always_on_top_controller.h index 22fe342..386ce27 100644 --- a/ash/wm/always_on_top_controller.h +++ b/ash/wm/always_on_top_controller.h @@ -41,6 +41,8 @@ class AlwaysOnTopController : public aura::WindowObserver { intptr_t old) OVERRIDE; virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE; + // TODO(sky): remove this after workspace2 is the default. + // Is NULL if workspace2 is enabled. aura::Window* default_container_; aura::Window* always_on_top_container_; diff --git a/ash/wm/base_layout_manager.cc b/ash/wm/base_layout_manager.cc index cd582ad..bf4e47c 100644 --- a/ash/wm/base_layout_manager.cc +++ b/ash/wm/base_layout_manager.cc @@ -11,6 +11,7 @@ #include "ash/wm/window_animations.h" #include "ash/wm/window_properties.h" #include "ash/wm/window_util.h" +#include "ash/wm/workspace_controller.h" #include "base/command_line.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/root_window.h" @@ -143,7 +144,9 @@ void BaseLayoutManager::OnWindowPropertyChanged(aura::Window* window, SetRestoreBoundsInParent(window, window->bounds()); } // Minimized state handles its own animations. - bool animate = (old_state != ui::SHOW_STATE_MINIMIZED); + // TODO(sky): get animations to work with Workspace2. + bool animate = (old_state != ui::SHOW_STATE_MINIMIZED) && + !WorkspaceController::IsWorkspace2Enabled(); UpdateBoundsFromShowState(window, animate); ShowStateChanged(window, old_state); } diff --git a/ash/wm/window_cycle_controller.cc b/ash/wm/window_cycle_controller.cc index 3223018..9f12a13 100644 --- a/ash/wm/window_cycle_controller.cc +++ b/ash/wm/window_cycle_controller.cc @@ -11,6 +11,7 @@ #include "ash/wm/activation_controller.h" #include "ash/wm/window_cycle_list.h" #include "ash/wm/window_util.h" +#include "ash/wm/workspace_controller.h" #include "ui/aura/event_filter.h" #include "ui/aura/root_window.h" #include "ui/base/event.h" @@ -84,6 +85,34 @@ ui::GestureStatus WindowCycleEventFilter::PreHandleGestureEvent( return ui::GESTURE_STATUS_UNKNOWN; // Not handled. } +// Adds all the children of |window| to |windows|. +void AddAllChildren(aura::Window* window, + WindowCycleList::WindowList* windows) { + const WindowCycleList::WindowList& children(window->children()); + windows->insert(windows->end(), children.begin(), children.end()); +} + +// Adds all the children of all of |window|s children to |windows|. +void AddWorkspace2Children(aura::Window* window, + WindowCycleList::WindowList* windows) { + for (size_t i = 0; i < window->children().size(); ++i) + AddAllChildren(window->children()[i], windows); +} + +// Adds the windows that can be cycled through for the specified window id to +// |windows|. +void AddCycleWindows(aura::RootWindow* root, + int container_id, + WindowCycleList::WindowList* windows) { + aura::Window* container = Shell::GetContainer(root, container_id); + if (container_id == internal::kShellWindowId_DefaultContainer && + internal::WorkspaceController::IsWorkspace2Enabled()) { + AddWorkspace2Children(container, windows); + } else { + AddAllChildren(container, windows); + } +} + } // namespace ////////////////////////////////////////////////////////////////////////////// @@ -91,7 +120,7 @@ ui::GestureStatus WindowCycleEventFilter::PreHandleGestureEvent( WindowCycleController::WindowCycleController( internal::ActivationController* activation_controller) - : activation_controller_(activation_controller) { + : activation_controller_(activation_controller) { activation_controller_->AddObserver(this); } @@ -104,6 +133,16 @@ WindowCycleController::~WindowCycleController() { if (container) container->RemoveObserver(this); } + aura::Window* default_container = + Shell::GetContainer(*iter, internal::kShellWindowId_DefaultContainer); + if (default_container) { + for (size_t i = 0; i < default_container->children().size(); ++i) { + aura::Window* workspace_window = default_container->children()[i]; + DCHECK_EQ(internal::kShellWindowId_WorkspaceContainer, + workspace_window->id()); + workspace_window->RemoveObserver(this); + } + } } activation_controller_->RemoveObserver(this); @@ -152,26 +191,19 @@ std::vector<aura::Window*> WindowCycleController::BuildWindowList( WindowCycleList::WindowList windows; Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); + aura::RootWindow* active_root = Shell::GetActiveRootWindow(); for (Shell::RootWindowList::const_iterator iter = root_windows.begin(); iter != root_windows.end(); ++iter) { - if (*iter == Shell::GetActiveRootWindow()) + if (*iter == active_root) continue; - for (size_t i = 0; i < arraysize(kContainerIds); ++i) { - aura::Window* container = Shell::GetContainer(*iter, kContainerIds[i]); - WindowCycleList::WindowList children = container->children(); - windows.insert(windows.end(), children.begin(), children.end()); - } + for (size_t i = 0; i < arraysize(kContainerIds); ++i) + AddCycleWindows(*iter, kContainerIds[i], &windows); } // Add windows in the active root windows last so that the topmost window // in the active root window becomes the front of the list. - for (size_t i = 0; i < arraysize(kContainerIds); ++i) { - aura::Window* container = - Shell::GetContainer(Shell::GetActiveRootWindow(), kContainerIds[i]); - - WindowCycleList::WindowList children = container->children(); - windows.insert(windows.end(), children.begin(), children.end()); - } + for (size_t i = 0; i < arraysize(kContainerIds); ++i) + AddCycleWindows(active_root, kContainerIds[i], &windows); // Removes unfocusable windows. WindowCycleList::WindowList::iterator last = @@ -209,6 +241,16 @@ void WindowCycleController::OnRootWindowAdded(aura::RootWindow* root_window) { Shell::GetContainer(root_window, kContainerIds[i]); container->AddObserver(this); } + + aura::Window* default_container = + Shell::GetContainer(root_window, + internal::kShellWindowId_DefaultContainer); + for (size_t i = 0; i < default_container->children().size(); ++i) { + aura::Window* workspace_window = default_container->children()[i]; + DCHECK_EQ(internal::kShellWindowId_WorkspaceContainer, + workspace_window->id()); + workspace_window->AddObserver(this); + } } ////////////////////////////////////////////////////////////////////////////// @@ -247,7 +289,7 @@ bool WindowCycleController::IsTrackedContainer(aura::Window* window) { return true; } } - return false; + return window->id() == internal::kShellWindowId_WorkspaceContainer; } void WindowCycleController::InstallEventFilter() { @@ -263,8 +305,15 @@ void WindowCycleController::OnWindowActivated(aura::Window* active, } } +void WindowCycleController::OnWindowAdded(aura::Window* window) { + if (window->id() == internal::kShellWindowId_WorkspaceContainer) + window->AddObserver(this); +} + void WindowCycleController::OnWillRemoveWindow(aura::Window* window) { mru_windows_.remove(window); + if (window->id() == internal::kShellWindowId_WorkspaceContainer) + window->RemoveObserver(this); } void WindowCycleController::OnWindowDestroying(aura::Window* window) { diff --git a/ash/wm/window_cycle_controller.h b/ash/wm/window_cycle_controller.h index bd77b9f..76836cb 100644 --- a/ash/wm/window_cycle_controller.h +++ b/ash/wm/window_cycle_controller.h @@ -100,6 +100,7 @@ class ASH_EXPORT WindowCycleController aura::Window* old_active) OVERRIDE; // Overridden from WindowObserver: + virtual void OnWindowAdded(aura::Window* window) OVERRIDE; virtual void OnWillRemoveWindow(aura::Window* window) OVERRIDE; virtual void OnWindowDestroying(aura::Window* window) OVERRIDE; diff --git a/ash/wm/window_util.h b/ash/wm/window_util.h index e994f4d..756e212 100644 --- a/ash/wm/window_util.h +++ b/ash/wm/window_util.h @@ -17,9 +17,6 @@ class Layer; } namespace ash { -namespace internal { -class RootWindowController; -} namespace wm { // Convenience setters/getters for |aura::client::kRootWindowActiveWindow|. diff --git a/ash/wm/workspace/workspace2.cc b/ash/wm/workspace/workspace2.cc new file mode 100644 index 0000000..9d585b5 --- /dev/null +++ b/ash/wm/workspace/workspace2.cc @@ -0,0 +1,86 @@ +// 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/workspace/workspace2.h" + +#include "ash/shell_window_ids.h" +#include "ash/wm/property_util.h" +#include "ash/wm/window_properties.h" +#include "ash/wm/window_util.h" +#include "ash/wm/workspace/workspace_event_filter.h" +#include "ash/wm/workspace/workspace_manager2.h" +#include "ui/aura/window.h" + +namespace ash { +namespace internal { + +Workspace2::Workspace2(WorkspaceManager2* manager, + aura::Window* parent, + bool is_maximized) + : is_maximized_(is_maximized), + workspace_manager_(manager), + window_(new aura::Window(NULL)), + event_filter_(new WorkspaceEventFilter(window_)) { + window_->set_id(kShellWindowId_WorkspaceContainer); + window_->SetName("WorkspaceContainer"); + window_->Init(ui::LAYER_NOT_DRAWN); + window_->Show(); + window_->SetParent(parent); + window_->SetEventFilter(event_filter_); + window_->SetProperty(internal::kUsesScreenCoordinatesKey, true); +} + +Workspace2::~Workspace2() { + // ReleaseWindow() should have been invoked before we're deleted. + DCHECK(!window_); +} + +aura::Window* Workspace2::ReleaseWindow() { + // Remove the LayoutManager and EventFilter as they refer back to us and/or + // WorkspaceManager. + window_->SetLayoutManager(NULL); + window_->SetEventFilter(NULL); + aura::Window* window = window_; + window_ = NULL; + event_filter_ = NULL; + return window; +} + +void Workspace2::SetGridSize(int grid_size) { + event_filter_->set_grid_size(grid_size); +} + +bool Workspace2::ShouldMoveToPending() const { + if (!is_maximized_) + return false; + + bool has_visible_non_maximized_window = false; + for (size_t i = 0; i < window_->children().size(); ++i) { + aura::Window* child(window_->children()[i]); + if (!child->TargetVisibility() || wm::IsWindowMinimized(child)) + continue; + if (WorkspaceManager2::IsMaximized(child)) + return false; + + if (GetTrackedByWorkspace(child) && !GetPersistsAcrossAllWorkspaces(child)) + has_visible_non_maximized_window = true; + } + return !has_visible_non_maximized_window; +} + +int Workspace2::GetNumMaximizedWindows() const { + int count = 0; + for (size_t i = 0; i < window_->children().size(); ++i) { + aura::Window* child = window_->children()[i]; + if (WorkspaceManager2::IsMaximized(child) || + WorkspaceManager2::WillRestoreMaximized(child)) { + if (++count == 2) + return count; + } + } + return count; +} + +} // namespace internal +} // namespace ash diff --git a/ash/wm/workspace/workspace2.h b/ash/wm/workspace/workspace2.h new file mode 100644 index 0000000..3045424 --- /dev/null +++ b/ash/wm/workspace/workspace2.h @@ -0,0 +1,74 @@ +// 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. + +#ifndef ASH_WM_WORKSPACE_WORKSPACE2_H_ +#define ASH_WM_WORKSPACE_WORKSPACE_H_ + +#include <vector> + +#include "ash/ash_export.h" +#include "base/basictypes.h" + +namespace aura { +class Window; +} + +namespace gfx { +class Rect; +} + +namespace ash { +namespace internal { + +class WorkspaceEventFilter; +class WorkspaceManager2; + +// Workspace is used to maintain either a single maximized windows (including +// transients and other windows) or any number of windows (for the +// desktop). Workspace is used by WorkspaceManager to manage a set of windows. +class ASH_EXPORT Workspace2 { + public: + Workspace2(WorkspaceManager2* manager, + aura::Window* parent, + bool is_maximized); + ~Workspace2(); + + // Resets state. This should be used before destroying the Workspace. + aura::Window* ReleaseWindow(); + + bool is_maximized() const { return is_maximized_; } + + aura::Window* window() { return window_; } + + WorkspaceManager2* workspace_manager() { return workspace_manager_; } + + void SetGridSize(int grid_size); + + // Returns true if the Workspace should be moved to pending. This is true + // if there are no visible maximized windows. + bool ShouldMoveToPending() const; + + // Returns the number of maximized windows (including minimized windows that + // would be maximized on restore). This does not consider visibility. + int GetNumMaximizedWindows() const; + + private: + // Is this a workspace for maximized windows? + const bool is_maximized_; + + WorkspaceManager2* workspace_manager_; + + // Our Window, owned by |parent| passed to the constructor. + aura::Window* window_; + + // Owned by |window_|. + WorkspaceEventFilter* event_filter_; + + DISALLOW_COPY_AND_ASSIGN(Workspace2); +}; + +} // namespace internal +} // namespace ash + +#endif // ASH_WM_WORKSPACE_WORKSPACE2_H_ diff --git a/ash/wm/workspace/workspace_manager2.cc b/ash/wm/workspace/workspace_manager2.cc new file mode 100644 index 0000000..385e346 --- /dev/null +++ b/ash/wm/workspace/workspace_manager2.cc @@ -0,0 +1,522 @@ +// 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/workspace/workspace_manager2.h" + +#include <algorithm> +#include <functional> + +#include "ash/screen_ash.h" +#include "ash/shell.h" +#include "ash/shell_window_ids.h" +#include "ash/wm/always_on_top_controller.h" +#include "ash/wm/base_layout_manager.h" +#include "ash/wm/property_util.h" +#include "ash/wm/shelf_layout_manager.h" +#include "ash/wm/window_animations.h" +#include "ash/wm/window_properties.h" +#include "ash/wm/window_util.h" +#include "ash/wm/workspace/workspace2.h" +#include "base/auto_reset.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/stringprintf.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/env.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window.h" +#include "ui/aura/window_property.h" +#include "ui/base/ui_base_types.h" +#include "ui/compositor/layer.h" +#include "ui/compositor/layer_animator.h" +#include "ui/compositor/scoped_layer_animation_settings.h" +#include "ui/gfx/screen.h" +#include "ui/gfx/transform.h" + +DECLARE_WINDOW_PROPERTY_TYPE(ash::internal::Workspace2*); + +using aura::Window; + +namespace ash { +namespace internal { + +DEFINE_WINDOW_PROPERTY_KEY(Workspace2*, kWorkspaceKey, NULL); + +namespace { + +// Changes the parent of |window| and all its transient children to +// |new_parent|. If |stack_beneach| is non-NULL all the windows are stacked +// beneath it. +void ReparentWindow(Window* window, + Window* new_parent, + Window* stack_beneath) { + window->SetParent(new_parent); + if (stack_beneath) + new_parent->StackChildBelow(window, stack_beneath); + for (size_t i = 0; i < window->transient_children().size(); ++i) + ReparentWindow(window->transient_children()[i], new_parent, stack_beneath); +} + +// Workspace ------------------------------------------------------------------- + +// LayoutManager installed on the parent window of all the Workspace windows (eg +// |WorkspaceManager2::contents_view_|). +class WorkspaceManagerLayoutManager2 : public BaseLayoutManager { + public: + WorkspaceManagerLayoutManager2(Window* window) + : BaseLayoutManager(window->GetRootWindow()), + window_(window) { + } + virtual ~WorkspaceManagerLayoutManager2() {} + + // Overridden from BaseWorkspaceLayoutManager: + virtual void OnWindowResized() OVERRIDE { + for (size_t i = 0; i < window_->children().size(); ++i) + window_->children()[i]->SetBounds(gfx::Rect(window_->bounds().size())); + } + virtual void OnWindowAddedToLayout(Window* child) OVERRIDE { + // Only workspaces should be added as children. + DCHECK_EQ(kShellWindowId_WorkspaceContainer, child->id()); + child->SetBounds(gfx::Rect(window_->bounds().size())); + } + + private: + Window* window_; + + DISALLOW_COPY_AND_ASSIGN(WorkspaceManagerLayoutManager2); +}; + +} // namespace + +// WorkspaceLayoutManager ------------------------------------------------------ + +// LayoutManager installed on the window each workspace contains. +class WorkspaceManager2::WorkspaceLayoutManager : public BaseLayoutManager { + public: + WorkspaceLayoutManager(aura::RootWindow* root_window, Workspace2* workspace) + : BaseLayoutManager(root_window), + workspace_(workspace) { + } + virtual ~WorkspaceLayoutManager() { + } + + // Overridden from BaseWorkspaceLayoutManager: + virtual void OnWindowAddedToLayout(Window* child) OVERRIDE { + BaseLayoutManager::OnWindowAddedToLayout(child); + child->SetProperty(kWorkspaceKey, workspace_); + workspace_manager()->OnWindowAddedToWorkspace(workspace_, child); + } + + virtual void OnWillRemoveWindowFromLayout(Window* child) OVERRIDE { + BaseLayoutManager::OnWillRemoveWindowFromLayout(child); + child->ClearProperty(kWorkspaceKey); + } + + virtual void OnWindowRemovedFromLayout(Window* child) OVERRIDE { + BaseLayoutManager::OnWindowRemovedFromLayout(child); + workspace_manager()->OnWindowRemovedFromWorkspace(workspace_, child); + } + + virtual void OnChildWindowVisibilityChanged(Window* child, + bool visibile) OVERRIDE { + BaseLayoutManager::OnChildWindowVisibilityChanged(child, visibile); + workspace_manager()->OnWorkspaceChildWindowVisibilityChanged(workspace_, + child); + } + + virtual void SetChildBounds(Window* child, + const gfx::Rect& requested_bounds) OVERRIDE { + BaseLayoutManager::SetChildBounds(child, requested_bounds); + workspace_manager()->OnWorkspaceWindowChildBoundsChanged(workspace_, child); + } + + + // Overriden from WindowObserver: + virtual void OnWindowPropertyChanged(Window* window, + const void* key, + intptr_t old) { + BaseLayoutManager::OnWindowPropertyChanged(window, key, old); + + if (key == aura::client::kAlwaysOnTopKey && + window->GetProperty(aura::client::kAlwaysOnTopKey)) { + internal::AlwaysOnTopController* controller = + window->GetRootWindow()->GetProperty( + internal::kAlwaysOnTopControllerKey); + controller->GetContainer(window)->AddChild(window); + } + } + + protected: + // Overriden from WindowObserver: + virtual void ShowStateChanged(Window* window, + ui::WindowShowState last_show_state) OVERRIDE { + // NOTE: we can't use BaseLayoutManager::ShowStateChanged() as we need to + // forward to WorkspaceManager before the window is hidden. + if (wm::IsWindowMinimized(window)) { + // Save the previous show state so that we can correctly restore it. + window->SetProperty(internal::kRestoreShowStateKey, last_show_state); + SetWindowVisibilityAnimationType( + window, WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE); + + workspace_manager()->OnWorkspaceWindowShowStateChanged( + workspace_, window, last_show_state); + + // Hide the window. + window->Hide(); + + // Activate another window. + if (wm::IsActiveWindow(window)) + wm::DeactivateWindow(window); + } else { + if ((window->TargetVisibility() || + last_show_state == ui::SHOW_STATE_MINIMIZED) && + !window->layer()->visible()) { + // The layer may be hidden if the window was previously minimized. Make + // sure it's visible. + window->Show(); + } + workspace_manager()->OnWorkspaceWindowShowStateChanged( + workspace_, window, last_show_state); + } + } + + private: + WorkspaceManager2* workspace_manager() { + return workspace_->workspace_manager(); + } + + Workspace2* workspace_; + + DISALLOW_COPY_AND_ASSIGN(WorkspaceLayoutManager); +}; + +// WorkspaceManager2 ----------------------------------------------------------- + +WorkspaceManager2::WorkspaceManager2(Window* contents_view) + : contents_view_(contents_view), + active_workspace_(NULL), + grid_size_(0), + shelf_(NULL), + in_move_(false) { + // Clobber any existing event filter. + contents_view->SetEventFilter(NULL); + // |contents_view| takes ownership of WorkspaceManagerLayoutManager2. + contents_view->SetLayoutManager( + new WorkspaceManagerLayoutManager2(contents_view)); + active_workspace_ = CreateWorkspace(false); + workspaces_.push_back(active_workspace_); +} + +WorkspaceManager2::~WorkspaceManager2() { + // Release the windows, they'll be destroyed when |contents_view_| is + // destroyed. + std::for_each(workspaces_.begin(), workspaces_.end(), + std::mem_fun(&Workspace2::ReleaseWindow)); + std::for_each(pending_workspaces_.begin(), pending_workspaces_.end(), + std::mem_fun(&Workspace2::ReleaseWindow)); + STLDeleteElements(&workspaces_); + STLDeleteElements(&pending_workspaces_); +} + +// static +bool WorkspaceManager2::IsMaximized(Window* window) { + return wm::IsWindowFullscreen(window) || wm::IsWindowMaximized(window); +} + +// static +bool WorkspaceManager2::WillRestoreMaximized(aura::Window* window) { + if (!wm::IsWindowMinimized(window)) + return false; + + ui::WindowShowState restore_state = + window->GetProperty(internal::kRestoreShowStateKey); + return restore_state == ui::SHOW_STATE_MAXIMIZED || + restore_state == ui::SHOW_STATE_FULLSCREEN; + +} + +bool WorkspaceManager2::IsInMaximizedMode() const { + return active_workspace_ && active_workspace_->is_maximized(); +} + +void WorkspaceManager2::SetGridSize(int size) { + grid_size_ = size; + std::for_each(workspaces_.begin(), workspaces_.end(), + std::bind2nd(std::mem_fun(&Workspace2::SetGridSize), + grid_size_)); + std::for_each(pending_workspaces_.begin(), pending_workspaces_.end(), + std::bind2nd(std::mem_fun(&Workspace2::SetGridSize), + grid_size_)); +} + +int WorkspaceManager2::GetGridSize() const { + return grid_size_; +} + +WorkspaceWindowState WorkspaceManager2::GetWindowState() const { + if (!shelf_) + return WORKSPACE_WINDOW_STATE_DEFAULT; + + gfx::Rect shelf_bounds(shelf_->GetIdealBounds()); + const Window::Windows& windows(active_workspace_->window()->children()); + bool window_overlaps_launcher = false; + bool has_maximized_window = false; + for (Window::Windows::const_iterator i = windows.begin(); + i != windows.end(); ++i) { + ui::Layer* layer = (*i)->layer(); + if (!layer->GetTargetVisibility() || layer->GetTargetOpacity() == 0.0f) + continue; + if (wm::IsWindowMaximized(*i)) { + // An untracked window may still be fullscreen so we keep iterating when + // we hit a maximized window. + has_maximized_window = true; + } else if (wm::IsWindowFullscreen(*i)) { + return WORKSPACE_WINDOW_STATE_FULL_SCREEN; + } + if (!window_overlaps_launcher && (*i)->bounds().Intersects(shelf_bounds)) + window_overlaps_launcher = true; + } + if (has_maximized_window) + return WORKSPACE_WINDOW_STATE_MAXIMIZED; + + return window_overlaps_launcher ? + WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF : + WORKSPACE_WINDOW_STATE_DEFAULT; +} + +void WorkspaceManager2::SetShelf(ShelfLayoutManager* shelf) { + shelf_ = shelf; +} + +void WorkspaceManager2::SetActiveWorkspaceByWindow(Window* window) { + Workspace2* workspace = FindBy(window); + if (!workspace) + return; + + if (workspace != active_workspace_) { + // If the window persists across all workspaces, move it to the current + // workspace. + if (GetPersistsAcrossAllWorkspaces(window) && !IsMaximized(window)) + ReparentWindow(window, active_workspace_->window(), NULL); + else + SetActiveWorkspace(workspace); + } + UpdateShelfVisibility(); +} + +Window* WorkspaceManager2::GetParentForNewWindow(Window* window) { + if (window->transient_parent()) { + DCHECK(contents_view_->Contains(window->transient_parent())); + DCHECK(!IsMaximized(window)); + return window->transient_parent()->parent(); + } + + if (IsMaximized(window)) { + // Wait for the window to be made active before showing the workspace. + Workspace2* workspace = CreateWorkspace(false); + pending_workspaces_.insert(workspace); + return workspace->window(); + } + + if (!GetTrackedByWorkspace(window) || GetPersistsAcrossAllWorkspaces(window)) + return active_workspace_->window(); + + return desktop_workspace()->window(); +} + +void WorkspaceManager2::UpdateShelfVisibility() { + if (shelf_) + shelf_->UpdateVisibilityState(); +} + +Workspace2* WorkspaceManager2::FindBy(Window* window) const { + while (window) { + Workspace2* workspace = window->GetProperty(kWorkspaceKey); + if (workspace) + return workspace; + window = window->transient_parent(); + } + return NULL; +} + +void WorkspaceManager2::SetActiveWorkspace(Workspace2* workspace) { + DCHECK(workspace); + if (active_workspace_ == workspace) + return; + + // TODO: sort out animations. + + pending_workspaces_.erase(workspace); + + // Adjust the z-order. No need to adjust the z-order for the desktop since + // it always stays at the bottom. + if (workspace != desktop_workspace()) { + if (FindWorkspace(workspace) == workspaces_.end()) { + contents_view_->StackChildAbove(workspace->window(), + workspaces_.back()->window()); + workspaces_.push_back(workspace); + } + } + + active_workspace_ = workspace; + + for (size_t i = 0; i < workspaces_.size(); ++i) { + if (workspaces_[i] == active_workspace_) + workspaces_[i]->window()->Show(); + else + workspaces_[i]->window()->Hide(); + } +} + +WorkspaceManager2::Workspaces::iterator +WorkspaceManager2::FindWorkspace(Workspace2* workspace) { + return std::find(workspaces_.begin(), workspaces_.end(), workspace); +} + +Workspace2* WorkspaceManager2::CreateWorkspace(bool maximized) { + Workspace2* workspace = new Workspace2(this, contents_view_, maximized); + workspace->SetGridSize(grid_size_); + workspace->window()->SetLayoutManager( + new WorkspaceLayoutManager(contents_view_->GetRootWindow(), workspace)); + return workspace; +} + +void WorkspaceManager2::MoveWorkspaceToPendingOrDelete( + Workspace2* workspace, + Window* stack_beneath) { + // We're all ready moving windows. + if (in_move_) + return; + + DCHECK_NE(desktop_workspace(), workspace); + + if (workspace == active_workspace_) + SelectNextWorkspace(); + + AutoReset<bool> setter(&in_move_, true); + + // Move all non-maximized/fullscreen windows to the desktop. + { + // Build the list of windows to move. Exclude maximized/fullscreen and + // windows with transient parents. + Window::Windows to_move; + Window* w_window = workspace->window(); + for (size_t i = 0; i < w_window->children().size(); ++i) { + Window* child = w_window->children()[i]; + if (!child->transient_parent() && !IsMaximized(child) && + !WillRestoreMaximized(child)) { + to_move.push_back(child); + } + } + // Move the windows, but make sure the window is still a child of |w_window| + // before moving (moving may cascade and cause other windows to move). + for (size_t i = 0; i < to_move.size(); ++i) { + if (std::find(w_window->children().begin(), w_window->children().end(), + to_move[i]) != w_window->children().end()) { + ReparentWindow(to_move[i], desktop_workspace()->window(), + stack_beneath); + } + } + } + + { + Workspaces::iterator workspace_i(FindWorkspace(workspace)); + if (workspace_i != workspaces_.end()) + workspaces_.erase(workspace_i); + } + + if (workspace->window()->children().empty()) { + pending_workspaces_.erase(workspace); + delete workspace->ReleaseWindow(); + delete workspace; + } else { + pending_workspaces_.insert(workspace); + } +} + +void WorkspaceManager2::SelectNextWorkspace() { + DCHECK_NE(active_workspace_, desktop_workspace()); + + Workspaces::const_iterator workspace_i(FindWorkspace(active_workspace_)); + Workspaces::const_iterator next_workspace_i(workspace_i + 1); + if (next_workspace_i != workspaces_.end()) + SetActiveWorkspace(*next_workspace_i); + else + SetActiveWorkspace(*(workspace_i - 1)); + UpdateShelfVisibility(); +} + +void WorkspaceManager2::OnWindowAddedToWorkspace(Workspace2* workspace, + Window* child) { + // Do nothing (other than updating shelf visibility) as the right parent was + // chosen by way of GetParentForNewWindow() or we explicitly moved the window + // to the workspace. + if (workspace == active_workspace_) + UpdateShelfVisibility(); +} + +void WorkspaceManager2::OnWindowRemovedFromWorkspace(Workspace2* workspace, + Window* child) { + if (workspace->ShouldMoveToPending()) + MoveWorkspaceToPendingOrDelete(workspace, NULL); +} + +void WorkspaceManager2::OnWorkspaceChildWindowVisibilityChanged( + Workspace2* workspace, + Window* child) { + if (workspace->ShouldMoveToPending()) + MoveWorkspaceToPendingOrDelete(workspace, NULL); +} + +void WorkspaceManager2::OnWorkspaceWindowChildBoundsChanged( + Workspace2* workspace, + Window* child) { +} + +void WorkspaceManager2::OnWorkspaceWindowShowStateChanged( + Workspace2* workspace, + Window* child, + ui::WindowShowState last_show_state) { + if (wm::IsWindowMinimized(child)) { + if (workspace->ShouldMoveToPending()) + MoveWorkspaceToPendingOrDelete(workspace, NULL); + } else { + // Here's the cases that need to be handled: + // . More than one maximized window: move newly maximized window into + // own workspace. + // . One maximized window and not in a maximized workspace: move window + // into own workspace. + // . No maximized window and not in desktop: move to desktop and further + // any existing windows are stacked beneath |child|. + const bool is_active = wm::IsActiveWindow(child); + Workspace2* new_workspace = NULL; + const int max_count = workspace->GetNumMaximizedWindows(); + if (max_count == 0) { + if (workspace != desktop_workspace()) { + { + AutoReset<bool> setter(&in_move_, true); + ReparentWindow(child, desktop_workspace()->window(), NULL); + } + MoveWorkspaceToPendingOrDelete(workspace, child); + new_workspace = desktop_workspace(); + } + } else if (max_count == 1) { + if (workspace == desktop_workspace()) { + new_workspace = CreateWorkspace(true); + pending_workspaces_.insert(new_workspace); + ReparentWindow(child, new_workspace->window(), NULL); + } + } else { + new_workspace = CreateWorkspace(true); + pending_workspaces_.insert(new_workspace); + ReparentWindow(child, new_workspace->window(), NULL); + } + if (is_active && new_workspace) + SetActiveWorkspace(new_workspace); + } + UpdateShelfVisibility(); +} + +} // namespace internal +} // namespace ash diff --git a/ash/wm/workspace/workspace_manager2.h b/ash/wm/workspace/workspace_manager2.h new file mode 100644 index 0000000..de72b6a --- /dev/null +++ b/ash/wm/workspace/workspace_manager2.h @@ -0,0 +1,149 @@ +// 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. + +#ifndef ASH_WM_WORKSPACE_WORKSPACE_MANAGER2_H_ +#define ASH_WM_WORKSPACE_WORKSPACE_MANAGER2_H_ + +#include <set> +#include <string> +#include <vector> + +#include "ash/ash_export.h" +#include "ash/wm/workspace/base_workspace_manager.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "ui/base/ui_base_types.h" + +namespace aura { +class Window; +} + +namespace gfx { +class Point; +class Rect; +} + +namespace ash { +namespace internal { + +class ShelfLayoutManager; +class WorkspaceManagerTest2; +class Workspace2; + +// WorkspaceManager manages multiple workspaces in the desktop. Workspaces are +// implicitly created as windows are maximized (or made fullscreen), and +// destroyed when maximized windows are closed or restored. There is always one +// workspace for the desktop. +// Internally WorkspaceManager2 creates a Window for each Workspace. As windows +// are maximized and restored they are reparented to the right Window. +class ASH_EXPORT WorkspaceManager2 : public BaseWorkspaceManager { + public: + explicit WorkspaceManager2(aura::Window* viewport); + virtual ~WorkspaceManager2(); + + // Returns true if |window| is considered maximized and should exist in its + // own workspace. + static bool IsMaximized(aura::Window* window); + + // Returns true if |window| is minimized and will restore to a maximized + // window. + static bool WillRestoreMaximized(aura::Window* window); + + // BaseWorkspaceManager2 overrides: + virtual bool IsInMaximizedMode() const OVERRIDE; + virtual void SetGridSize(int size) OVERRIDE; + virtual int GetGridSize() const OVERRIDE; + virtual WorkspaceWindowState GetWindowState() const OVERRIDE; + virtual void SetShelf(ShelfLayoutManager* shelf) OVERRIDE; + virtual void SetActiveWorkspaceByWindow(aura::Window* window) OVERRIDE; + virtual aura::Window* GetParentForNewWindow(aura::Window* window) OVERRIDE; + + private: + friend class WorkspaceManager2Test; + + class LayoutManager; + class WorkspaceLayoutManager; + + typedef std::vector<Workspace2*> Workspaces; + + // Updates the visibility and whether any windows overlap the shelf. + void UpdateShelfVisibility(); + + // Returns the workspace that contains |window|. + Workspace2* FindBy(aura::Window* window) const; + + // Sets the active workspace. + void SetActiveWorkspace(Workspace2* workspace); + + // Returns the bounds of the work area. + gfx::Rect GetWorkAreaBounds() const; + + // Returns an iterator into |workspaces_| for |workspace|. + Workspaces::iterator FindWorkspace(Workspace2* workspace); + + Workspace2* desktop_workspace() { return workspaces_[0]; } + const Workspace2* desktop_workspace() const { return workspaces_[0]; } + + // Creates a new workspace. The Workspace is not added to anything and is + // owned by the caller. + Workspace2* CreateWorkspace(bool maximized); + + // Moves all the non-maximized child windows of |workspace| to the desktop + // stacked beneath |stack_beneath| (if non-NULL). After moving child windows + // if |workspace| contains no children it is deleted, otherwise it it moved to + // |pending_workspaces_|. + void MoveWorkspaceToPendingOrDelete(Workspace2* workspace, + aura::Window* stack_beneath); + + // Selects the next workspace. + void SelectNextWorkspace(); + + // These methods are forwarded from the LayoutManager installed on the + // Workspace's window. + void OnWindowAddedToWorkspace(Workspace2* workspace, aura::Window* child); + void OnWindowRemovedFromWorkspace(Workspace2* workspace, aura::Window* child); + void OnWorkspaceChildWindowVisibilityChanged(Workspace2* workspace, + aura::Window* child); + void OnWorkspaceWindowChildBoundsChanged(Workspace2* workspace, + aura::Window* child); + void OnWorkspaceWindowShowStateChanged(Workspace2* workspace, + aura::Window* child, + ui::WindowShowState last_show_state); + + aura::Window* contents_view_; + + Workspace2* active_workspace_; + + // The set of active workspaces. There is always at least one in this stack, + // which identifies the desktop. + Workspaces workspaces_; + + // The set of workspaces not currently active. Workspaces ended up here in + // two scenarios: + // . Prior to adding a window a new worskpace is created for it. The + // Workspace is added to this set. + // . When the maximized window is minimized the workspace is added here. + // Once any window in the workspace is activated the workspace is moved to + // |workspaces_|. + std::set<Workspace2*> pending_workspaces_; + + // See description above setter. + int grid_size_; + + // Owned by the Shell. May be NULL. + ShelfLayoutManager* shelf_; + + // Whether or not we're in MoveWorkspaceToPendingOrDelete(). As + // MoveWorkspaceToPendingOrDelete() may trigger another call to + // MoveWorkspaceToPendingOrDelete() we use this to avoid doing anything if + // already in MoveWorkspaceToPendingOrDelete(). + bool in_move_; + + DISALLOW_COPY_AND_ASSIGN(WorkspaceManager2); +}; + +} // namespace internal +} // namespace ash + +#endif // ASH_WM_WORKSPACE_WORKSPACE_MANAGER2_H_ diff --git a/ash/wm/workspace/workspace_manager2_unittest.cc b/ash/wm/workspace/workspace_manager2_unittest.cc new file mode 100644 index 0000000..2b3327c --- /dev/null +++ b/ash/wm/workspace/workspace_manager2_unittest.cc @@ -0,0 +1,764 @@ +// 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/workspace/workspace_manager2.h" + +#include "ash/ash_switches.h" +#include "ash/screen_ash.h" +#include "ash/shell.h" +#include "ash/shell_window_ids.h" +#include "ash/test/ash_test_base.h" +#include "ash/wm/activation_controller.h" +#include "ash/wm/property_util.h" +#include "ash/wm/shelf_layout_manager.h" +#include "ash/wm/window_util.h" +#include "ash/wm/workspace/workspace2.h" +#include "ash/wm/workspace_controller_test_helper.h" +#include "base/command_line.h" +#include "base/string_number_conversions.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/root_window.h" +#include "ui/aura/test/event_generator.h" +#include "ui/aura/window.h" +#include "ui/base/ui_base_types.h" +#include "ui/compositor/layer.h" +#include "ui/gfx/screen.h" + +using aura::Window; + +namespace ash { +namespace internal { + +class WorkspaceManager2Test : public test::AshTestBase { + public: + WorkspaceManager2Test() : manager_(NULL) {} + virtual ~WorkspaceManager2Test() {} + + aura::Window* CreateTestWindowUnparented() { + aura::Window* window = new aura::Window(NULL); + window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + window->SetType(aura::client::WINDOW_TYPE_NORMAL); + window->Init(ui::LAYER_TEXTURED); + return window; + } + + aura::Window* CreateTestWindow() { + aura::Window* window = new aura::Window(NULL); + window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + window->SetType(aura::client::WINDOW_TYPE_NORMAL); + window->Init(ui::LAYER_TEXTURED); + window->SetParent(NULL); + return window; + } + + aura::Window* GetViewport() { + return Shell::GetContainer(Shell::GetPrimaryRootWindow(), + kShellWindowId_DefaultContainer); + } + + const std::vector<Workspace2*>& workspaces() const { + return manager_->workspaces_; + } + + gfx::Rect GetFullscreenBounds(aura::Window* window) { + return gfx::Screen::GetDisplayNearestWindow(window).bounds(); + } + + Workspace2* active_workspace() { + return manager_->active_workspace_; + } + + Workspace2* FindBy(aura::Window* window) const { + return manager_->FindBy(window); + } + + std::string WorkspaceStateString(Workspace2* workspace) { + return (workspace->is_maximized() ? "M" : "") + + base::IntToString(static_cast<int>( + workspace->window()->children().size())); + } + + int active_index() { + return static_cast<int>( + manager_->FindWorkspace(manager_->active_workspace_) - + manager_->workspaces_.begin()); + } + + std::string StateString() { + std::string result; + for (size_t i = 0; i < manager_->workspaces_.size(); ++i) { + if (i > 0) + result += " "; + result += WorkspaceStateString(manager_->workspaces_[i]); + } + + if (!manager_->pending_workspaces_.empty()) { + result += " P="; + for (std::set<Workspace2*>::const_iterator i = + manager_->pending_workspaces_.begin(); + i != manager_->pending_workspaces_.end(); ++i) { + if (i != manager_->pending_workspaces_.begin()) + result += " "; + result += WorkspaceStateString(*i); + } + } + + result += " active=" + base::IntToString(active_index()); + return result; + } + + // Overridden from AshTestBase: + virtual void SetUp() OVERRIDE { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kAshEnableWorkspace2); + test::AshTestBase::SetUp(); + WorkspaceControllerTestHelper workspace_helper( + Shell::TestApi(Shell::GetInstance()).workspace_controller()); + manager_ = workspace_helper.workspace_manager2(); + manager_->SetGridSize(0); + } + + virtual void TearDown() OVERRIDE { + manager_ = NULL; + test::AshTestBase::TearDown(); + } + + protected: + WorkspaceManager2* manager_; + + private: + scoped_ptr<ActivationController> activation_controller_; + + DISALLOW_COPY_AND_ASSIGN(WorkspaceManager2Test); +}; + +// Assertions around adding a normal window. +TEST_F(WorkspaceManager2Test, AddNormalWindowWhenEmpty) { + scoped_ptr<Window> w1(CreateTestWindow()); + w1->SetBounds(gfx::Rect(0, 0, 250, 251)); + + EXPECT_TRUE(GetRestoreBoundsInScreen(w1.get()) == NULL); + + w1->Show(); + + EXPECT_TRUE(GetRestoreBoundsInScreen(w1.get()) == NULL); + + ASSERT_TRUE(w1->layer() != NULL); + EXPECT_TRUE(w1->layer()->visible()); + + EXPECT_EQ("0,0 250x251", w1->bounds().ToString()); + + // Should be 1 workspace for the desktop, not maximized. + ASSERT_EQ("1 active=0", StateString()); + EXPECT_EQ(w1.get(), workspaces()[0]->window()->children()[0]); +} + +// Assertions around maximizing/unmaximizing. +TEST_F(WorkspaceManager2Test, SingleMaximizeWindow) { + scoped_ptr<Window> w1(CreateTestWindow()); + w1->SetBounds(gfx::Rect(0, 0, 250, 251)); + + w1->Show(); + wm::ActivateWindow(w1.get()); + + EXPECT_TRUE(wm::IsActiveWindow(w1.get())); + + ASSERT_TRUE(w1->layer() != NULL); + EXPECT_TRUE(w1->layer()->visible()); + + EXPECT_EQ("0,0 250x251", w1->bounds().ToString()); + + // Maximize the window. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + + EXPECT_TRUE(wm::IsActiveWindow(w1.get())); + + // Should be 2 workspaces, the second maximized with w1. + ASSERT_EQ("0 M1 active=1", StateString()); + EXPECT_EQ(w1.get(), workspaces()[1]->window()->children()[0]); + EXPECT_EQ(ScreenAsh::GetMaximizedWindowBoundsInParent(w1.get()).width(), + w1->bounds().width()); + EXPECT_EQ(ScreenAsh::GetMaximizedWindowBoundsInParent(w1.get()).height(), + w1->bounds().height()); + + // Restore the window. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + + // Should be 1 workspace for the desktop. + ASSERT_EQ("1 active=0", StateString()); + EXPECT_EQ(w1.get(), workspaces()[0]->window()->children()[0]); + EXPECT_EQ("0,0 250x251", w1->bounds().ToString()); +} + +// Assertions around closing the last window in a workspace. +TEST_F(WorkspaceManager2Test, CloseLastWindowInWorkspace) { + scoped_ptr<Window> w1(CreateTestWindow()); + scoped_ptr<Window> w2(CreateTestWindow()); + w1->SetBounds(gfx::Rect(0, 0, 250, 251)); + w1->Show(); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + w2->Show(); + wm::ActivateWindow(w1.get()); + + // Should be 1 workspace and 1 pending, !maximized and maximized. The second + // workspace is pending since the window wasn't active. + ASSERT_EQ("1 P=M1 active=0", StateString()); + EXPECT_EQ(w1.get(), workspaces()[0]->window()->children()[0]); + + // Close w2. + w2.reset(); + + // Should have one workspace. + ASSERT_EQ("1 active=0", StateString()); + EXPECT_EQ(w1.get(), workspaces()[0]->window()->children()[0]); + EXPECT_TRUE(w1->IsVisible()); +} + +// Assertions around adding a maximized window when empty. +TEST_F(WorkspaceManager2Test, AddMaximizedWindowWhenEmpty) { + scoped_ptr<Window> w1(CreateTestWindow()); + w1->SetBounds(gfx::Rect(0, 0, 250, 251)); + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + w1->Show(); + wm::ActivateWindow(w1.get()); + + ASSERT_TRUE(w1->layer() != NULL); + EXPECT_TRUE(w1->layer()->visible()); + gfx::Rect work_area( + ScreenAsh::GetMaximizedWindowBoundsInParent(w1.get())); + EXPECT_EQ(work_area.width(), w1->bounds().width()); + EXPECT_EQ(work_area.height(), w1->bounds().height()); + + // Should be 2 workspaces (since we always keep the desktop). + ASSERT_EQ("0 M1 active=1", StateString()); + EXPECT_EQ(w1.get(), workspaces()[1]->window()->children()[0]); +} + +// Assertions around two windows and toggling one to be maximized. +TEST_F(WorkspaceManager2Test, MaximizeWithNormalWindow) { + scoped_ptr<Window> w1(CreateTestWindow()); + scoped_ptr<Window> w2(CreateTestWindow()); + w1->SetBounds(gfx::Rect(0, 0, 250, 251)); + w1->Show(); + + ASSERT_TRUE(w1->layer() != NULL); + EXPECT_TRUE(w1->layer()->visible()); + + w2->SetBounds(gfx::Rect(0, 0, 50, 51)); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + w2->Show(); + wm::ActivateWindow(w2.get()); + + // Should now be two workspaces. + ASSERT_EQ("1 M1 active=1", StateString()); + EXPECT_EQ(w1.get(), workspaces()[0]->window()->children()[0]); + EXPECT_EQ(w2.get(), workspaces()[1]->window()->children()[0]); + + gfx::Rect work_area(ScreenAsh::GetMaximizedWindowBoundsInParent(w1.get())); + EXPECT_EQ(work_area.width(), w2->bounds().width()); + EXPECT_EQ(work_area.height(), w2->bounds().height()); + + // Restore w2, which should then go back to one workspace. + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + ASSERT_EQ("2 active=0", StateString()); + EXPECT_EQ(w1.get(), workspaces()[0]->window()->children()[0]); + EXPECT_EQ(w2.get(), workspaces()[0]->window()->children()[1]); + EXPECT_EQ(50, w2->bounds().width()); + EXPECT_EQ(51, w2->bounds().height()); + EXPECT_TRUE(wm::IsActiveWindow(w2.get())); +} + +// Assertions around two maximized windows. +TEST_F(WorkspaceManager2Test, TwoMaximized) { + scoped_ptr<Window> w1(CreateTestWindow()); + scoped_ptr<Window> w2(CreateTestWindow()); + w1->SetBounds(gfx::Rect(0, 0, 250, 251)); + w1->Show(); + wm::ActivateWindow(w1.get()); + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + ASSERT_EQ("1 M1 active=1", StateString()); + + w2->SetBounds(gfx::Rect(0, 0, 50, 51)); + w2->Show(); + wm::ActivateWindow(w2.get()); + ASSERT_EQ("1 M1 active=0", StateString()); + + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + EXPECT_TRUE(wm::IsActiveWindow(w2.get())); + ASSERT_EQ("0 M1 M1 active=2", StateString()); + + // The last stacked window (|w2|) should be last since it was maximized last. + EXPECT_EQ(w1.get(), workspaces()[1]->window()->children()[0]); + EXPECT_EQ(w2.get(), workspaces()[2]->window()->children()[0]); +} + +// Makes sure requests to change the bounds of a normal window go through. +TEST_F(WorkspaceManager2Test, ChangeBoundsOfNormalWindow) { + scoped_ptr<Window> w1(CreateTestWindow()); + w1->Show(); + + // Setting the bounds should go through since the window is in the normal + // workspace. + w1->SetBounds(gfx::Rect(0, 0, 200, 500)); + EXPECT_EQ(200, w1->bounds().width()); + EXPECT_EQ(500, w1->bounds().height()); +} + +// Verifies the bounds is not altered when showing and grid is enabled. +TEST_F(WorkspaceManager2Test, SnapToGrid) { + manager_->SetGridSize(8); + + scoped_ptr<Window> w1(CreateTestWindowUnparented()); + w1->SetBounds(gfx::Rect(1, 6, 25, 30)); + w1->SetParent(NULL); + // We are not aligning this anymore this way. When the window gets shown + // the window is expected to be handled differently, but this cannot be + // tested with this test. So the result of this test should be that the + // bounds are exactly as passed in. + EXPECT_EQ("1,6 25x30", w1->bounds().ToString()); +} + +// Assertions around a fullscreen window. +TEST_F(WorkspaceManager2Test, SingleFullscreenWindow) { + scoped_ptr<Window> w1(CreateTestWindow()); + w1->SetBounds(gfx::Rect(0, 0, 250, 251)); + // Make the window fullscreen. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); + w1->Show(); + wm::ActivateWindow(w1.get()); + + // Should be 2 workspaces, normal and maximized. + ASSERT_EQ("0 M1 active=1", StateString()); + EXPECT_EQ(w1.get(), workspaces()[1]->window()->children()[0]); + EXPECT_EQ(GetFullscreenBounds(w1.get()).width(), w1->bounds().width()); + EXPECT_EQ(GetFullscreenBounds(w1.get()).height(), w1->bounds().height()); + + // Restore the window. Use SHOW_STATE_DEFAULT as that is what we'll end up + // with when using views::Widget. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_DEFAULT); + EXPECT_EQ("0,0 250x251", w1->bounds().ToString()); + + // Should be 1 workspace for the desktop. + ASSERT_EQ("1 active=0", StateString()); + EXPECT_EQ(w1.get(), workspaces()[0]->window()->children()[0]); + EXPECT_EQ(250, w1->bounds().width()); + EXPECT_EQ(251, w1->bounds().height()); + + // Back to fullscreen. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); + ASSERT_EQ("0 M1 active=1", StateString()); + EXPECT_EQ(w1.get(), workspaces()[1]->window()->children()[0]); + EXPECT_EQ(GetFullscreenBounds(w1.get()).width(), w1->bounds().width()); + EXPECT_EQ(GetFullscreenBounds(w1.get()).height(), w1->bounds().height()); + ASSERT_TRUE(GetRestoreBoundsInScreen(w1.get())); + EXPECT_EQ("0,0 250x251", GetRestoreBoundsInScreen(w1.get())->ToString()); +} + +// Makes sure switching workspaces doesn't show transient windows. +TEST_F(WorkspaceManager2Test, DontShowTransientsOnSwitch) { + scoped_ptr<Window> w1(CreateTestWindow()); + scoped_ptr<Window> w2(CreateTestWindow()); + + w1->SetBounds(gfx::Rect(0, 0, 250, 251)); + w2->SetBounds(gfx::Rect(0, 0, 250, 251)); + w1->AddTransientChild(w2.get()); + + w1->Show(); + + scoped_ptr<Window> w3(CreateTestWindow()); + w3->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + w3->Show(); + wm::ActivateWindow(w3.get()); + + EXPECT_FALSE(w1->layer()->IsDrawn()); + EXPECT_FALSE(w2->layer()->IsDrawn()); + EXPECT_TRUE(w3->layer()->IsDrawn()); + + wm::ActivateWindow(w1.get()); + EXPECT_TRUE(w1->layer()->IsDrawn()); + EXPECT_FALSE(w2->layer()->IsDrawn()); + EXPECT_FALSE(w3->layer()->IsDrawn()); +} + +// Assertions around minimizing a single window. +TEST_F(WorkspaceManager2Test, MinimizeSingleWindow) { + scoped_ptr<Window> w1(CreateTestWindow()); + + w1->Show(); + ASSERT_EQ("1 active=0", StateString()); + + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + ASSERT_EQ("1 active=0", StateString()); + EXPECT_FALSE(w1->layer()->IsDrawn()); + + // Show the window. + w1->Show(); + EXPECT_TRUE(wm::IsWindowNormal(w1.get())); + ASSERT_EQ("1 active=0", StateString()); + EXPECT_TRUE(w1->layer()->IsDrawn()); +} + +// Assertions around minimizing a maximized window. +TEST_F(WorkspaceManager2Test, MinimizeMaximizedWindow) { + // Two windows, w1 normal, w2 maximized. + scoped_ptr<Window> w1(CreateTestWindow()); + scoped_ptr<Window> w2(CreateTestWindow()); + w1->Show(); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + w2->Show(); + wm::ActivateWindow(w2.get()); + ASSERT_EQ("1 M1 active=1", StateString()); + + // Minimize w2. + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + ASSERT_EQ("1 P=M1 active=0", StateString()); + EXPECT_TRUE(w1->layer()->IsDrawn()); + EXPECT_FALSE(w2->layer()->IsDrawn()); + + // Show the window, which should trigger unminimizing. + w2->Show(); + ASSERT_EQ("1 P=M1 active=0", StateString()); + + wm::ActivateWindow(w2.get()); + ASSERT_EQ("1 M1 active=1", StateString()); + + EXPECT_TRUE(wm::IsWindowMaximized(w2.get())); + EXPECT_FALSE(w1->layer()->IsDrawn()); + EXPECT_TRUE(w2->layer()->IsDrawn()); + + // Minimize the window, which should hide the window and activate another. + EXPECT_TRUE(wm::IsActiveWindow(w2.get())); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + EXPECT_FALSE(wm::IsActiveWindow(w2.get())); + EXPECT_FALSE(w2->layer()->IsDrawn()); + EXPECT_TRUE(wm::IsActiveWindow(w1.get())); + + // Make the window normal. + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + ASSERT_EQ("2 active=0", StateString()); + EXPECT_EQ(w1.get(), workspaces()[0]->window()->children()[0]); + EXPECT_EQ(w2.get(), workspaces()[0]->window()->children()[1]); + EXPECT_TRUE(w2->layer()->IsDrawn()); +} + +// Verifies ShelfLayoutManager's visibility/auto-hide state is correctly +// updated. +TEST_F(WorkspaceManager2Test, ShelfStateUpdated) { + // Since ShelfLayoutManager queries for mouse location, move the mouse so + // it isn't over the shelf. + aura::test::EventGenerator generator( + Shell::GetPrimaryRootWindow(), gfx::Point()); + generator.MoveMouseTo(0, 0); + + scoped_ptr<Window> w1(CreateTestWindow()); + w1->SetBounds(gfx::Rect(0, 1, 101, 102)); + w1->Show(); + wm::ActivateWindow(w1.get()); + + ShelfLayoutManager* shelf = Shell::GetInstance()->shelf(); + + EXPECT_EQ(ShelfLayoutManager::VISIBLE, shelf->visibility_state()); + + // Maximize the window. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + EXPECT_EQ(ShelfLayoutManager::AUTO_HIDE, shelf->visibility_state()); + EXPECT_EQ(ShelfLayoutManager::AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); + + // Restore. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + EXPECT_EQ(ShelfLayoutManager::VISIBLE, shelf->visibility_state()); + EXPECT_EQ("0,1 101x102", w1->bounds().ToString()); + + // Fullscreen. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); + EXPECT_EQ(ShelfLayoutManager::HIDDEN, shelf->visibility_state()); + + // Normal. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + EXPECT_EQ(ShelfLayoutManager::VISIBLE, shelf->visibility_state()); + EXPECT_EQ("0,1 101x102", w1->bounds().ToString()); + + // Maximize again. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + EXPECT_EQ(ShelfLayoutManager::AUTO_HIDE, shelf->visibility_state()); + EXPECT_EQ(ShelfLayoutManager::AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); + + // Minimize. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + EXPECT_EQ(ShelfLayoutManager::VISIBLE, shelf->visibility_state()); + + // Restore. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + EXPECT_EQ(ShelfLayoutManager::VISIBLE, shelf->visibility_state()); + EXPECT_EQ("0,1 101x102", w1->bounds().ToString()); + + // Create another window, maximized. + scoped_ptr<Window> w2(CreateTestWindow()); + w2->SetBounds(gfx::Rect(10, 11, 250, 251)); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + w2->Show(); + wm::ActivateWindow(w2.get()); + EXPECT_EQ(1, active_index()); + EXPECT_EQ(ShelfLayoutManager::AUTO_HIDE, shelf->visibility_state()); + EXPECT_EQ(ShelfLayoutManager::AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); + EXPECT_EQ("0,1 101x102", w1->bounds().ToString()); + + // Switch to w1. + wm::ActivateWindow(w1.get()); + EXPECT_EQ(0, active_index()); + EXPECT_EQ(ShelfLayoutManager::VISIBLE, shelf->visibility_state()); + EXPECT_EQ("0,1 101x102", w1->bounds().ToString()); + EXPECT_EQ(ScreenAsh::GetMaximizedWindowBoundsInParent(w2.get()).ToString(), + w2->bounds().ToString()); + + // Switch to w2. + wm::ActivateWindow(w2.get()); + EXPECT_EQ(1, active_index()); + EXPECT_EQ(ShelfLayoutManager::AUTO_HIDE, shelf->visibility_state()); + EXPECT_EQ(ShelfLayoutManager::AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); + EXPECT_EQ("0,1 101x102", w1->bounds().ToString()); + EXPECT_EQ(ScreenAsh::GetMaximizedWindowBoundsInParent(w2.get()).ToString(), + w2->bounds().ToString()); +} + +// Verifies persist across all workspaces. +TEST_F(WorkspaceManager2Test, PersistAcrossAllWorkspaces) { + // Create a maximized window. + scoped_ptr<Window> w1(CreateTestWindow()); + w1->Show(); + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + wm::ActivateWindow(w1.get()); + ASSERT_EQ("0 M1 active=1", StateString()); + + // Create a window that persists across all workspaces. It should be placed in + // the current maximized workspace. + scoped_ptr<Window> w2(CreateTestWindow()); + SetPersistsAcrossAllWorkspaces( + w2.get(), + WINDOW_PERSISTS_ACROSS_ALL_WORKSPACES_VALUE_YES); + w2->Show(); + ASSERT_EQ("1 M1 active=1", StateString()); + + // Activate w2, which should move it to the 2nd workspace. + wm::ActivateWindow(w2.get()); + ASSERT_EQ("0 M2 active=1", StateString()); + + // Restoring w2 should drop the persists window back to the desktop, and drop + // it to the bottom of the stack. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + ASSERT_EQ("2 active=0", StateString()); + EXPECT_EQ(w2.get(), workspaces()[0]->window()->children()[0]); + EXPECT_EQ(w1.get(), workspaces()[0]->window()->children()[1]); + + // Repeat, but this time minimize. The minimize window should end up in + // pending. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + w2.reset(CreateTestWindow()); + SetPersistsAcrossAllWorkspaces( + w2.get(), + WINDOW_PERSISTS_ACROSS_ALL_WORKSPACES_VALUE_YES); + w2->Show(); + ASSERT_EQ("1 M1 active=1", StateString()); + wm::ActivateWindow(w2.get()); + ASSERT_EQ("0 M2 active=1", StateString()); + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + ASSERT_EQ("1 P=M1 active=0", StateString()); + EXPECT_EQ(w2.get(), workspaces()[0]->window()->children()[0]); +} + +// Verifies that when a window persists across all workpaces is activated that +// it moves to the current workspace. +TEST_F(WorkspaceManager2Test, ActivatePersistAcrossAllWorkspacesWhenNotActive) { + // Create a window that persists across all workspaces. + scoped_ptr<Window> w2(CreateTestWindow()); + SetPersistsAcrossAllWorkspaces( + w2.get(), + WINDOW_PERSISTS_ACROSS_ALL_WORKSPACES_VALUE_YES); + w2->Show(); + ASSERT_EQ("1 active=0", StateString()); + + // Create a maximized window. + scoped_ptr<Window> w1(CreateTestWindow()); + w1->Show(); + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + wm::ActivateWindow(w1.get()); + ASSERT_EQ("1 M1 active=1", StateString()); + + // Activate the persists across all workspace window. It should move to the + // current workspace. + wm::ActivateWindow(w2.get()); + ASSERT_EQ("0 M2 active=1", StateString()); + // The window that persists across all workspaces should be moved to the top + // of the stacking order. + EXPECT_EQ(w1.get(), workspaces()[1]->window()->children()[0]); + EXPECT_EQ(w2.get(), workspaces()[1]->window()->children()[1]); + EXPECT_TRUE(wm::IsActiveWindow(w2.get())); +} + +// Verifies Show()ing a minimized window that persists across all workspaces +// unminimizes the window. +TEST_F(WorkspaceManager2Test, ShowMinimizedPersistWindow) { + // Create a window that persists across all workspaces. + scoped_ptr<Window> w1(CreateTestWindow()); + SetPersistsAcrossAllWorkspaces( + w1.get(), + WINDOW_PERSISTS_ACROSS_ALL_WORKSPACES_VALUE_YES); + w1->Show(); + wm::ActivateWindow(w1.get()); + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + EXPECT_FALSE(w1->IsVisible()); + w1->Show(); + EXPECT_TRUE(w1->IsVisible()); +} + +// Test that we report we're in the fullscreen state even if the fullscreen +// window isn't being managed by us (http://crbug.com/123931). +TEST_F(WorkspaceManager2Test, GetWindowStateWithUnmanagedFullscreenWindow) { + ShelfLayoutManager* shelf = Shell::GetInstance()->shelf(); + + // We need to create a regular window first so there's an active workspace. + scoped_ptr<Window> w1(CreateTestWindow()); + w1->Show(); + + scoped_ptr<Window> w2(CreateTestWindow()); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); + SetPersistsAcrossAllWorkspaces( + w2.get(), + WINDOW_PERSISTS_ACROSS_ALL_WORKSPACES_VALUE_YES); + w2->Show(); + wm::ActivateWindow(w2.get()); + + ASSERT_EQ("1 M1 active=1", StateString()); + + EXPECT_EQ(ShelfLayoutManager::HIDDEN, shelf->visibility_state()); + EXPECT_EQ(WORKSPACE_WINDOW_STATE_FULL_SCREEN, manager_->GetWindowState()); + + w2->Hide(); + ASSERT_EQ("1 P=M1 active=0", StateString()); + EXPECT_EQ(ShelfLayoutManager::VISIBLE, shelf->visibility_state()); + + w2->Show(); + ASSERT_EQ("1 P=M1 active=0", StateString()); + EXPECT_EQ(ShelfLayoutManager::VISIBLE, shelf->visibility_state()); + EXPECT_EQ(WORKSPACE_WINDOW_STATE_DEFAULT, manager_->GetWindowState()); + + wm::ActivateWindow(w2.get()); + ASSERT_EQ("1 M1 active=1", StateString()); + EXPECT_EQ(ShelfLayoutManager::HIDDEN, shelf->visibility_state()); + EXPECT_EQ(WORKSPACE_WINDOW_STATE_FULL_SCREEN, manager_->GetWindowState()); + + w2.reset(); + ASSERT_EQ("1 active=0", StateString()); + EXPECT_EQ(ShelfLayoutManager::VISIBLE, shelf->visibility_state()); + EXPECT_EQ(WORKSPACE_WINDOW_STATE_DEFAULT, manager_->GetWindowState()); +} + +// Variant of GetWindowStateWithUnmanagedFullscreenWindow that uses a maximized +// window rather than a normal window. +TEST_F(WorkspaceManager2Test, + GetWindowStateWithUnmanagedFullscreenWindowWithMaximized) { + ShelfLayoutManager* shelf = Shell::GetInstance()->shelf(); + shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER); + + // Make the first window maximized. + scoped_ptr<Window> w1(CreateTestWindow()); + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + w1->Show(); + + scoped_ptr<Window> w2(CreateTestWindow()); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); + SetPersistsAcrossAllWorkspaces( + w2.get(), + WINDOW_PERSISTS_ACROSS_ALL_WORKSPACES_VALUE_YES); + w2->Show(); + wm::ActivateWindow(w2.get()); + + // Even though auto-hide behavior is NEVER full-screen windows cause the shelf + // to hide. + EXPECT_EQ(ShelfLayoutManager::HIDDEN, shelf->visibility_state()); + EXPECT_EQ(WORKSPACE_WINDOW_STATE_FULL_SCREEN, + manager_->GetWindowState()); + + w2->Hide(); + EXPECT_EQ(ShelfLayoutManager::VISIBLE, shelf->visibility_state()); + + w2->Show(); + wm::ActivateWindow(w2.get()); + EXPECT_EQ(ShelfLayoutManager::HIDDEN, shelf->visibility_state()); + EXPECT_EQ(WORKSPACE_WINDOW_STATE_FULL_SCREEN, + manager_->GetWindowState()); + + w2.reset(); + EXPECT_EQ(ShelfLayoutManager::VISIBLE, shelf->visibility_state()); +} + +// Verifies a window marked as persisting across all workspaces ends up in its +// own workspace when maximized. +TEST_F(WorkspaceManager2Test, MaximizeDontPersistEndsUpInOwnWorkspace) { + scoped_ptr<Window> w1(CreateTestWindow()); + + SetPersistsAcrossAllWorkspaces( + w1.get(), + WINDOW_PERSISTS_ACROSS_ALL_WORKSPACES_VALUE_YES); + w1->Show(); + + ASSERT_EQ("1 active=0", StateString()); + + // Maximize should trigger containing the window. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + ASSERT_EQ("0 P=M1 active=0", StateString()); + + // And resetting to normal should remove it. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + ASSERT_EQ("1 active=0", StateString()); +} + +// Verifies going from maximized to minimized sets the right state for painting +// the background of the launcher. +TEST_F(WorkspaceManager2Test, MinimizeResetsVisibility) { + scoped_ptr<Window> w1(CreateTestWindow()); + w1->Show(); + wm::ActivateWindow(w1.get()); + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + EXPECT_EQ(ShelfLayoutManager::VISIBLE, + Shell::GetInstance()->shelf()->visibility_state()); + EXPECT_FALSE(Shell::GetInstance()->launcher()->paints_background()); +} + +// Verifies transients are moved when maximizing. +TEST_F(WorkspaceManager2Test, MoveTransientOnMaximize) { + scoped_ptr<Window> w1(CreateTestWindow()); + w1->Show(); + scoped_ptr<Window> w2(CreateTestWindow()); + w1->AddTransientChild(w2.get()); + w2->Show(); + wm::ActivateWindow(w1.get()); + ASSERT_EQ("2 active=0", StateString()); + + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + ASSERT_EQ("0 M2 active=1", StateString()); + EXPECT_TRUE(wm::IsActiveWindow(w1.get())); + + // Create another transient child of |w1|. We do this unparented, set up the + // transient parent then set parent. This is how NativeWidgetAura does things + // too. + scoped_ptr<Window> w3(CreateTestWindowUnparented()); + w1->AddTransientChild(w3.get()); + w3->SetParent(NULL); + w3->Show(); + ASSERT_EQ("0 M3 active=1", StateString()); + + // Minimize the window. All the transients are hidden as a result, so it ends + // up in pending. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + ASSERT_EQ("0 P=M3 active=0", StateString()); + + // Restore and everything should go back to the first workspace. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + ASSERT_EQ("3 active=0", StateString()); +} + +} // namespace internal +} // namespace ash diff --git a/ash/wm/workspace_controller.cc b/ash/wm/workspace_controller.cc index 0592513..6abd041 100644 --- a/ash/wm/workspace_controller.cc +++ b/ash/wm/workspace_controller.cc @@ -4,10 +4,13 @@ #include "ash/wm/workspace_controller.h" +#include "ash/ash_switches.h" #include "ash/wm/window_util.h" #include "ash/wm/workspace/workspace_event_filter.h" #include "ash/wm/workspace/workspace_layout_manager.h" #include "ash/wm/workspace/workspace_manager.h" +#include "ash/wm/workspace/workspace_manager2.h" +#include "base/command_line.h" #include "ui/aura/client/activation_client.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/root_window.h" @@ -28,12 +31,18 @@ WorkspaceController::WorkspaceController(aura::Window* viewport) layout_manager_(NULL), event_filter_(NULL) { aura::RootWindow* root_window = viewport->GetRootWindow(); - event_filter_ = new WorkspaceEventFilter(viewport); - viewport->SetEventFilter(event_filter_); - WorkspaceManager* workspace_manager = new WorkspaceManager(viewport); - workspace_manager_.reset(workspace_manager); - layout_manager_ = new WorkspaceLayoutManager(root_window, workspace_manager); - viewport->SetLayoutManager(layout_manager_); + if (IsWorkspace2Enabled()) { + WorkspaceManager2* workspace_manager = new WorkspaceManager2(viewport); + workspace_manager_.reset(workspace_manager); + } else { + WorkspaceManager* workspace_manager = new WorkspaceManager(viewport); + workspace_manager_.reset(workspace_manager); + layout_manager_ = new WorkspaceLayoutManager( + root_window, workspace_manager); + viewport->SetLayoutManager(layout_manager_); + event_filter_ = new WorkspaceEventFilter(viewport); + viewport->SetEventFilter(event_filter_); + } aura::client::GetActivationClient(root_window)->AddObserver(this); SetGridSize(kGridSize); } @@ -42,10 +51,16 @@ WorkspaceController::~WorkspaceController() { aura::client::GetActivationClient(viewport_->GetRootWindow())-> RemoveObserver(this); // WorkspaceLayoutManager may attempt to access state from us. Destroy it now. - if (viewport_->layout_manager() == layout_manager_) + if (layout_manager_ && viewport_->layout_manager() == layout_manager_) viewport_->SetLayoutManager(NULL); } +// static +bool WorkspaceController::IsWorkspace2Enabled() { + return CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAshEnableWorkspace2); +} + bool WorkspaceController::IsInMaximizedMode() const { return workspace_manager_->IsInMaximizedMode(); } diff --git a/ash/wm/workspace_controller.h b/ash/wm/workspace_controller.h index d7f7b6b..46d6efb 100644 --- a/ash/wm/workspace_controller.h +++ b/ash/wm/workspace_controller.h @@ -33,6 +33,9 @@ class ASH_EXPORT WorkspaceController explicit WorkspaceController(aura::Window* viewport); virtual ~WorkspaceController(); + // Returns true if Workspace2 is enabled. + static bool IsWorkspace2Enabled(); + // Returns true if in maximized or fullscreen mode. bool IsInMaximizedMode() const; @@ -62,6 +65,9 @@ class ASH_EXPORT WorkspaceController scoped_ptr<BaseWorkspaceManager> workspace_manager_; + // TODO(sky): remove |layout_manager_| and |event_filter_| when Workspace2 + // is the default. + // Owned by the window its attached to. WorkspaceLayoutManager* layout_manager_; diff --git a/ash/wm/workspace_controller_test_helper.cc b/ash/wm/workspace_controller_test_helper.cc index 75177ef..df83eca 100644 --- a/ash/wm/workspace_controller_test_helper.cc +++ b/ash/wm/workspace_controller_test_helper.cc @@ -20,6 +20,9 @@ WorkspaceControllerTestHelper::~WorkspaceControllerTestHelper() { } WorkspaceEventFilter* WorkspaceControllerTestHelper::GetFilter() { + if (WorkspaceController::IsWorkspace2Enabled()) + return static_cast<WorkspaceEventFilter*>( + controller_->viewport_->children()[0]->event_filter()); return controller_->event_filter_; } diff --git a/ash/wm/workspace_controller_test_helper.h b/ash/wm/workspace_controller_test_helper.h index c48f096..38abfb2 100644 --- a/ash/wm/workspace_controller_test_helper.h +++ b/ash/wm/workspace_controller_test_helper.h @@ -7,6 +7,7 @@ #include "ash/wm/workspace_controller.h" #include "ash/wm/workspace/workspace_manager.h" +#include "ash/wm/workspace/workspace_manager2.h" namespace ash { namespace internal { @@ -25,6 +26,10 @@ class WorkspaceControllerTestHelper { return static_cast<WorkspaceManager*>( controller_->workspace_manager_.get()); } + WorkspaceManager2* workspace_manager2() { + return static_cast<WorkspaceManager2*>( + controller_->workspace_manager_.get()); + } private: WorkspaceController* controller_; diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 068b131..0ebf4c3 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -6033,6 +6033,12 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_FLAGS_DISABLE_BOOT_ANIMATION_DESCRIPTION" desc="Description for the flag to disable wallpaper boot animation (except for OOBE)."> Disables wallpaper boot animation (except for OOBE case). </message> + <message name="IDS_FLAGS_ENABLE_WORKSPACE2" desc="Description for the flag to turn on workspace2."> + Enable new window stacking. + </message> + <message name="IDS_FLAGS_ENABLE_WORKSPACE2_DESCRIPTION" desc="Description for the flag to turn on workspace2."> + Enable new window stacking. + </message> </if> <message name="IDS_FLAGS_OLD_CHECKBOX_STYLE" desc="Name of the flag to turn off the new checkbox style."> diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 54438cd..51ef078 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc @@ -852,6 +852,13 @@ const Experiment kExperiments[] = { kOsCrOS, SINGLE_VALUE_TYPE(switches::kDisableBootAnimation), }, + { + "enable-workspace2", + IDS_FLAGS_ENABLE_WORKSPACE2, + IDS_FLAGS_ENABLE_WORKSPACE2_DESCRIPTION, + kOsCrOS, + SINGLE_VALUE_TYPE(ash::switches::kAshEnableWorkspace2), + }, #endif { "enable-views-textfield", |