1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ash/wm/system_modal_container_layout_manager.h" 6 7 #include <cmath> 8 9 #include "ash/session/session_state_delegate.h" 10 #include "ash/shell.h" 11 #include "ash/shell_window_ids.h" 12 #include "ash/wm/system_modal_container_event_filter.h" 13 #include "ash/wm/window_animations.h" 14 #include "ash/wm/window_util.h" 15 #include "base/bind.h" 16 #include "ui/aura/client/aura_constants.h" 17 #include "ui/aura/client/capture_client.h" 18 #include "ui/aura/window.h" 19 #include "ui/aura/window_event_dispatcher.h" 20 #include "ui/aura/window_property.h" 21 #include "ui/base/ui_base_switches_util.h" 22 #include "ui/compositor/layer.h" 23 #include "ui/compositor/layer_animator.h" 24 #include "ui/compositor/scoped_layer_animation_settings.h" 25 #include "ui/events/event.h" 26 #include "ui/gfx/screen.h" 27 #include "ui/keyboard/keyboard_controller.h" 28 #include "ui/views/background.h" 29 #include "ui/views/view.h" 30 #include "ui/views/widget/widget.h" 31 #include "ui/wm/core/compound_event_filter.h" 32 33 DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(ASH_EXPORT, bool); 34 35 namespace ash { 36 37 // If this is set to true, the window will get centered. 38 DEFINE_WINDOW_PROPERTY_KEY(bool, kCenteredKey, false); 39 40 // The center point of the window can diverge this much from the center point 41 // of the container to be kept centered upon resizing operations. 42 const int kCenterPixelDelta = 32; 43 44 //////////////////////////////////////////////////////////////////////////////// 45 // SystemModalContainerLayoutManager, public: 46 47 SystemModalContainerLayoutManager::SystemModalContainerLayoutManager( 48 aura::Window* container) 49 : container_(container), 50 modal_background_(NULL) { 51 } 52 53 SystemModalContainerLayoutManager::~SystemModalContainerLayoutManager() { 54 } 55 56 //////////////////////////////////////////////////////////////////////////////// 57 // SystemModalContainerLayoutManager, aura::LayoutManager implementation: 58 59 void SystemModalContainerLayoutManager::OnWindowResized() { 60 if (modal_background_) { 61 // Note: we have to set the entire bounds with the screen offset. 62 modal_background_->SetBounds( 63 Shell::GetScreen()->GetDisplayNearestWindow(container_).bounds()); 64 } 65 PositionDialogsAfterWorkAreaResize(); 66 } 67 68 void SystemModalContainerLayoutManager::OnWindowAddedToLayout( 69 aura::Window* child) { 70 DCHECK((modal_background_ && child == modal_background_->GetNativeView()) || 71 child->type() == ui::wm::WINDOW_TYPE_NORMAL || 72 child->type() == ui::wm::WINDOW_TYPE_POPUP); 73 DCHECK( 74 container_->id() != kShellWindowId_LockSystemModalContainer || 75 Shell::GetInstance()->session_state_delegate()->IsUserSessionBlocked()); 76 77 child->AddObserver(this); 78 if (child->GetProperty(aura::client::kModalKey) != ui::MODAL_TYPE_NONE) 79 AddModalWindow(child); 80 } 81 82 void SystemModalContainerLayoutManager::OnWillRemoveWindowFromLayout( 83 aura::Window* child) { 84 child->RemoveObserver(this); 85 if (child->GetProperty(aura::client::kModalKey) != ui::MODAL_TYPE_NONE) 86 RemoveModalWindow(child); 87 } 88 89 void SystemModalContainerLayoutManager::OnWindowRemovedFromLayout( 90 aura::Window* child) { 91 } 92 93 void SystemModalContainerLayoutManager::OnChildWindowVisibilityChanged( 94 aura::Window* child, 95 bool visible) { 96 } 97 98 void SystemModalContainerLayoutManager::SetChildBounds( 99 aura::Window* child, 100 const gfx::Rect& requested_bounds) { 101 SetChildBoundsDirect(child, requested_bounds); 102 child->SetProperty(kCenteredKey, DialogIsCentered(requested_bounds)); 103 } 104 105 //////////////////////////////////////////////////////////////////////////////// 106 // SystemModalContainerLayoutManager, aura::WindowObserver implementation: 107 108 void SystemModalContainerLayoutManager::OnWindowPropertyChanged( 109 aura::Window* window, 110 const void* key, 111 intptr_t old) { 112 if (key != aura::client::kModalKey) 113 return; 114 115 if (window->GetProperty(aura::client::kModalKey) != ui::MODAL_TYPE_NONE) { 116 AddModalWindow(window); 117 } else if (static_cast<ui::ModalType>(old) != ui::MODAL_TYPE_NONE) { 118 RemoveModalWindow(window); 119 Shell::GetInstance()->OnModalWindowRemoved(window); 120 } 121 } 122 123 void SystemModalContainerLayoutManager::OnWindowDestroying( 124 aura::Window* window) { 125 if (modal_background_ && modal_background_->GetNativeView() == window) 126 modal_background_ = NULL; 127 } 128 129 //////////////////////////////////////////////////////////////////////////////// 130 // SystemModalContainerLayoutManager, Keyboard::KeybaordControllerObserver 131 // implementation: 132 133 void SystemModalContainerLayoutManager::OnKeyboardBoundsChanging( 134 const gfx::Rect& new_bounds) { 135 PositionDialogsAfterWorkAreaResize(); 136 } 137 138 bool SystemModalContainerLayoutManager::CanWindowReceiveEvents( 139 aura::Window* window) { 140 // We could get when we're at lock screen and there is modal window at 141 // system modal window layer which added event filter. 142 // Now this lock modal windows layer layout manager should not block events 143 // for windows at lock layer. 144 // See SystemModalContainerLayoutManagerTest.EventFocusContainers and 145 // http://crbug.com/157469 146 if (modal_windows_.empty()) 147 return true; 148 // This container can not handle events if the screen is locked and it is not 149 // above the lock screen layer (crbug.com/110920). 150 if (Shell::GetInstance()->session_state_delegate()->IsUserSessionBlocked() && 151 container_->id() < ash::kShellWindowId_LockScreenContainer) 152 return true; 153 return wm::GetActivatableWindow(window) == modal_window(); 154 } 155 156 bool SystemModalContainerLayoutManager::ActivateNextModalWindow() { 157 if (modal_windows_.empty()) 158 return false; 159 wm::ActivateWindow(modal_window()); 160 return true; 161 } 162 163 void SystemModalContainerLayoutManager::CreateModalBackground() { 164 if (!modal_background_) { 165 modal_background_ = new views::Widget; 166 views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL); 167 params.parent = container_; 168 params.bounds = Shell::GetScreen()->GetDisplayNearestWindow( 169 container_).bounds(); 170 modal_background_->Init(params); 171 modal_background_->GetNativeView()->SetName( 172 "SystemModalContainerLayoutManager.ModalBackground"); 173 views::View* contents_view = new views::View(); 174 // TODO(jamescook): This could be SK_ColorWHITE for the new dialog style. 175 contents_view->set_background( 176 views::Background::CreateSolidBackground(SK_ColorBLACK)); 177 modal_background_->SetContentsView(contents_view); 178 modal_background_->GetNativeView()->layer()->SetOpacity(0.0f); 179 // There isn't always a keyboard controller. 180 if (keyboard::KeyboardController::GetInstance()) 181 keyboard::KeyboardController::GetInstance()->AddObserver(this); 182 } 183 184 ui::ScopedLayerAnimationSettings settings( 185 modal_background_->GetNativeView()->layer()->GetAnimator()); 186 // Show should not be called with a target opacity of 0. We therefore start 187 // the fade to show animation before Show() is called. 188 modal_background_->GetNativeView()->layer()->SetOpacity(0.5f); 189 modal_background_->Show(); 190 container_->StackChildAtTop(modal_background_->GetNativeView()); 191 } 192 193 void SystemModalContainerLayoutManager::DestroyModalBackground() { 194 // modal_background_ can be NULL when a root window is shutting down 195 // and OnWindowDestroying is called first. 196 if (modal_background_) { 197 if (keyboard::KeyboardController::GetInstance()) 198 keyboard::KeyboardController::GetInstance()->RemoveObserver(this); 199 ::wm::ScopedHidingAnimationSettings settings( 200 modal_background_->GetNativeView()); 201 modal_background_->Close(); 202 modal_background_->GetNativeView()->layer()->SetOpacity(0.0f); 203 modal_background_ = NULL; 204 } 205 } 206 207 // static 208 bool SystemModalContainerLayoutManager::IsModalBackground( 209 aura::Window* window) { 210 int id = window->parent()->id(); 211 if (id != kShellWindowId_SystemModalContainer && 212 id != kShellWindowId_LockSystemModalContainer) 213 return false; 214 SystemModalContainerLayoutManager* layout_manager = 215 static_cast<SystemModalContainerLayoutManager*>( 216 window->parent()->layout_manager()); 217 return layout_manager->modal_background_ && 218 layout_manager->modal_background_->GetNativeWindow() == window; 219 } 220 221 //////////////////////////////////////////////////////////////////////////////// 222 // SystemModalContainerLayoutManager, private: 223 224 void SystemModalContainerLayoutManager::AddModalWindow(aura::Window* window) { 225 if (modal_windows_.empty()) { 226 aura::Window* capture_window = aura::client::GetCaptureWindow(container_); 227 if (capture_window) 228 capture_window->ReleaseCapture(); 229 } 230 modal_windows_.push_back(window); 231 Shell::GetInstance()->CreateModalBackground(window); 232 window->parent()->StackChildAtTop(window); 233 234 gfx::Rect target_bounds = window->bounds(); 235 target_bounds.AdjustToFit(GetUsableDialogArea()); 236 window->SetBounds(target_bounds); 237 } 238 239 void SystemModalContainerLayoutManager::RemoveModalWindow( 240 aura::Window* window) { 241 aura::Window::Windows::iterator it = 242 std::find(modal_windows_.begin(), modal_windows_.end(), window); 243 if (it != modal_windows_.end()) 244 modal_windows_.erase(it); 245 } 246 247 void SystemModalContainerLayoutManager::PositionDialogsAfterWorkAreaResize() { 248 if (!modal_windows_.empty()) { 249 for (aura::Window::Windows::iterator it = modal_windows_.begin(); 250 it != modal_windows_.end(); ++it) { 251 (*it)->SetBounds(GetCenteredAndOrFittedBounds(*it)); 252 } 253 } 254 } 255 256 gfx::Rect SystemModalContainerLayoutManager::GetUsableDialogArea() { 257 // Instead of resizing the system modal container, we move only the modal 258 // windows. This way we avoid flashing lines upon resize animation and if the 259 // keyboard will not fill left to right, the background is still covered. 260 gfx::Rect valid_bounds = container_->bounds(); 261 keyboard::KeyboardController* keyboard_controller = 262 keyboard::KeyboardController::GetInstance(); 263 if (keyboard_controller) { 264 gfx::Rect bounds = keyboard_controller->current_keyboard_bounds(); 265 if (!bounds.IsEmpty()) { 266 valid_bounds.set_height(std::max( 267 0, valid_bounds.height() - bounds.height())); 268 } 269 } 270 return valid_bounds; 271 } 272 273 gfx::Rect SystemModalContainerLayoutManager::GetCenteredAndOrFittedBounds( 274 const aura::Window* window) { 275 gfx::Rect target_bounds; 276 gfx::Rect usable_area = GetUsableDialogArea(); 277 if (window->GetProperty(kCenteredKey)) { 278 // Keep the dialog centered if it was centered before. 279 target_bounds = usable_area; 280 target_bounds.ClampToCenteredSize(window->bounds().size()); 281 } else { 282 // Keep the dialog within the usable area. 283 target_bounds = window->bounds(); 284 target_bounds.AdjustToFit(usable_area); 285 } 286 if (usable_area != container_->bounds()) { 287 // Don't clamp the dialog for the keyboard. Keep the size as it is but make 288 // sure that the top remains visible. 289 // TODO(skuhne): M37 should add over scroll functionality to address this. 290 target_bounds.set_size(window->bounds().size()); 291 } 292 return target_bounds; 293 } 294 295 bool SystemModalContainerLayoutManager::DialogIsCentered( 296 const gfx::Rect& window_bounds) { 297 gfx::Point window_center = window_bounds.CenterPoint(); 298 gfx::Point container_center = GetUsableDialogArea().CenterPoint(); 299 return 300 std::abs(window_center.x() - container_center.x()) < kCenterPixelDelta && 301 std::abs(window_center.y() - container_center.y()) < kCenterPixelDelta; 302 } 303 304 } // namespace ash 305