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/toplevel_window_event_handler.h"
      6 
      7 #include "ash/shell.h"
      8 #include "ash/wm/resize_shadow_controller.h"
      9 #include "ash/wm/window_resizer.h"
     10 #include "ash/wm/window_state.h"
     11 #include "ash/wm/window_state_observer.h"
     12 #include "ash/wm/window_util.h"
     13 #include "ash/wm/workspace/snap_sizer.h"
     14 #include "base/message_loop/message_loop.h"
     15 #include "base/run_loop.h"
     16 #include "ui/aura/client/cursor_client.h"
     17 #include "ui/aura/env.h"
     18 #include "ui/aura/root_window.h"
     19 #include "ui/aura/window.h"
     20 #include "ui/aura/window_delegate.h"
     21 #include "ui/aura/window_observer.h"
     22 #include "ui/base/cursor/cursor.h"
     23 #include "ui/base/hit_test.h"
     24 #include "ui/base/ui_base_types.h"
     25 #include "ui/compositor/layer.h"
     26 #include "ui/compositor/scoped_layer_animation_settings.h"
     27 #include "ui/events/event.h"
     28 #include "ui/events/event_utils.h"
     29 #include "ui/events/gestures/gesture_recognizer.h"
     30 #include "ui/gfx/screen.h"
     31 
     32 namespace {
     33 const double kMinHorizVelocityForWindowSwipe = 1100;
     34 const double kMinVertVelocityForWindowMinimize = 1000;
     35 }
     36 
     37 namespace ash {
     38 
     39 namespace {
     40 
     41 gfx::Point ConvertPointToParent(aura::Window* window,
     42                                 const gfx::Point& point) {
     43   gfx::Point result(point);
     44   aura::Window::ConvertPointToTarget(window, window->parent(), &result);
     45   return result;
     46 }
     47 
     48 }  // namespace
     49 
     50 // ScopedWindowResizer ---------------------------------------------------------
     51 
     52 // Wraps a WindowResizer and installs an observer on its target window.  When
     53 // the window is destroyed ResizerWindowDestroyed() is invoked back on the
     54 // ToplevelWindowEventHandler to clean up.
     55 class ToplevelWindowEventHandler::ScopedWindowResizer
     56     : public aura::WindowObserver,
     57       public wm::WindowStateObserver {
     58  public:
     59   ScopedWindowResizer(ToplevelWindowEventHandler* handler,
     60                       WindowResizer* resizer);
     61   virtual ~ScopedWindowResizer();
     62 
     63   WindowResizer* resizer() { return resizer_.get(); }
     64 
     65   // WindowObserver overrides:
     66   virtual void OnWindowHierarchyChanging(
     67       const HierarchyChangeParams& params) OVERRIDE;
     68   virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
     69 
     70   // WindowStateObserver overrides:
     71   virtual void OnWindowShowTypeChanged(wm::WindowState* window_state,
     72                                        wm::WindowShowType type) OVERRIDE;
     73 
     74  private:
     75   void AddHandlers(aura::Window* container);
     76   void RemoveHandlers();
     77 
     78   ToplevelWindowEventHandler* handler_;
     79   scoped_ptr<WindowResizer> resizer_;
     80 
     81   // If not NULL, this is an additional container that the dragged window has
     82   // moved to which ScopedWindowResizer has temporarily added observers on.
     83   aura::Window* target_container_;
     84 
     85   DISALLOW_COPY_AND_ASSIGN(ScopedWindowResizer);
     86 };
     87 
     88 ToplevelWindowEventHandler::ScopedWindowResizer::ScopedWindowResizer(
     89     ToplevelWindowEventHandler* handler,
     90     WindowResizer* resizer)
     91     : handler_(handler),
     92       resizer_(resizer),
     93       target_container_(NULL) {
     94   if (resizer_) {
     95     resizer_->GetTarget()->AddObserver(this);
     96     wm::GetWindowState(resizer_->GetTarget())->AddObserver(this);
     97   }
     98 }
     99 
    100 ToplevelWindowEventHandler::ScopedWindowResizer::~ScopedWindowResizer() {
    101   RemoveHandlers();
    102   if (resizer_) {
    103     resizer_->GetTarget()->RemoveObserver(this);
    104     wm::GetWindowState(resizer_->GetTarget())->RemoveObserver(this);
    105   }
    106 }
    107 
    108 void ToplevelWindowEventHandler::ScopedWindowResizer::OnWindowHierarchyChanging(
    109     const HierarchyChangeParams& params) {
    110   if (params.receiver != resizer_->GetTarget())
    111     return;
    112   wm::WindowState* state = wm::GetWindowState(params.receiver);
    113   if (state->continue_drag_after_reparent()) {
    114     state->set_continue_drag_after_reparent(false);
    115     AddHandlers(params.new_parent);
    116   } else {
    117     handler_->CompleteDrag(DRAG_COMPLETE, 0);
    118   }
    119 }
    120 
    121 void ToplevelWindowEventHandler::ScopedWindowResizer::OnWindowShowTypeChanged(
    122     wm::WindowState* window_state,
    123     wm::WindowShowType old) {
    124   if (!window_state->IsNormalShowState())
    125     handler_->CompleteDrag(DRAG_COMPLETE, 0);
    126 }
    127 
    128 void ToplevelWindowEventHandler::ScopedWindowResizer::OnWindowDestroying(
    129     aura::Window* window) {
    130   DCHECK(resizer_.get());
    131   DCHECK_EQ(resizer_->GetTarget(), window);
    132   handler_->ResizerWindowDestroyed();
    133 }
    134 
    135 void ToplevelWindowEventHandler::ScopedWindowResizer::AddHandlers(
    136     aura::Window* container) {
    137   RemoveHandlers();
    138   if (!handler_->owner()->Contains(container)) {
    139     container->AddPreTargetHandler(handler_);
    140     container->AddPostTargetHandler(handler_);
    141     target_container_ = container;
    142   }
    143 }
    144 
    145 void ToplevelWindowEventHandler::ScopedWindowResizer::RemoveHandlers() {
    146   if (target_container_) {
    147     target_container_->RemovePreTargetHandler(handler_);
    148     target_container_->RemovePostTargetHandler(handler_);
    149     target_container_ = NULL;
    150   }
    151 }
    152 
    153 
    154 // ToplevelWindowEventHandler --------------------------------------------------
    155 
    156 ToplevelWindowEventHandler::ToplevelWindowEventHandler(aura::Window* owner)
    157     : owner_(owner),
    158       in_move_loop_(false),
    159       move_cancelled_(false),
    160       in_gesture_drag_(false),
    161       destroyed_(NULL) {
    162   aura::client::SetWindowMoveClient(owner, this);
    163   Shell::GetInstance()->display_controller()->AddObserver(this);
    164   owner->AddPreTargetHandler(this);
    165   owner->AddPostTargetHandler(this);
    166 }
    167 
    168 ToplevelWindowEventHandler::~ToplevelWindowEventHandler() {
    169   Shell::GetInstance()->display_controller()->RemoveObserver(this);
    170   if (destroyed_)
    171     *destroyed_ = true;
    172 }
    173 
    174 void ToplevelWindowEventHandler::OnKeyEvent(ui::KeyEvent* event) {
    175   if (window_resizer_.get() && event->type() == ui::ET_KEY_PRESSED &&
    176       event->key_code() == ui::VKEY_ESCAPE) {
    177     CompleteDrag(DRAG_REVERT, event->flags());
    178   }
    179 }
    180 
    181 void ToplevelWindowEventHandler::OnMouseEvent(
    182     ui::MouseEvent* event) {
    183   if ((event->flags() &
    184       (ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON)) != 0)
    185     return;
    186 
    187   if (in_gesture_drag_)
    188     return;
    189 
    190   aura::Window* target = static_cast<aura::Window*>(event->target());
    191   switch (event->type()) {
    192     case ui::ET_MOUSE_PRESSED:
    193       HandleMousePressed(target, event);
    194       break;
    195     case ui::ET_MOUSE_DRAGGED:
    196       HandleDrag(target, event);
    197       break;
    198     case ui::ET_MOUSE_CAPTURE_CHANGED:
    199     case ui::ET_MOUSE_RELEASED:
    200       HandleMouseReleased(target, event);
    201       break;
    202     case ui::ET_MOUSE_MOVED:
    203       HandleMouseMoved(target, event);
    204       break;
    205     case ui::ET_MOUSE_EXITED:
    206       HandleMouseExited(target, event);
    207       break;
    208     default:
    209       break;
    210   }
    211 }
    212 
    213 void ToplevelWindowEventHandler::OnGestureEvent(ui::GestureEvent* event) {
    214   aura::Window* target = static_cast<aura::Window*>(event->target());
    215   if (!target->delegate())
    216     return;
    217 
    218   if (in_move_loop_ && !in_gesture_drag_)
    219     return;
    220 
    221   switch (event->type()) {
    222     case ui::ET_GESTURE_TAP_DOWN: {
    223       int component =
    224           target->delegate()->GetNonClientComponent(event->location());
    225       if (!(WindowResizer::GetBoundsChangeForWindowComponent(component) &
    226             WindowResizer::kBoundsChange_Resizes))
    227         return;
    228       internal::ResizeShadowController* controller =
    229           Shell::GetInstance()->resize_shadow_controller();
    230       if (controller)
    231         controller->ShowShadow(target, component);
    232       return;
    233     }
    234     case ui::ET_GESTURE_END: {
    235       internal::ResizeShadowController* controller =
    236           Shell::GetInstance()->resize_shadow_controller();
    237       if (controller)
    238         controller->HideShadow(target);
    239       return;
    240     }
    241     case ui::ET_GESTURE_SCROLL_BEGIN: {
    242       if (in_gesture_drag_)
    243         return;
    244       int component =
    245           target->delegate()->GetNonClientComponent(event->location());
    246       if (WindowResizer::GetBoundsChangeForWindowComponent(component) == 0) {
    247         window_resizer_.reset();
    248         return;
    249       }
    250       in_gesture_drag_ = true;
    251       pre_drag_window_bounds_ = target->bounds();
    252       gfx::Point location_in_parent(
    253           ConvertPointToParent(target, event->location()));
    254       CreateScopedWindowResizer(target, location_in_parent, component,
    255                                 aura::client::WINDOW_MOVE_SOURCE_TOUCH);
    256       break;
    257     }
    258     case ui::ET_GESTURE_SCROLL_UPDATE: {
    259       if (!in_gesture_drag_)
    260         return;
    261       if (window_resizer_.get() &&
    262           window_resizer_->resizer()->GetTarget() != target) {
    263         return;
    264       }
    265       HandleDrag(target, event);
    266       break;
    267     }
    268     case ui::ET_GESTURE_SCROLL_END:
    269     case ui::ET_SCROLL_FLING_START: {
    270       if (!in_gesture_drag_)
    271         return;
    272       if (window_resizer_.get() &&
    273           window_resizer_->resizer()->GetTarget() != target) {
    274         return;
    275       }
    276 
    277       CompleteDrag(DRAG_COMPLETE, event->flags());
    278       if (in_move_loop_) {
    279         quit_closure_.Run();
    280         in_move_loop_ = false;
    281       }
    282       in_gesture_drag_ = false;
    283 
    284       if (event->type() == ui::ET_GESTURE_SCROLL_END) {
    285         event->StopPropagation();
    286         return;
    287       }
    288 
    289       int component =
    290           target->delegate()->GetNonClientComponent(event->location());
    291       if (WindowResizer::GetBoundsChangeForWindowComponent(component) == 0)
    292         return;
    293 
    294       wm::WindowState* window_state = wm::GetWindowState(target);
    295       if (!window_state->IsNormalShowState())
    296         return;
    297 
    298       if (fabs(event->details().velocity_y()) >
    299           kMinVertVelocityForWindowMinimize) {
    300         // Minimize/maximize.
    301         if (event->details().velocity_y() > 0 &&
    302             window_state->CanMinimize()) {
    303           window_state->Minimize();
    304           window_state->set_always_restores_to_restore_bounds(true);
    305           window_state->SetRestoreBoundsInParent(pre_drag_window_bounds_);
    306         } else if (window_state->CanMaximize()) {
    307           window_state->SetRestoreBoundsInParent(pre_drag_window_bounds_);
    308           window_state->Maximize();
    309         }
    310       } else if (window_state->CanSnap() &&
    311                  fabs(event->details().velocity_x()) >
    312                  kMinHorizVelocityForWindowSwipe) {
    313         // Snap left/right.
    314         ui::ScopedLayerAnimationSettings scoped_setter(
    315             target->layer()->GetAnimator());
    316         scoped_setter.SetPreemptionStrategy(
    317             ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
    318         internal::SnapSizer::SnapWindow(window_state,
    319             event->details().velocity_x() < 0 ?
    320             internal::SnapSizer::LEFT_EDGE : internal::SnapSizer::RIGHT_EDGE);
    321       }
    322       break;
    323     }
    324     default:
    325       return;
    326   }
    327 
    328   event->StopPropagation();
    329 }
    330 
    331 aura::client::WindowMoveResult ToplevelWindowEventHandler::RunMoveLoop(
    332     aura::Window* source,
    333     const gfx::Vector2d& drag_offset,
    334     aura::client::WindowMoveSource move_source) {
    335   DCHECK(!in_move_loop_);  // Can only handle one nested loop at a time.
    336   in_move_loop_ = true;
    337   move_cancelled_ = false;
    338   aura::Window* root_window = source->GetRootWindow();
    339   DCHECK(root_window);
    340   gfx::Point drag_location;
    341   if (move_source == aura::client::WINDOW_MOVE_SOURCE_TOUCH &&
    342       aura::Env::GetInstance()->is_touch_down()) {
    343     in_gesture_drag_ = true;
    344     bool has_point = ui::GestureRecognizer::Get()->
    345         GetLastTouchPointForTarget(source, &drag_location);
    346     DCHECK(has_point);
    347   } else {
    348     drag_location = root_window->GetDispatcher()->GetLastMouseLocationInRoot();
    349     aura::Window::ConvertPointToTarget(
    350         root_window, source->parent(), &drag_location);
    351   }
    352   // Set the cursor before calling CreateScopedWindowResizer(), as that will
    353   // eventually call LockCursor() and prevent the cursor from changing.
    354   aura::client::CursorClient* cursor_client =
    355       aura::client::GetCursorClient(root_window);
    356   if (cursor_client)
    357     cursor_client->SetCursor(ui::kCursorPointer);
    358   CreateScopedWindowResizer(source, drag_location, HTCAPTION, move_source);
    359   bool destroyed = false;
    360   destroyed_ = &destroyed;
    361   base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
    362   base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
    363   base::RunLoop run_loop(aura::Env::GetInstance()->GetDispatcher());
    364   quit_closure_ = run_loop.QuitClosure();
    365   run_loop.Run();
    366   if (destroyed)
    367     return aura::client::MOVE_CANCELED;
    368   destroyed_ = NULL;
    369   in_gesture_drag_ = in_move_loop_ = false;
    370   return move_cancelled_ ? aura::client::MOVE_CANCELED :
    371       aura::client::MOVE_SUCCESSFUL;
    372 }
    373 
    374 void ToplevelWindowEventHandler::EndMoveLoop() {
    375   if (!in_move_loop_)
    376     return;
    377 
    378   in_move_loop_ = false;
    379   CompleteDrag(DRAG_REVERT, 0);
    380   quit_closure_.Run();
    381 }
    382 
    383 void ToplevelWindowEventHandler::OnDisplayConfigurationChanging() {
    384   if (in_move_loop_) {
    385     move_cancelled_ = true;
    386     EndMoveLoop();
    387   } else {
    388     CompleteDrag(DRAG_REVERT, 0);
    389   }
    390 }
    391 
    392 void ToplevelWindowEventHandler::CreateScopedWindowResizer(
    393     aura::Window* window,
    394     const gfx::Point& point_in_parent,
    395     int window_component,
    396     aura::client::WindowMoveSource source) {
    397   window_resizer_.reset();
    398   WindowResizer* resizer =
    399       CreateWindowResizer(window, point_in_parent, window_component,
    400                           source).release();
    401   if (resizer)
    402     window_resizer_.reset(new ScopedWindowResizer(this, resizer));
    403 }
    404 
    405 void ToplevelWindowEventHandler::CompleteDrag(DragCompletionStatus status,
    406                                               int event_flags) {
    407   scoped_ptr<ScopedWindowResizer> resizer(window_resizer_.release());
    408   if (resizer) {
    409     if (status == DRAG_COMPLETE)
    410       resizer->resizer()->CompleteDrag(event_flags);
    411     else
    412       resizer->resizer()->RevertDrag();
    413   }
    414 }
    415 
    416 void ToplevelWindowEventHandler::HandleMousePressed(
    417     aura::Window* target,
    418     ui::MouseEvent* event) {
    419   // Move/size operations are initiated post-target handling to give the target
    420   // an opportunity to cancel this default behavior by returning ER_HANDLED.
    421   if (ui::EventCanceledDefaultHandling(*event))
    422     return;
    423 
    424   // We also update the current window component here because for the
    425   // mouse-drag-release-press case, where the mouse is released and
    426   // pressed without mouse move event.
    427   int component =
    428       target->delegate()->GetNonClientComponent(event->location());
    429   if ((event->flags() &
    430         (ui::EF_IS_DOUBLE_CLICK | ui::EF_IS_TRIPLE_CLICK)) == 0 &&
    431       WindowResizer::GetBoundsChangeForWindowComponent(component)) {
    432     gfx::Point location_in_parent(
    433         ConvertPointToParent(target, event->location()));
    434     CreateScopedWindowResizer(target, location_in_parent, component,
    435                               aura::client::WINDOW_MOVE_SOURCE_MOUSE);
    436   } else {
    437     window_resizer_.reset();
    438   }
    439   if (WindowResizer::GetBoundsChangeForWindowComponent(component) != 0)
    440     event->StopPropagation();
    441 }
    442 
    443 void ToplevelWindowEventHandler::HandleMouseReleased(
    444     aura::Window* target,
    445     ui::MouseEvent* event) {
    446   if (event->phase() != ui::EP_PRETARGET)
    447     return;
    448 
    449   CompleteDrag(event->type() == ui::ET_MOUSE_RELEASED ?
    450                     DRAG_COMPLETE : DRAG_REVERT,
    451                 event->flags());
    452   if (in_move_loop_) {
    453     quit_closure_.Run();
    454     in_move_loop_ = false;
    455   }
    456   // Completing the drag may result in hiding the window. If this happens
    457   // return true so no other handlers/observers see the event. Otherwise
    458   // they see the event on a hidden window.
    459   if (event->type() == ui::ET_MOUSE_CAPTURE_CHANGED &&
    460       !target->IsVisible()) {
    461     event->StopPropagation();
    462   }
    463 }
    464 
    465 void ToplevelWindowEventHandler::HandleDrag(
    466     aura::Window* target,
    467     ui::LocatedEvent* event) {
    468   // This function only be triggered to move window
    469   // by mouse drag or touch move event.
    470   DCHECK(event->type() == ui::ET_MOUSE_DRAGGED ||
    471          event->type() == ui::ET_TOUCH_MOVED ||
    472          event->type() == ui::ET_GESTURE_SCROLL_UPDATE);
    473 
    474   // Drag actions are performed pre-target handling to prevent spurious mouse
    475   // moves from the move/size operation from being sent to the target.
    476   if (event->phase() != ui::EP_PRETARGET)
    477     return;
    478 
    479   if (!window_resizer_)
    480     return;
    481   window_resizer_->resizer()->Drag(
    482       ConvertPointToParent(target, event->location()), event->flags());
    483   event->StopPropagation();
    484 }
    485 
    486 void ToplevelWindowEventHandler::HandleMouseMoved(
    487     aura::Window* target,
    488     ui::LocatedEvent* event) {
    489   // Shadow effects are applied after target handling. Note that we don't
    490   // respect ER_HANDLED here right now since we have not had a reason to allow
    491   // the target to cancel shadow rendering.
    492   if (event->phase() != ui::EP_POSTTARGET)
    493     return;
    494 
    495   // TODO(jamescook): Move the resize cursor update code into here from
    496   // CompoundEventFilter?
    497   internal::ResizeShadowController* controller =
    498       Shell::GetInstance()->resize_shadow_controller();
    499   if (controller) {
    500     if (event->flags() & ui::EF_IS_NON_CLIENT) {
    501       int component =
    502           target->delegate()->GetNonClientComponent(event->location());
    503       controller->ShowShadow(target, component);
    504     } else {
    505       controller->HideShadow(target);
    506     }
    507   }
    508 }
    509 
    510 void ToplevelWindowEventHandler::HandleMouseExited(
    511     aura::Window* target,
    512     ui::LocatedEvent* event) {
    513   // Shadow effects are applied after target handling. Note that we don't
    514   // respect ER_HANDLED here right now since we have not had a reason to allow
    515   // the target to cancel shadow rendering.
    516   if (event->phase() != ui::EP_POSTTARGET)
    517     return;
    518 
    519   internal::ResizeShadowController* controller =
    520       Shell::GetInstance()->resize_shadow_controller();
    521   if (controller)
    522     controller->HideShadow(target);
    523 }
    524 
    525 void ToplevelWindowEventHandler::ResizerWindowDestroyed() {
    526   // We explicitly don't invoke RevertDrag() since that may do things to window.
    527   // Instead we destroy the resizer.
    528   window_resizer_.reset();
    529 
    530   // End the move loop. This does nothing if we're not in a move loop.
    531   EndMoveLoop();
    532 }
    533 
    534 }  // namespace ash
    535