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