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/dock/docked_window_layout_manager.h"
16#include "ash/wm/window_state.h"
17#include "ash/wm/window_util.h"
18#include "ash/wm/workspace/magnetism_matcher.h"
19#include "ash/wm/workspace/workspace_window_resizer.h"
20#include "base/command_line.h"
21#include "base/memory/weak_ptr.h"
22#include "ui/aura/client/aura_constants.h"
23#include "ui/aura/client/window_tree_client.h"
24#include "ui/aura/env.h"
25#include "ui/aura/window.h"
26#include "ui/aura/window_delegate.h"
27#include "ui/aura/window_event_dispatcher.h"
28#include "ui/base/hit_test.h"
29#include "ui/base/ui_base_types.h"
30#include "ui/gfx/screen.h"
31#include "ui/views/widget/widget.h"
32#include "ui/wm/core/coordinate_conversion.h"
33
34namespace ash {
35namespace {
36
37DockedWindowLayoutManager* 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
52DockedWindowResizer::~DockedWindowResizer() {
53}
54
55// static
56DockedWindowResizer*
57DockedWindowResizer::Create(WindowResizer* next_window_resizer,
58                            wm::WindowState* window_state) {
59  return new DockedWindowResizer(next_window_resizer, window_state);
60}
61
62void 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
111void 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
117void 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
130DockedWindowResizer::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
152void 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
182void 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
211void 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
265DockedAction 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