Home | History | Annotate | Download | only in wm
      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