Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / ash / wm / workspace / multi_window_resize_controller.cc
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/workspace/multi_window_resize_controller.h"
6
7 #include "ash/screen_util.h"
8 #include "ash/shell.h"
9 #include "ash/shell_window_ids.h"
10 #include "ash/wm/window_animations.h"
11 #include "ash/wm/workspace/workspace_event_handler.h"
12 #include "ash/wm/workspace/workspace_window_resizer.h"
13 #include "grit/ash_resources.h"
14 #include "ui/aura/client/screen_position_client.h"
15 #include "ui/aura/window.h"
16 #include "ui/aura/window_delegate.h"
17 #include "ui/aura/window_event_dispatcher.h"
18 #include "ui/base/hit_test.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/gfx/canvas.h"
21 #include "ui/gfx/image/image.h"
22 #include "ui/gfx/screen.h"
23 #include "ui/views/view.h"
24 #include "ui/views/widget/widget.h"
25 #include "ui/views/widget/widget_delegate.h"
26 #include "ui/wm/core/compound_event_filter.h"
27 #include "ui/wm/core/coordinate_conversion.h"
28
29 using aura::Window;
30
31 namespace ash {
32 namespace {
33
34 // Delay before showing.
35 const int kShowDelayMS = 400;
36
37 // Delay before hiding.
38 const int kHideDelayMS = 500;
39
40 // Padding from the bottom/right edge the resize widget is shown at.
41 const int kResizeWidgetPadding = 15;
42
43 bool ContainsX(Window* window, int x) {
44   return window->bounds().x() <= x && window->bounds().right() >= x;
45 }
46
47 bool ContainsY(Window* window, int y) {
48   return window->bounds().y() <= y && window->bounds().bottom() >= y;
49 }
50
51 bool Intersects(int x1, int max_1, int x2, int max_2) {
52   return x2 <= max_1 && max_2 > x1;
53 }
54
55 }  // namespace
56
57 // View contained in the widget. Passes along mouse events to the
58 // MultiWindowResizeController so that it can start/stop the resize loop.
59 class MultiWindowResizeController::ResizeView : public views::View {
60  public:
61   explicit ResizeView(MultiWindowResizeController* controller,
62                       Direction direction)
63       : controller_(controller),
64         direction_(direction),
65         image_(NULL) {
66     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
67     int image_id =
68         direction == TOP_BOTTOM ? IDR_AURA_MULTI_WINDOW_RESIZE_H :
69                                   IDR_AURA_MULTI_WINDOW_RESIZE_V;
70     image_ = rb.GetImageNamed(image_id).ToImageSkia();
71   }
72
73   // views::View overrides:
74   gfx::Size GetPreferredSize() const override {
75     return gfx::Size(image_->width(), image_->height());
76   }
77   void OnPaint(gfx::Canvas* canvas) override {
78     canvas->DrawImageInt(*image_, 0, 0);
79   }
80   bool OnMousePressed(const ui::MouseEvent& event) override {
81     gfx::Point location(event.location());
82     views::View::ConvertPointToScreen(this, &location);
83     controller_->StartResize(location);
84     return true;
85   }
86   bool OnMouseDragged(const ui::MouseEvent& event) override {
87     gfx::Point location(event.location());
88     views::View::ConvertPointToScreen(this, &location);
89     controller_->Resize(location, event.flags());
90     return true;
91   }
92   void OnMouseReleased(const ui::MouseEvent& event) override {
93     controller_->CompleteResize();
94   }
95   void OnMouseCaptureLost() override { controller_->CancelResize(); }
96   gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override {
97     int component = (direction_ == LEFT_RIGHT) ? HTRIGHT : HTBOTTOM;
98     return ::wm::CompoundEventFilter::CursorForWindowComponent(
99         component);
100   }
101
102  private:
103   MultiWindowResizeController* controller_;
104   const Direction direction_;
105   const gfx::ImageSkia* image_;
106
107   DISALLOW_COPY_AND_ASSIGN(ResizeView);
108 };
109
110 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards
111 // Contains() to MultiWindowResizeController.
112 class MultiWindowResizeController::ResizeMouseWatcherHost :
113    public views::MouseWatcherHost {
114  public:
115   ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {}
116
117   // MouseWatcherHost overrides:
118   bool Contains(const gfx::Point& point_in_screen,
119                 MouseEventType type) override {
120     return host_->IsOverWindows(point_in_screen);
121   }
122
123  private:
124   MultiWindowResizeController* host_;
125
126   DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost);
127 };
128
129 MultiWindowResizeController::ResizeWindows::ResizeWindows()
130     : window1(NULL),
131       window2(NULL),
132       direction(TOP_BOTTOM){
133 }
134
135 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
136 }
137
138 bool MultiWindowResizeController::ResizeWindows::Equals(
139     const ResizeWindows& other) const {
140   return window1 == other.window1 &&
141       window2 == other.window2 &&
142       direction == other.direction;
143 }
144
145 MultiWindowResizeController::MultiWindowResizeController() {
146 }
147
148 MultiWindowResizeController::~MultiWindowResizeController() {
149   window_resizer_.reset();
150   Hide();
151 }
152
153 void MultiWindowResizeController::Show(Window* window,
154                                        int component,
155                                        const gfx::Point& point_in_window) {
156   // When the resize widget is showing we ignore Show() requests. Instead we
157   // only care about mouse movements from MouseWatcher. This is necessary as
158   // WorkspaceEventHandler only sees mouse movements over the windows, not all
159   // windows or over the desktop.
160   if (resize_widget_)
161     return;
162
163   ResizeWindows windows(DetermineWindows(window, component, point_in_window));
164   if (IsShowing()) {
165     if (windows_.Equals(windows))
166       return;  // Over the same windows.
167     DelayedHide();
168   }
169
170   if (!windows.is_valid())
171     return;
172   Hide();
173   windows_ = windows;
174   windows_.window1->AddObserver(this);
175   windows_.window2->AddObserver(this);
176   show_location_in_parent_ = point_in_window;
177   Window::ConvertPointToTarget(
178       window, window->parent(), &show_location_in_parent_);
179   if (show_timer_.IsRunning())
180     return;
181   show_timer_.Start(
182       FROM_HERE, base::TimeDelta::FromMilliseconds(kShowDelayMS),
183       this, &MultiWindowResizeController::ShowIfValidMouseLocation);
184 }
185
186 void MultiWindowResizeController::Hide() {
187   hide_timer_.Stop();
188   if (window_resizer_)
189     return;  // Ignore hides while actively resizing.
190
191   if (windows_.window1) {
192     windows_.window1->RemoveObserver(this);
193     windows_.window1 = NULL;
194   }
195   if (windows_.window2) {
196     windows_.window2->RemoveObserver(this);
197     windows_.window2 = NULL;
198   }
199
200   show_timer_.Stop();
201
202   if (!resize_widget_)
203     return;
204
205   for (size_t i = 0; i < windows_.other_windows.size(); ++i)
206     windows_.other_windows[i]->RemoveObserver(this);
207   mouse_watcher_.reset();
208   resize_widget_.reset();
209   windows_ = ResizeWindows();
210 }
211
212 void MultiWindowResizeController::MouseMovedOutOfHost() {
213   Hide();
214 }
215
216 void MultiWindowResizeController::OnWindowDestroying(
217     aura::Window* window) {
218   // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
219   window_resizer_.reset();
220   Hide();
221 }
222
223 MultiWindowResizeController::ResizeWindows
224 MultiWindowResizeController::DetermineWindowsFromScreenPoint(
225     aura::Window* window) const {
226   gfx::Point mouse_location(
227       gfx::Screen::GetScreenFor(window)->GetCursorScreenPoint());
228   ::wm::ConvertPointFromScreen(window, &mouse_location);
229   const int component =
230       window->delegate()->GetNonClientComponent(mouse_location);
231   return DetermineWindows(window, component, mouse_location);
232 }
233
234 MultiWindowResizeController::ResizeWindows
235 MultiWindowResizeController::DetermineWindows(
236     Window* window,
237     int window_component,
238     const gfx::Point& point) const {
239   ResizeWindows result;
240   gfx::Point point_in_parent(point);
241   Window::ConvertPointToTarget(window, window->parent(), &point_in_parent);
242   switch (window_component) {
243     case HTRIGHT:
244       result.direction = LEFT_RIGHT;
245       result.window1 = window;
246       result.window2 = FindWindowByEdge(
247           window, HTLEFT, window->bounds().right(), point_in_parent.y());
248       break;
249     case HTLEFT:
250       result.direction = LEFT_RIGHT;
251       result.window1 = FindWindowByEdge(
252           window, HTRIGHT, window->bounds().x(), point_in_parent.y());
253       result.window2 = window;
254       break;
255     case HTTOP:
256       result.direction = TOP_BOTTOM;
257       result.window1 = FindWindowByEdge(
258           window, HTBOTTOM, point_in_parent.x(), window->bounds().y());
259       result.window2 = window;
260       break;
261     case HTBOTTOM:
262       result.direction = TOP_BOTTOM;
263       result.window1 = window;
264       result.window2 = FindWindowByEdge(
265           window, HTTOP, point_in_parent.x(), window->bounds().bottom());
266       break;
267     default:
268       break;
269   }
270   return result;
271 }
272
273 Window* MultiWindowResizeController::FindWindowByEdge(
274     Window* window_to_ignore,
275     int edge_want,
276     int x,
277     int y) const {
278   Window* parent = window_to_ignore->parent();
279   const Window::Windows& windows(parent->children());
280   for (Window::Windows::const_reverse_iterator i = windows.rbegin();
281        i != windows.rend(); ++i) {
282     Window* window = *i;
283     if (window == window_to_ignore || !window->IsVisible())
284       continue;
285     switch (edge_want) {
286       case HTLEFT:
287         if (ContainsY(window, y) && window->bounds().x() == x)
288           return window;
289         break;
290       case HTRIGHT:
291         if (ContainsY(window, y) && window->bounds().right() == x)
292           return window;
293         break;
294       case HTTOP:
295         if (ContainsX(window, x) && window->bounds().y() == y)
296           return window;
297         break;
298       case HTBOTTOM:
299         if (ContainsX(window, x) && window->bounds().bottom() == y)
300           return window;
301         break;
302       default:
303         NOTREACHED();
304     }
305     // Window doesn't contain the edge, but if window contains |point|
306     // it's obscuring any other window that could be at the location.
307     if (window->bounds().Contains(x, y))
308       return NULL;
309   }
310   return NULL;
311 }
312
313 aura::Window* MultiWindowResizeController::FindWindowTouching(
314     aura::Window* window,
315     Direction direction) const {
316   int right = window->bounds().right();
317   int bottom = window->bounds().bottom();
318   Window* parent = window->parent();
319   const Window::Windows& windows(parent->children());
320   for (Window::Windows::const_reverse_iterator i = windows.rbegin();
321        i != windows.rend(); ++i) {
322     Window* other = *i;
323     if (other == window || !other->IsVisible())
324       continue;
325     switch (direction) {
326       case TOP_BOTTOM:
327         if (other->bounds().y() == bottom &&
328             Intersects(other->bounds().x(), other->bounds().right(),
329                        window->bounds().x(), window->bounds().right())) {
330           return other;
331         }
332         break;
333       case LEFT_RIGHT:
334         if (other->bounds().x() == right &&
335             Intersects(other->bounds().y(), other->bounds().bottom(),
336                        window->bounds().y(), window->bounds().bottom())) {
337           return other;
338         }
339         break;
340       default:
341         NOTREACHED();
342     }
343   }
344   return NULL;
345 }
346
347 void MultiWindowResizeController::FindWindowsTouching(
348     aura::Window* start,
349     Direction direction,
350     std::vector<aura::Window*>* others) const {
351   while (start) {
352     start = FindWindowTouching(start, direction);
353     if (start)
354       others->push_back(start);
355   }
356 }
357
358 void MultiWindowResizeController::DelayedHide() {
359   if (hide_timer_.IsRunning())
360     return;
361
362   hide_timer_.Start(FROM_HERE,
363                     base::TimeDelta::FromMilliseconds(kHideDelayMS),
364                     this, &MultiWindowResizeController::Hide);
365 }
366
367 void MultiWindowResizeController::ShowIfValidMouseLocation() {
368   if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) ||
369       DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) {
370     ShowNow();
371   } else {
372     Hide();
373   }
374 }
375
376 void MultiWindowResizeController::ShowNow() {
377   DCHECK(!resize_widget_.get());
378   DCHECK(windows_.is_valid());
379   show_timer_.Stop();
380   resize_widget_.reset(new views::Widget);
381   views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
382   params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
383   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
384   params.parent = Shell::GetContainer(Shell::GetTargetRootWindow(),
385                                       kShellWindowId_AlwaysOnTopContainer);
386   ResizeView* view = new ResizeView(this, windows_.direction);
387   resize_widget_->set_focus_on_creation(false);
388   resize_widget_->Init(params);
389   ::wm::SetWindowVisibilityAnimationType(
390       resize_widget_->GetNativeWindow(),
391       ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
392   resize_widget_->GetNativeWindow()->SetName("MultiWindowResizeController");
393   resize_widget_->SetContentsView(view);
394   show_bounds_in_screen_ = ScreenUtil::ConvertRectToScreen(
395       windows_.window1->parent(),
396       CalculateResizeWidgetBounds(show_location_in_parent_));
397   resize_widget_->SetBounds(show_bounds_in_screen_);
398   resize_widget_->Show();
399   mouse_watcher_.reset(new views::MouseWatcher(
400                            new ResizeMouseWatcherHost(this),
401                            this));
402   mouse_watcher_->set_notify_on_exit_time(
403       base::TimeDelta::FromMilliseconds(kHideDelayMS));
404   mouse_watcher_->Start();
405 }
406
407 bool MultiWindowResizeController::IsShowing() const {
408   return resize_widget_.get() || show_timer_.IsRunning();
409 }
410
411 void MultiWindowResizeController::StartResize(
412     const gfx::Point& location_in_screen) {
413   DCHECK(!window_resizer_.get());
414   DCHECK(windows_.is_valid());
415   hide_timer_.Stop();
416   gfx::Point location_in_parent(location_in_screen);
417   aura::client::GetScreenPositionClient(windows_.window2->GetRootWindow())->
418       ConvertPointFromScreen(windows_.window2->parent(), &location_in_parent);
419   std::vector<aura::Window*> windows;
420   windows.push_back(windows_.window2);
421   DCHECK(windows_.other_windows.empty());
422   FindWindowsTouching(windows_.window2, windows_.direction,
423                       &windows_.other_windows);
424   for (size_t i = 0; i < windows_.other_windows.size(); ++i) {
425     windows_.other_windows[i]->AddObserver(this);
426     windows.push_back(windows_.other_windows[i]);
427   }
428   int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM;
429   wm::WindowState* window_state = wm::GetWindowState(windows_.window1);
430   window_state->CreateDragDetails(windows_.window1,
431                                   location_in_parent,
432                                   component,
433                                   aura::client::WINDOW_MOVE_SOURCE_MOUSE);
434   window_resizer_.reset(WorkspaceWindowResizer::Create(window_state, windows));
435 }
436
437 void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen,
438                                          int event_flags) {
439   gfx::Point location_in_parent(location_in_screen);
440   aura::client::GetScreenPositionClient(windows_.window1->GetRootWindow())->
441       ConvertPointFromScreen(windows_.window1->parent(), &location_in_parent);
442   window_resizer_->Drag(location_in_parent, event_flags);
443   gfx::Rect bounds = ScreenUtil::ConvertRectToScreen(
444       windows_.window1->parent(),
445       CalculateResizeWidgetBounds(location_in_parent));
446
447   if (windows_.direction == LEFT_RIGHT)
448     bounds.set_y(show_bounds_in_screen_.y());
449   else
450     bounds.set_x(show_bounds_in_screen_.x());
451   resize_widget_->SetBounds(bounds);
452 }
453
454 void MultiWindowResizeController::CompleteResize() {
455   window_resizer_->CompleteDrag();
456   wm::GetWindowState(window_resizer_->GetTarget())->DeleteDragDetails();
457   window_resizer_.reset();
458
459   // Mouse may still be over resizer, if not hide.
460   gfx::Point screen_loc = Shell::GetScreen()->GetCursorScreenPoint();
461   if (!resize_widget_->GetWindowBoundsInScreen().Contains(screen_loc)) {
462     Hide();
463   } else {
464     // If the mouse is over the resizer we need to remove observers on any of
465     // the |other_windows|. If we start another resize we'll recalculate the
466     // |other_windows| and invoke AddObserver() as necessary.
467     for (size_t i = 0; i < windows_.other_windows.size(); ++i)
468       windows_.other_windows[i]->RemoveObserver(this);
469     windows_.other_windows.clear();
470   }
471 }
472
473 void MultiWindowResizeController::CancelResize() {
474   if (!window_resizer_)
475     return;  // Happens if window was destroyed and we nuked the WindowResizer.
476   window_resizer_->RevertDrag();
477   wm::GetWindowState(window_resizer_->GetTarget())->DeleteDragDetails();
478   window_resizer_.reset();
479   Hide();
480 }
481
482 gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds(
483     const gfx::Point& location_in_parent) const {
484   gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize();
485   int x = 0, y = 0;
486   if (windows_.direction == LEFT_RIGHT) {
487     x = windows_.window1->bounds().right() - pref.width() / 2;
488     y = location_in_parent.y() + kResizeWidgetPadding;
489     if (y + pref.height() / 2 > windows_.window1->bounds().bottom() &&
490         y + pref.height() / 2 > windows_.window2->bounds().bottom()) {
491       y = location_in_parent.y() - kResizeWidgetPadding - pref.height();
492     }
493   } else {
494     x = location_in_parent.x() + kResizeWidgetPadding;
495     if (x + pref.height() / 2 > windows_.window1->bounds().right() &&
496         x + pref.height() / 2 > windows_.window2->bounds().right()) {
497       x = location_in_parent.x() - kResizeWidgetPadding - pref.width();
498     }
499     y = windows_.window1->bounds().bottom() - pref.height() / 2;
500   }
501   return gfx::Rect(x, y, pref.width(), pref.height());
502 }
503
504 bool MultiWindowResizeController::IsOverWindows(
505     const gfx::Point& location_in_screen) const {
506   if (window_resizer_)
507     return true;  // Ignore hides while actively resizing.
508
509   if (resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen))
510     return true;
511
512   int hit1, hit2;
513   if (windows_.direction == TOP_BOTTOM) {
514     hit1 = HTBOTTOM;
515     hit2 = HTTOP;
516   } else {
517     hit1 = HTRIGHT;
518     hit2 = HTLEFT;
519   }
520
521   return IsOverWindow(windows_.window1, location_in_screen, hit1) ||
522       IsOverWindow(windows_.window2, location_in_screen, hit2);
523 }
524
525 bool MultiWindowResizeController::IsOverWindow(
526     aura::Window* window,
527     const gfx::Point& location_in_screen,
528     int component) const {
529   if (!window->delegate())
530     return false;
531
532   gfx::Point window_loc(location_in_screen);
533   aura::Window::ConvertPointToTarget(
534       window->GetRootWindow(), window, &window_loc);
535   return window->ContainsPoint(window_loc) &&
536       window->delegate()->GetNonClientComponent(window_loc) == component;
537 }
538
539 }  // namespace ash