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