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     : 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