Home | History | Annotate | Download | only in dock
      1 // Copyright 2013 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/dock/docked_window_resizer.h"
      6 
      7 #include "ash/display/display_controller.h"
      8 #include "ash/root_window_controller.h"
      9 #include "ash/screen_util.h"
     10 #include "ash/shelf/shelf.h"
     11 #include "ash/shelf/shelf_types.h"
     12 #include "ash/shelf/shelf_widget.h"
     13 #include "ash/shell.h"
     14 #include "ash/shell_window_ids.h"
     15 #include "ash/wm/coordinate_conversion.h"
     16 #include "ash/wm/dock/docked_window_layout_manager.h"
     17 #include "ash/wm/window_state.h"
     18 #include "ash/wm/window_util.h"
     19 #include "ash/wm/workspace/magnetism_matcher.h"
     20 #include "ash/wm/workspace/workspace_window_resizer.h"
     21 #include "base/command_line.h"
     22 #include "base/memory/weak_ptr.h"
     23 #include "ui/aura/client/aura_constants.h"
     24 #include "ui/aura/client/window_tree_client.h"
     25 #include "ui/aura/env.h"
     26 #include "ui/aura/window.h"
     27 #include "ui/aura/window_delegate.h"
     28 #include "ui/aura/window_event_dispatcher.h"
     29 #include "ui/base/hit_test.h"
     30 #include "ui/base/ui_base_types.h"
     31 #include "ui/gfx/screen.h"
     32 #include "ui/views/widget/widget.h"
     33 
     34 namespace ash {
     35 namespace {
     36 
     37 DockedWindowLayoutManager* GetDockedLayoutManagerAtPoint(
     38     const gfx::Point& point) {
     39   gfx::Display display = ScreenUtil::FindDisplayContainingPoint(point);
     40   if (!display.is_valid())
     41     return NULL;
     42   aura::Window* root = Shell::GetInstance()->display_controller()->
     43       GetRootWindowForDisplayId(display.id());
     44   aura::Window* dock_container = Shell::GetContainer(
     45       root, kShellWindowId_DockedContainer);
     46   return static_cast<DockedWindowLayoutManager*>(
     47       dock_container->layout_manager());
     48 }
     49 
     50 }  // namespace
     51 
     52 DockedWindowResizer::~DockedWindowResizer() {
     53 }
     54 
     55 // static
     56 DockedWindowResizer*
     57 DockedWindowResizer::Create(WindowResizer* next_window_resizer,
     58                             wm::WindowState* window_state) {
     59   return new DockedWindowResizer(next_window_resizer, window_state);
     60 }
     61 
     62 void DockedWindowResizer::Drag(const gfx::Point& location, int event_flags) {
     63   last_location_ = location;
     64   wm::ConvertPointToScreen(GetTarget()->parent(), &last_location_);
     65   if (!did_move_or_resize_) {
     66     did_move_or_resize_ = true;
     67     StartedDragging();
     68   }
     69   gfx::Point offset;
     70   gfx::Rect bounds(CalculateBoundsForDrag(location));
     71   MaybeSnapToEdge(bounds, &offset);
     72   gfx::Point modified_location(location);
     73   modified_location.Offset(offset.x(), offset.y());
     74 
     75   base::WeakPtr<DockedWindowResizer> resizer(weak_ptr_factory_.GetWeakPtr());
     76   next_window_resizer_->Drag(modified_location, event_flags);
     77   if (!resizer)
     78     return;
     79 
     80   DockedWindowLayoutManager* new_dock_layout =
     81       GetDockedLayoutManagerAtPoint(last_location_);
     82   if (new_dock_layout && new_dock_layout != dock_layout_) {
     83     // The window is being dragged to a new display. If the previous
     84     // container is the current parent of the window it will be informed of
     85     // the end of drag when the window is reparented, otherwise let the
     86     // previous container know the drag is complete. If we told the
     87     // window's parent that the drag was complete it would begin
     88     // positioning the window.
     89     if (is_docked_ && dock_layout_->is_dragged_window_docked())
     90       dock_layout_->UndockDraggedWindow();
     91     if (dock_layout_ != initial_dock_layout_)
     92       dock_layout_->FinishDragging(
     93           DOCKED_ACTION_NONE,
     94           details().source == aura::client::WINDOW_MOVE_SOURCE_MOUSE ?
     95               DOCKED_ACTION_SOURCE_MOUSE : DOCKED_ACTION_SOURCE_TOUCH);
     96     is_docked_ = false;
     97     dock_layout_ = new_dock_layout;
     98     // The window's initial layout manager already knows that the drag is
     99     // in progress for this window.
    100     if (new_dock_layout != initial_dock_layout_)
    101       new_dock_layout->StartDragging(GetTarget());
    102   }
    103   // Window could get docked by the WorkspaceWindowResizer, update the state.
    104   is_docked_ = dock_layout_->is_dragged_window_docked();
    105   // Whenever a window is dragged out of the dock it will be auto-sized
    106   // in the dock if it gets docked again.
    107   if (!is_docked_)
    108     was_bounds_changed_by_user_ = false;
    109 }
    110 
    111 void DockedWindowResizer::CompleteDrag() {
    112   // The root window can change when dragging into a different screen.
    113   next_window_resizer_->CompleteDrag();
    114   FinishedDragging(aura::client::MOVE_SUCCESSFUL);
    115 }
    116 
    117 void DockedWindowResizer::RevertDrag() {
    118   next_window_resizer_->RevertDrag();
    119   // Restore docked state to what it was before the drag if necessary.
    120   if (is_docked_ != was_docked_) {
    121     is_docked_ = was_docked_;
    122     if (is_docked_)
    123       dock_layout_->DockDraggedWindow(GetTarget());
    124     else
    125       dock_layout_->UndockDraggedWindow();
    126   }
    127   FinishedDragging(aura::client::MOVE_CANCELED);
    128 }
    129 
    130 DockedWindowResizer::DockedWindowResizer(WindowResizer* next_window_resizer,
    131                                          wm::WindowState* window_state)
    132     : WindowResizer(window_state),
    133       next_window_resizer_(next_window_resizer),
    134       dock_layout_(NULL),
    135       initial_dock_layout_(NULL),
    136       did_move_or_resize_(false),
    137       was_docked_(false),
    138       is_docked_(false),
    139       was_bounds_changed_by_user_(window_state->bounds_changed_by_user()),
    140       weak_ptr_factory_(this) {
    141   DCHECK(details().is_resizable);
    142   aura::Window* dock_container = Shell::GetContainer(
    143       GetTarget()->GetRootWindow(),
    144       kShellWindowId_DockedContainer);
    145   dock_layout_ = static_cast<DockedWindowLayoutManager*>(
    146       dock_container->layout_manager());
    147   initial_dock_layout_ = dock_layout_;
    148   was_docked_ = GetTarget()->parent() == dock_container;
    149   is_docked_ = was_docked_;
    150 }
    151 
    152 void DockedWindowResizer::MaybeSnapToEdge(const gfx::Rect& bounds,
    153                                           gfx::Point* offset) {
    154   // Windows only snap magnetically when they were previously docked.
    155   if (!was_docked_)
    156     return;
    157   DockedAlignment dock_alignment = dock_layout_->CalculateAlignment();
    158   gfx::Rect dock_bounds = ScreenUtil::ConvertRectFromScreen(
    159       GetTarget()->parent(),
    160       dock_layout_->dock_container()->GetBoundsInScreen());
    161 
    162   // Short-range magnetism when retaining docked state. Same constant as in
    163   // MagnetismMatcher is used for consistency.
    164   const int kSnapToDockDistance = MagnetismMatcher::kMagneticDistance;
    165 
    166   if (dock_alignment == DOCKED_ALIGNMENT_LEFT ||
    167       dock_alignment == DOCKED_ALIGNMENT_NONE) {
    168     const int distance = bounds.x() - dock_bounds.x();
    169     if (distance < kSnapToDockDistance && distance > 0) {
    170       offset->set_x(-distance);
    171       return;
    172     }
    173   }
    174   if (dock_alignment == DOCKED_ALIGNMENT_RIGHT ||
    175       dock_alignment == DOCKED_ALIGNMENT_NONE) {
    176     const int distance = dock_bounds.right() - bounds.right();
    177     if (distance < kSnapToDockDistance && distance > 0)
    178       offset->set_x(distance);
    179   }
    180 }
    181 
    182 void DockedWindowResizer::StartedDragging() {
    183   // During resizing the window width is preserved by DockedwindowLayoutManager.
    184   if (is_docked_ &&
    185       (details().bounds_change & WindowResizer::kBoundsChange_Resizes)) {
    186     window_state_->set_bounds_changed_by_user(true);
    187   }
    188 
    189   // Tell the dock layout manager that we are dragging this window.
    190   // At this point we are not yet animating the window as it may not be
    191   // inside the docked area.
    192   dock_layout_->StartDragging(GetTarget());
    193   // Reparent workspace windows during the drag to elevate them above workspace.
    194   // Other windows for which the DockedWindowResizer is instantiated include
    195   // panels and windows that are already docked. Those do not need reparenting.
    196   if (GetTarget()->type() != ui::wm::WINDOW_TYPE_PANEL &&
    197       GetTarget()->parent()->id() == kShellWindowId_DefaultContainer) {
    198     // Reparent the window into the docked windows container in order to get it
    199     // on top of other docked windows.
    200     aura::Window* docked_container = Shell::GetContainer(
    201         GetTarget()->GetRootWindow(),
    202         kShellWindowId_DockedContainer);
    203     wm::ReparentChildWithTransientChildren(GetTarget(),
    204                                            GetTarget()->parent(),
    205                                            docked_container);
    206   }
    207   if (is_docked_)
    208     dock_layout_->DockDraggedWindow(GetTarget());
    209 }
    210 
    211 void DockedWindowResizer::FinishedDragging(
    212     aura::client::WindowMoveResult move_result) {
    213   if (!did_move_or_resize_)
    214     return;
    215   did_move_or_resize_ = false;
    216   aura::Window* window = GetTarget();
    217   const bool is_attached_panel = window->type() == ui::wm::WINDOW_TYPE_PANEL &&
    218                                  window_state_->panel_attached();
    219   const bool is_resized =
    220       (details().bounds_change & WindowResizer::kBoundsChange_Resizes) != 0;
    221 
    222   // Undock the window if it is not in the normal or minimized state type. This
    223   // happens if a user snaps or maximizes a window using a keyboard shortcut
    224   // while it is being dragged.
    225   if (!window_state_->IsMinimized() && !window_state_->IsNormalStateType())
    226     is_docked_ = false;
    227 
    228   // When drag is completed the dragged docked window is resized to the bounds
    229   // calculated by the layout manager that conform to other docked windows.
    230   if (!is_attached_panel && is_docked_ && !is_resized) {
    231     gfx::Rect bounds = ScreenUtil::ConvertRectFromScreen(
    232         window->parent(), dock_layout_->dragged_bounds());
    233     if (!bounds.IsEmpty() && bounds.width() != window->bounds().width()) {
    234       window->SetBounds(bounds);
    235     }
    236   }
    237   // If a window has restore bounds, update the restore origin and width but not
    238   // the height (since the height is auto-calculated for the docked windows).
    239   if (is_resized && is_docked_ && window_state_->HasRestoreBounds()) {
    240     gfx::Rect restore_bounds = window->GetBoundsInScreen();
    241     restore_bounds.set_height(
    242         window_state_->GetRestoreBoundsInScreen().height());
    243     window_state_->SetRestoreBoundsInScreen(restore_bounds);
    244   }
    245 
    246   // Check if the window needs to be docked or returned to workspace.
    247   DockedAction action = MaybeReparentWindowOnDragCompletion(is_resized,
    248                                                             is_attached_panel);
    249   dock_layout_->FinishDragging(
    250       move_result == aura::client::MOVE_CANCELED ? DOCKED_ACTION_NONE : action,
    251       details().source == aura::client::WINDOW_MOVE_SOURCE_MOUSE ?
    252           DOCKED_ACTION_SOURCE_MOUSE : DOCKED_ACTION_SOURCE_TOUCH);
    253 
    254   // If we started the drag in one root window and moved into another root
    255   // but then canceled the drag we may need to inform the original layout
    256   // manager that the drag is finished.
    257   if (initial_dock_layout_ != dock_layout_)
    258     initial_dock_layout_->FinishDragging(
    259         DOCKED_ACTION_NONE,
    260         details().source == aura::client::WINDOW_MOVE_SOURCE_MOUSE ?
    261             DOCKED_ACTION_SOURCE_MOUSE : DOCKED_ACTION_SOURCE_TOUCH);
    262   is_docked_ = false;
    263 }
    264 
    265 DockedAction DockedWindowResizer::MaybeReparentWindowOnDragCompletion(
    266     bool is_resized, bool is_attached_panel) {
    267   aura::Window* window = GetTarget();
    268 
    269   // Check if the window needs to be docked or returned to workspace.
    270   DockedAction action = DOCKED_ACTION_NONE;
    271   aura::Window* dock_container = Shell::GetContainer(
    272       window->GetRootWindow(),
    273       kShellWindowId_DockedContainer);
    274   if ((is_resized || !is_attached_panel) &&
    275       is_docked_ != (window->parent() == dock_container)) {
    276     if (is_docked_) {
    277       wm::ReparentChildWithTransientChildren(window,
    278                                              window->parent(),
    279                                              dock_container);
    280       action = DOCKED_ACTION_DOCK;
    281     } else if (window->parent()->id() == kShellWindowId_DockedContainer) {
    282       // Reparent the window back to workspace.
    283       // We need to be careful to give ParentWindowWithContext a location in
    284       // the right root window (matching the logic in DragWindowResizer) based
    285       // on which root window a mouse pointer is in. We want to undock into the
    286       // right screen near the edge of a multiscreen setup (based on where the
    287       // mouse is).
    288       gfx::Rect near_last_location(last_location_, gfx::Size());
    289       // Reparenting will cause Relayout and possible dock shrinking.
    290       aura::Window* previous_parent = window->parent();
    291       aura::client::ParentWindowWithContext(window, window, near_last_location);
    292       if (window->parent() != previous_parent) {
    293         wm::ReparentTransientChildrenOfChild(window,
    294                                              previous_parent,
    295                                              window->parent());
    296       }
    297       action = was_docked_ ? DOCKED_ACTION_UNDOCK : DOCKED_ACTION_NONE;
    298     }
    299   } else {
    300     // Docked state was not changed but still need to record a UMA action.
    301     if (is_resized && is_docked_ && was_docked_)
    302       action = DOCKED_ACTION_RESIZE;
    303     else if (is_docked_ && was_docked_)
    304       action = DOCKED_ACTION_REORDER;
    305     else if (is_docked_ && !was_docked_)
    306       action = DOCKED_ACTION_DOCK;
    307     else
    308       action = DOCKED_ACTION_NONE;
    309   }
    310   // When a window is newly docked it is auto-sized by docked layout adjusting
    311   // to other windows. If it is just dragged (but not resized) while being
    312   // docked it is auto-sized unless it has been resized while being docked
    313   // before.
    314   if (is_docked_) {
    315     wm::GetWindowState(window)->set_bounds_changed_by_user(
    316         was_docked_ && (is_resized || was_bounds_changed_by_user_));
    317   }
    318   return action;
    319 }
    320 
    321 }  // namespace ash
    322