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/property_util.h"
      9 #include "ash/wm/resize_shadow_controller.h"
     10 #include "ash/wm/window_properties.h"
     11 #include "ash/wm/window_resizer.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/aura_constants.h"
     17 #include "ui/aura/client/cursor_client.h"
     18 #include "ui/aura/env.h"
     19 #include "ui/aura/root_window.h"
     20 #include "ui/aura/window.h"
     21 #include "ui/aura/window_delegate.h"
     22 #include "ui/aura/window_observer.h"
     23 #include "ui/base/cursor/cursor.h"
     24 #include "ui/base/events/event.h"
     25 #include "ui/base/events/event_utils.h"
     26 #include "ui/base/gestures/gesture_recognizer.h"
     27 #include "ui/base/hit_test.h"
     28 #include "ui/base/ui_base_types.h"
     29 #include "ui/compositor/layer.h"
     30 #include "ui/compositor/scoped_layer_animation_settings.h"
     31 #include "ui/gfx/screen.h"
     32 
     33 namespace {
     34 const double kMinHorizVelocityForWindowSwipe = 1100;
     35 const double kMinVertVelocityForWindowMinimize = 1000;
     36 }
     37 
     38 namespace ash {
     39 
     40 namespace {
     41 
     42 gfx::Point ConvertPointToParent(aura::Window* window,
     43                                 const gfx::Point& point) {
     44   gfx::Point result(point);
     45   aura::Window::ConvertPointToTarget(window, window->parent(), &result);
     46   return result;
     47 }
     48 
     49 }  // namespace
     50 
     51 // ScopedWindowResizer ---------------------------------------------------------
     52 
     53 // Wraps a WindowResizer and installs an observer on its target window.  When
     54 // the window is destroyed ResizerWindowDestroyed() is invoked back on the
     55 // ToplevelWindowEventHandler to clean up.
     56 class ToplevelWindowEventHandler::ScopedWindowResizer
     57     : public aura::WindowObserver {
     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 OnWindowPropertyChanged(aura::Window* window,
     69                                        const void* key,
     70                                        intptr_t old) OVERRIDE;
     71   virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
     72 
     73  private:
     74   void AddHandlers(aura::Window* container);
     75   void RemoveHandlers();
     76 
     77   ToplevelWindowEventHandler* handler_;
     78   scoped_ptr<WindowResizer> resizer_;
     79 
     80   // If not NULL, this is an additional container that the dragged window has
     81   // moved to which ScopedWindowResizer has temporarily added observers on.
     82   aura::Window* target_container_;
     83 
     84   DISALLOW_COPY_AND_ASSIGN(ScopedWindowResizer);
     85 };
     86 
     87 ToplevelWindowEventHandler::ScopedWindowResizer::ScopedWindowResizer(
     88     ToplevelWindowEventHandler* handler,
     89     WindowResizer* resizer)
     90     : handler_(handler),
     91       resizer_(resizer),
     92       target_container_(NULL) {
     93   if (resizer_)
     94     resizer_->GetTarget()->AddObserver(this);
     95 }
     96 
     97 ToplevelWindowEventHandler::ScopedWindowResizer::~ScopedWindowResizer() {
     98   RemoveHandlers();
     99   if (resizer_)
    100     resizer_->GetTarget()->RemoveObserver(this);
    101 }
    102 
    103 void ToplevelWindowEventHandler::ScopedWindowResizer::OnWindowHierarchyChanging(
    104     const HierarchyChangeParams& params) {
    105   if (params.receiver != resizer_->GetTarget())
    106     return;
    107 
    108   if (params.receiver->GetProperty(internal::kContinueDragAfterReparent)) {
    109     params.receiver->SetProperty(internal::kContinueDragAfterReparent, false);
    110     AddHandlers(params.new_parent);
    111   } else {
    112     handler_->CompleteDrag(DRAG_COMPLETE, 0);
    113   }
    114 }
    115 
    116 void ToplevelWindowEventHandler::ScopedWindowResizer::OnWindowPropertyChanged(
    117     aura::Window* window,
    118     const void* key,
    119     intptr_t old) {
    120   if (key == aura::client::kShowStateKey && !wm::IsWindowNormal(window))
    121     handler_->CompleteDrag(DRAG_COMPLETE, 0);
    122 }
    123 
    124 void ToplevelWindowEventHandler::ScopedWindowResizer::OnWindowDestroying(
    125     aura::Window* window) {
    126   DCHECK(resizer_.get());
    127   DCHECK_EQ(resizer_->GetTarget(), window);
    128   handler_->ResizerWindowDestroyed();
    129 }
    130 
    131 void ToplevelWindowEventHandler::ScopedWindowResizer::AddHandlers(
    132     aura::Window* container) {
    133   RemoveHandlers();
    134   if (!handler_->owner()->Contains(container)) {
    135     container->AddPreTargetHandler(handler_);
    136     container->AddPostTargetHandler(handler_);
    137     target_container_ = container;
    138   }
    139 }
    140 
    141 void ToplevelWindowEventHandler::ScopedWindowResizer::RemoveHandlers() {
    142   if (target_container_) {
    143     target_container_->RemovePreTargetHandler(handler_);
    144     target_container_->RemovePostTargetHandler(handler_);
    145     target_container_ = NULL;
    146   }
    147 }
    148 
    149 
    150 // ToplevelWindowEventHandler --------------------------------------------------
    151 
    152 ToplevelWindowEventHandler::ToplevelWindowEventHandler(aura::Window* owner)
    153     : owner_(owner),
    154       in_move_loop_(false),
    155       move_cancelled_(false),
    156       in_gesture_drag_(false),
    157       destroyed_(NULL) {
    158   aura::client::SetWindowMoveClient(owner, this);
    159   Shell::GetInstance()->display_controller()->AddObserver(this);
    160   owner->AddPreTargetHandler(this);
    161   owner->AddPostTargetHandler(this);
    162 }
    163 
    164 ToplevelWindowEventHandler::~ToplevelWindowEventHandler() {
    165   Shell::GetInstance()->display_controller()->RemoveObserver(this);
    166   if (destroyed_)
    167     *destroyed_ = true;
    168 }
    169 
    170 void ToplevelWindowEventHandler::OnKeyEvent(ui::KeyEvent* event) {
    171   if (window_resizer_.get() && event->type() == ui::ET_KEY_PRESSED &&
    172       event->key_code() == ui::VKEY_ESCAPE) {
    173     CompleteDrag(DRAG_REVERT, event->flags());
    174   }
    175 }
    176 
    177 void ToplevelWindowEventHandler::OnMouseEvent(
    178     ui::MouseEvent* event) {
    179   if ((event->flags() &
    180       (ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON)) != 0)
    181     return;
    182 
    183   if (in_gesture_drag_)
    184     return;
    185 
    186   aura::Window* target = static_cast<aura::Window*>(event->target());
    187   switch (event->type()) {
    188     case ui::ET_MOUSE_PRESSED:
    189       HandleMousePressed(target, event);
    190       break;
    191     case ui::ET_MOUSE_DRAGGED:
    192       HandleDrag(target, event);
    193       break;
    194     case ui::ET_MOUSE_CAPTURE_CHANGED:
    195     case ui::ET_MOUSE_RELEASED:
    196       HandleMouseReleased(target, event);
    197       break;
    198     case ui::ET_MOUSE_MOVED:
    199       HandleMouseMoved(target, event);
    200       break;
    201     case ui::ET_MOUSE_EXITED:
    202       HandleMouseExited(target, event);
    203       break;
    204     default:
    205       break;
    206   }
    207 }
    208 
    209 void ToplevelWindowEventHandler::OnGestureEvent(ui::GestureEvent* event) {
    210   aura::Window* target = static_cast<aura::Window*>(event->target());
    211   if (!target->delegate())
    212     return;
    213 
    214   if (in_move_loop_ && !in_gesture_drag_)
    215     return;
    216 
    217   switch (event->type()) {
    218     case ui::ET_GESTURE_TAP_DOWN: {
    219       int component =
    220           target->delegate()->GetNonClientComponent(event->location());
    221       if (!(WindowResizer::GetBoundsChangeForWindowComponent(component) &
    222             WindowResizer::kBoundsChange_Resizes))
    223         return;
    224       internal::ResizeShadowController* controller =
    225           Shell::GetInstance()->resize_shadow_controller();
    226       if (controller)
    227         controller->ShowShadow(target, component);
    228       return;
    229     }
    230     case ui::ET_GESTURE_END: {
    231       internal::ResizeShadowController* controller =
    232           Shell::GetInstance()->resize_shadow_controller();
    233       if (controller)
    234         controller->HideShadow(target);
    235       return;
    236     }
    237     case ui::ET_GESTURE_SCROLL_BEGIN: {
    238       if (in_gesture_drag_)
    239         return;
    240       int component =
    241           target->delegate()->GetNonClientComponent(event->location());
    242       if (WindowResizer::GetBoundsChangeForWindowComponent(component) == 0) {
    243         window_resizer_.reset();
    244         return;
    245       }
    246       in_gesture_drag_ = true;
    247       pre_drag_window_bounds_ = target->bounds();
    248       gfx::Point location_in_parent(
    249           ConvertPointToParent(target, event->location()));
    250       CreateScopedWindowResizer(target, location_in_parent, component,
    251                                 aura::client::WINDOW_MOVE_SOURCE_TOUCH);
    252       break;
    253     }
    254     case ui::ET_GESTURE_SCROLL_UPDATE: {
    255       if (!in_gesture_drag_)
    256         return;
    257       if (window_resizer_.get() &&
    258           window_resizer_->resizer()->GetTarget() != target) {
    259         return;
    260       }
    261       HandleDrag(target, event);
    262       break;
    263     }
    264     case ui::ET_GESTURE_SCROLL_END:
    265     case ui::ET_SCROLL_FLING_START: {
    266       if (!in_gesture_drag_)
    267         return;
    268       if (window_resizer_.get() &&
    269           window_resizer_->resizer()->GetTarget() != target) {
    270         return;
    271       }
    272 
    273       CompleteDrag(DRAG_COMPLETE, event->flags());
    274       if (in_move_loop_) {
    275         quit_closure_.Run();
    276         in_move_loop_ = false;
    277       }
    278       in_gesture_drag_ = false;
    279 
    280       if (event->type() == ui::ET_GESTURE_SCROLL_END) {
    281         event->StopPropagation();
    282         return;
    283       }
    284 
    285       int component =
    286           target->delegate()->GetNonClientComponent(event->location());
    287       if (WindowResizer::GetBoundsChangeForWindowComponent(component) == 0)
    288         return;
    289       if (!wm::IsWindowNormal(target))
    290         return;
    291 
    292       if (fabs(event->details().velocity_y()) >
    293           kMinVertVelocityForWindowMinimize) {
    294         // Minimize/maximize.
    295         if (event->details().velocity_y() > 0 &&
    296             wm::CanMinimizeWindow(target)) {
    297           wm::MinimizeWindow(target);
    298           SetWindowAlwaysRestoresToRestoreBounds(target, true);
    299           SetRestoreBoundsInParent(target, pre_drag_window_bounds_);
    300         } else if (wm::CanMaximizeWindow(target)) {
    301           SetRestoreBoundsInParent(target, pre_drag_window_bounds_);
    302           wm::MaximizeWindow(target);
    303         }
    304       } else if (wm::CanSnapWindow(target) &&
    305                  fabs(event->details().velocity_x()) >
    306                  kMinHorizVelocityForWindowSwipe) {
    307         // Snap left/right.
    308         ui::ScopedLayerAnimationSettings scoped_setter(
    309             target->layer()->GetAnimator());
    310         scoped_setter.SetPreemptionStrategy(
    311             ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
    312         internal::SnapSizer::SnapWindow(target,
    313             event->details().velocity_x() < 0 ?
    314             internal::SnapSizer::LEFT_EDGE : internal::SnapSizer::RIGHT_EDGE);
    315       }
    316       break;
    317     }
    318     default:
    319       return;
    320   }
    321 
    322   event->StopPropagation();
    323 }
    324 
    325 aura::client::WindowMoveResult ToplevelWindowEventHandler::RunMoveLoop(
    326     aura::Window* source,
    327     const gfx::Vector2d& drag_offset,
    328     aura::client::WindowMoveSource move_source) {
    329   DCHECK(!in_move_loop_);  // Can only handle one nested loop at a time.
    330   in_move_loop_ = true;
    331   move_cancelled_ = false;
    332   aura::RootWindow* root_window = source->GetRootWindow();
    333   DCHECK(root_window);
    334   gfx::Point drag_location;
    335   if (move_source == aura::client::WINDOW_MOVE_SOURCE_TOUCH &&
    336       aura::Env::GetInstance()->is_touch_down()) {
    337     in_gesture_drag_ = true;
    338     bool has_point = root_window->gesture_recognizer()->
    339         GetLastTouchPointForTarget(source, &drag_location);
    340     DCHECK(has_point);
    341   } else {
    342     drag_location = root_window->GetLastMouseLocationInRoot();
    343     aura::Window::ConvertPointToTarget(
    344         root_window, source->parent(), &drag_location);
    345   }
    346   // Set the cursor before calling CreateScopedWindowResizer(), as that will
    347   // eventually call LockCursor() and prevent the cursor from changing.
    348   aura::client::CursorClient* cursor_client =
    349       aura::client::GetCursorClient(root_window);
    350   if (cursor_client)
    351     cursor_client->SetCursor(ui::kCursorPointer);
    352   CreateScopedWindowResizer(source, drag_location, HTCAPTION, move_source);
    353   bool destroyed = false;
    354   destroyed_ = &destroyed;
    355 #if !defined(OS_MACOSX)
    356   base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
    357   base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
    358   base::RunLoop run_loop(aura::Env::GetInstance()->GetDispatcher());
    359   quit_closure_ = run_loop.QuitClosure();
    360   run_loop.Run();
    361 #endif  // !defined(OS_MACOSX)
    362   if (destroyed)
    363     return aura::client::MOVE_CANCELED;
    364   destroyed_ = NULL;
    365   in_gesture_drag_ = in_move_loop_ = false;
    366   return move_cancelled_ ? aura::client::MOVE_CANCELED :
    367       aura::client::MOVE_SUCCESSFUL;
    368 }
    369 
    370 void ToplevelWindowEventHandler::EndMoveLoop() {
    371   if (!in_move_loop_)
    372     return;
    373 
    374   in_move_loop_ = false;
    375   if (window_resizer_) {
    376     window_resizer_->resizer()->RevertDrag();
    377     window_resizer_.reset();
    378   }
    379   quit_closure_.Run();
    380 }
    381 
    382 void ToplevelWindowEventHandler::OnDisplayConfigurationChanging() {
    383   if (in_move_loop_) {
    384     move_cancelled_ = true;
    385     EndMoveLoop();
    386   } else if (window_resizer_) {
    387     window_resizer_->resizer()->RevertDrag();
    388     window_resizer_.reset();
    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