Focus rings for checkboxes and radio buttons should not be shown in Sync setup overlay.
[chromium-blink-merge.git] / ash / drag_drop / drag_drop_controller.cc
blob5973343584e722cea6836ba357ed2d58ec909bca
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.
5 #include "ash/drag_drop/drag_drop_controller.h"
7 #include "ash/drag_drop/drag_drop_tracker.h"
8 #include "ash/drag_drop/drag_image_view.h"
9 #include "ash/shell.h"
10 #include "ash/wm/coordinate_conversion.h"
11 #include "base/bind.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/run_loop.h"
14 #include "ui/aura/client/capture_client.h"
15 #include "ui/aura/client/drag_drop_delegate.h"
16 #include "ui/aura/env.h"
17 #include "ui/aura/root_window.h"
18 #include "ui/aura/window.h"
19 #include "ui/aura/window_delegate.h"
20 #include "ui/base/animation/linear_animation.h"
21 #include "ui/base/dragdrop/drag_drop_types.h"
22 #include "ui/base/dragdrop/os_exchange_data.h"
23 #include "ui/base/events/event.h"
24 #include "ui/base/events/event_utils.h"
25 #include "ui/base/hit_test.h"
26 #include "ui/gfx/path.h"
27 #include "ui/gfx/point.h"
28 #include "ui/gfx/rect.h"
29 #include "ui/gfx/rect_conversions.h"
30 #include "ui/views/views_delegate.h"
31 #include "ui/views/widget/native_widget_aura.h"
33 namespace ash {
34 namespace internal {
36 using aura::RootWindow;
38 namespace {
39 // The duration of the drag cancel animation in millisecond.
40 const int kCancelAnimationDuration = 250;
41 const int kTouchCancelAnimationDuration = 20;
42 // The frame rate of the drag cancel animation in hertz.
43 const int kCancelAnimationFrameRate = 60;
45 // For touch initiated dragging, we scale and shift drag image by the following:
46 static const float kTouchDragImageScale = 1.2f;
47 static const int kTouchDragImageVerticalOffset = -25;
49 // Adjusts the drag image bounds such that the new bounds are scaled by |scale|
50 // and translated by the |drag_image_offset| and and additional
51 // |vertical_offset|.
52 gfx::Rect AdjustDragImageBoundsForScaleAndOffset(
53 const gfx::Rect& drag_image_bounds,
54 int vertical_offset,
55 float scale,
56 gfx::Vector2d* drag_image_offset) {
57 gfx::PointF final_origin = drag_image_bounds.origin();
58 gfx::SizeF final_size = drag_image_bounds.size();
59 final_size.Scale(scale);
60 drag_image_offset->set_x(drag_image_offset->x() * scale);
61 drag_image_offset->set_y(drag_image_offset->y() * scale);
62 float total_x_offset = drag_image_offset->x();
63 float total_y_offset = drag_image_offset->y() - vertical_offset;
64 final_origin.Offset(-total_x_offset, -total_y_offset);
65 return gfx::ToEnclosingRect(gfx::RectF(final_origin, final_size));
68 void DispatchGestureEndToWindow(aura::Window* window) {
69 if (window && window->delegate()) {
70 ui::GestureEvent gesture_end(
71 ui::ET_GESTURE_END,
75 ui::EventTimeForNow(),
76 ui::GestureEventDetails(ui::ET_GESTURE_END, 0, 0),
77 0);
78 window->delegate()->OnGestureEvent(&gesture_end);
81 } // namespace
83 class DragDropTrackerDelegate : public aura::WindowDelegate {
84 public:
85 explicit DragDropTrackerDelegate(DragDropController* controller)
86 : drag_drop_controller_(controller) {}
87 virtual ~DragDropTrackerDelegate() {}
89 // Overridden from WindowDelegate:
90 virtual gfx::Size GetMinimumSize() const OVERRIDE {
91 return gfx::Size();
94 virtual gfx::Size GetMaximumSize() const OVERRIDE {
95 return gfx::Size();
98 virtual void OnBoundsChanged(const gfx::Rect& old_bounds,
99 const gfx::Rect& new_bounds) OVERRIDE {}
100 virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE {
101 return gfx::kNullCursor;
103 virtual int GetNonClientComponent(const gfx::Point& point) const OVERRIDE {
104 return HTCAPTION;
106 virtual bool ShouldDescendIntoChildForEventHandling(
107 aura::Window* child,
108 const gfx::Point& location) OVERRIDE {
109 return true;
111 virtual bool CanFocus() OVERRIDE { return true; }
112 virtual void OnCaptureLost() OVERRIDE {
113 if (drag_drop_controller_->IsDragDropInProgress())
114 drag_drop_controller_->DragCancel();
116 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
118 virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE {}
119 virtual void OnWindowDestroying() OVERRIDE {}
120 virtual void OnWindowDestroyed() OVERRIDE {}
121 virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE {}
122 virtual bool HasHitTestMask() const OVERRIDE {
123 return true;
125 virtual void GetHitTestMask(gfx::Path* mask) const OVERRIDE {
126 DCHECK(mask->isEmpty());
128 virtual scoped_refptr<ui::Texture> CopyTexture() OVERRIDE {
129 return scoped_refptr<ui::Texture>();
132 private:
133 DragDropController* drag_drop_controller_;
135 DISALLOW_COPY_AND_ASSIGN(DragDropTrackerDelegate);
138 ////////////////////////////////////////////////////////////////////////////////
139 // DragDropController, public:
141 DragDropController::DragDropController()
142 : drag_data_(NULL),
143 drag_operation_(0),
144 drag_window_(NULL),
145 drag_source_window_(NULL),
146 should_block_during_drag_drop_(true),
147 drag_drop_window_delegate_(new DragDropTrackerDelegate(this)),
148 current_drag_event_source_(ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE),
149 weak_factory_(this) {
150 Shell::GetInstance()->PrependPreTargetHandler(this);
153 DragDropController::~DragDropController() {
154 Shell::GetInstance()->RemovePreTargetHandler(this);
155 Cleanup();
156 if (cancel_animation_)
157 cancel_animation_->End();
158 if (drag_image_)
159 drag_image_.reset();
162 int DragDropController::StartDragAndDrop(
163 const ui::OSExchangeData& data,
164 aura::RootWindow* root_window,
165 aura::Window* source_window,
166 const gfx::Point& root_location,
167 int operation,
168 ui::DragDropTypes::DragEventSource source) {
169 if (IsDragDropInProgress())
170 return 0;
172 const ui::OSExchangeData::Provider* provider = &data.provider();
173 // We do not support touch drag/drop without a drag image.
174 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH &&
175 provider->GetDragImage().size().IsEmpty())
176 return 0;
178 current_drag_event_source_ = source;
179 DragDropTracker* tracker =
180 new DragDropTracker(root_window, drag_drop_window_delegate_.get());
181 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
182 // We need to transfer the current gesture sequence and the GR's touch event
183 // queue to the |drag_drop_tracker_|'s capture window so that when it takes
184 // capture, it still gets a valid gesture state.
185 root_window->gesture_recognizer()->TransferEventsTo(source_window,
186 tracker->capture_window());
187 // We also send a gesture end to the source window so it can clear state.
188 // TODO(varunjain): Remove this whole block when gesture sequence
189 // transferring is properly done in the GR (http://crbug.com/160558)
190 DispatchGestureEndToWindow(source_window);
192 tracker->TakeCapture();
193 drag_drop_tracker_.reset(tracker);
194 drag_source_window_ = source_window;
195 if (drag_source_window_)
196 drag_source_window_->AddObserver(this);
197 pending_long_tap_.reset();
199 drag_data_ = &data;
200 drag_operation_ = operation;
202 float drag_image_scale = 1;
203 int drag_image_vertical_offset = 0;
204 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
205 drag_image_scale = kTouchDragImageScale;
206 drag_image_vertical_offset = kTouchDragImageVerticalOffset;
208 gfx::Point start_location = root_location;
209 ash::wm::ConvertPointToScreen(root_window, &start_location);
210 drag_image_final_bounds_for_cancel_animation_ = gfx::Rect(
211 start_location - provider->GetDragImageOffset(),
212 provider->GetDragImage().size());
213 drag_image_.reset(new DragImageView(source_window->GetRootWindow()));
214 drag_image_->SetImage(provider->GetDragImage());
215 drag_image_offset_ = provider->GetDragImageOffset();
216 gfx::Rect drag_image_bounds(start_location, drag_image_->GetPreferredSize());
217 drag_image_bounds = AdjustDragImageBoundsForScaleAndOffset(drag_image_bounds,
218 drag_image_vertical_offset, drag_image_scale, &drag_image_offset_);
219 drag_image_->SetBoundsInScreen(drag_image_bounds);
220 drag_image_->SetWidgetVisible(true);
222 drag_window_ = NULL;
224 // Ends cancel animation if it's in progress.
225 if (cancel_animation_)
226 cancel_animation_->End();
228 #if !defined(OS_MACOSX)
229 if (should_block_during_drag_drop_) {
230 base::RunLoop run_loop(aura::Env::GetInstance()->GetDispatcher());
231 quit_closure_ = run_loop.QuitClosure();
232 base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
233 base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
234 run_loop.Run();
236 #endif // !defined(OS_MACOSX)
238 if (!cancel_animation_.get() || !cancel_animation_->is_animating() ||
239 !pending_long_tap_.get()) {
240 // If drag cancel animation is running, this cleanup is done when the
241 // animation completes.
242 if (drag_source_window_)
243 drag_source_window_->RemoveObserver(this);
244 drag_source_window_ = NULL;
247 return drag_operation_;
250 void DragDropController::DragUpdate(aura::Window* target,
251 const ui::LocatedEvent& event) {
252 aura::client::DragDropDelegate* delegate = NULL;
253 if (target != drag_window_) {
254 if (drag_window_) {
255 if ((delegate = aura::client::GetDragDropDelegate(drag_window_)))
256 delegate->OnDragExited();
257 if (drag_window_ != drag_source_window_)
258 drag_window_->RemoveObserver(this);
260 drag_window_ = target;
261 // We are already an observer of |drag_source_window_| so no need to add.
262 if (drag_window_ != drag_source_window_)
263 drag_window_->AddObserver(this);
264 if ((delegate = aura::client::GetDragDropDelegate(drag_window_))) {
265 ui::DropTargetEvent e(*drag_data_,
266 event.location(),
267 event.root_location(),
268 drag_operation_);
269 e.set_flags(event.flags());
270 delegate->OnDragEntered(e);
272 } else {
273 if ((delegate = aura::client::GetDragDropDelegate(drag_window_))) {
274 ui::DropTargetEvent e(*drag_data_,
275 event.location(),
276 event.root_location(),
277 drag_operation_);
278 e.set_flags(event.flags());
279 int op = delegate->OnDragUpdated(e);
280 gfx::NativeCursor cursor = ui::kCursorNoDrop;
281 if (op & ui::DragDropTypes::DRAG_COPY)
282 cursor = ui::kCursorCopy;
283 else if (op & ui::DragDropTypes::DRAG_LINK)
284 cursor = ui::kCursorAlias;
285 else if (op & ui::DragDropTypes::DRAG_MOVE)
286 cursor = ui::kCursorGrabbing;
287 ash::Shell::GetInstance()->cursor_manager()->SetCursor(cursor);
291 DCHECK(drag_image_.get());
292 if (drag_image_->visible()) {
293 gfx::Point root_location_in_screen = event.root_location();
294 ash::wm::ConvertPointToScreen(target->GetRootWindow(),
295 &root_location_in_screen);
296 drag_image_->SetScreenPosition(
297 root_location_in_screen - drag_image_offset_);
301 void DragDropController::Drop(aura::Window* target,
302 const ui::LocatedEvent& event) {
303 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
304 aura::client::DragDropDelegate* delegate = NULL;
306 // We must guarantee that a target gets a OnDragEntered before Drop. WebKit
307 // depends on not getting a Drop without DragEnter. This behavior is
308 // consistent with drag/drop on other platforms.
309 if (target != drag_window_)
310 DragUpdate(target, event);
311 DCHECK(target == drag_window_);
313 if ((delegate = aura::client::GetDragDropDelegate(target))) {
314 ui::DropTargetEvent e(
315 *drag_data_, event.location(), event.root_location(), drag_operation_);
316 e.set_flags(event.flags());
317 drag_operation_ = delegate->OnPerformDrop(e);
318 if (drag_operation_ == 0)
319 StartCanceledAnimation(kCancelAnimationDuration);
320 else
321 drag_image_.reset();
322 } else {
323 drag_image_.reset();
326 Cleanup();
327 if (should_block_during_drag_drop_)
328 quit_closure_.Run();
331 void DragDropController::DragCancel() {
332 DoDragCancel(kCancelAnimationDuration);
335 bool DragDropController::IsDragDropInProgress() {
336 return !!drag_drop_tracker_.get();
339 void DragDropController::OnKeyEvent(ui::KeyEvent* event) {
340 if (IsDragDropInProgress() && event->key_code() == ui::VKEY_ESCAPE) {
341 DragCancel();
342 event->StopPropagation();
346 void DragDropController::OnMouseEvent(ui::MouseEvent* event) {
347 if (!IsDragDropInProgress())
348 return;
350 // If current drag session was not started by mouse, dont process this mouse
351 // event, but consume it so it does not interfere with current drag session.
352 if (current_drag_event_source_ !=
353 ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE) {
354 event->StopPropagation();
355 return;
358 aura::Window* translated_target = drag_drop_tracker_->GetTarget(*event);
359 if (!translated_target) {
360 DragCancel();
361 event->StopPropagation();
362 return;
364 scoped_ptr<ui::LocatedEvent> translated_event(
365 drag_drop_tracker_->ConvertEvent(translated_target, *event));
366 switch (translated_event->type()) {
367 case ui::ET_MOUSE_DRAGGED:
368 DragUpdate(translated_target, *translated_event.get());
369 break;
370 case ui::ET_MOUSE_RELEASED:
371 Drop(translated_target, *translated_event.get());
372 break;
373 default:
374 // We could also reach here because RootWindow may sometimes generate a
375 // bunch of fake mouse events
376 // (aura::RootWindow::PostMouseMoveEventAfterWindowChange).
377 break;
379 event->StopPropagation();
382 void DragDropController::OnTouchEvent(ui::TouchEvent* event) {
383 if (!IsDragDropInProgress())
384 return;
386 // If current drag session was not started by touch, dont process this touch
387 // event, but consume it so it does not interfere with current drag session.
388 if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
389 event->StopPropagation();
391 if (event->handled())
392 return;
394 if (event->type() == ui::ET_TOUCH_CANCELLED)
395 DragCancel();
398 void DragDropController::OnGestureEvent(ui::GestureEvent* event) {
399 if (!IsDragDropInProgress())
400 return;
402 // No one else should handle gesture events when in drag drop. Note that it is
403 // not enough to just set ER_HANDLED because the dispatcher only stops
404 // dispatching when the event has ER_CONSUMED. If we just set ER_HANDLED, the
405 // event will still be dispatched to other handlers and we depend on
406 // individual handlers' kindness to not touch events marked ER_HANDLED (not
407 // all handlers are so kind and may cause bugs like crbug.com/236493).
408 event->StopPropagation();
410 // If current drag session was not started by touch, dont process this event.
411 if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
412 return;
414 // Apply kTouchDragImageVerticalOffset to the location.
415 ui::GestureEvent touch_offset_event(*event,
416 static_cast<aura::Window*>(NULL),
417 static_cast<aura::Window*>(NULL));
418 gfx::Point touch_offset_location = touch_offset_event.location();
419 gfx::Point touch_offset_root_location = touch_offset_event.root_location();
420 touch_offset_location.Offset(0, kTouchDragImageVerticalOffset);
421 touch_offset_root_location.Offset(0, kTouchDragImageVerticalOffset);
422 touch_offset_event.set_location(touch_offset_location);
423 touch_offset_event.set_root_location(touch_offset_root_location);
425 aura::Window* translated_target =
426 drag_drop_tracker_->GetTarget(touch_offset_event);
427 if (!translated_target) {
428 DragCancel();
429 event->SetHandled();
430 return;
432 scoped_ptr<ui::LocatedEvent> translated_event(
433 drag_drop_tracker_->ConvertEvent(translated_target, touch_offset_event));
435 switch (event->type()) {
436 case ui::ET_GESTURE_SCROLL_UPDATE:
437 DragUpdate(translated_target, *translated_event.get());
438 break;
439 case ui::ET_GESTURE_SCROLL_END:
440 Drop(translated_target, *translated_event.get());
441 break;
442 case ui::ET_SCROLL_FLING_START:
443 case ui::ET_GESTURE_LONG_TAP:
444 // Ideally we would want to just forward this long tap event to the
445 // |drag_source_window_|. However, webkit does not accept events while a
446 // drag drop is still in progress. The drag drop ends only when the nested
447 // message loop ends. Due to this stupidity, we have to defer forwarding
448 // the long tap.
449 pending_long_tap_.reset(
450 new ui::GestureEvent(*event,
451 static_cast<aura::Window*>(drag_drop_tracker_->capture_window()),
452 static_cast<aura::Window*>(drag_source_window_)));
453 DoDragCancel(kTouchCancelAnimationDuration);
454 break;
455 default:
456 break;
458 event->SetHandled();
461 void DragDropController::OnWindowDestroyed(aura::Window* window) {
462 if (drag_window_ == window) {
463 drag_window_->RemoveObserver(this);
464 drag_window_ = NULL;
466 if (drag_source_window_ == window) {
467 drag_source_window_->RemoveObserver(this);
468 drag_source_window_ = NULL;
472 ////////////////////////////////////////////////////////////////////////////////
473 // DragDropController, protected:
475 ui::LinearAnimation* DragDropController::CreateCancelAnimation(
476 int duration,
477 int frame_rate,
478 ui::AnimationDelegate* delegate) {
479 return new ui::LinearAnimation(duration, frame_rate, delegate);
482 ////////////////////////////////////////////////////////////////////////////////
483 // DragDropController, private:
485 void DragDropController::AnimationEnded(const ui::Animation* animation) {
486 cancel_animation_.reset();
488 // By the time we finish animation, another drag/drop session may have
489 // started. We do not want to destroy the drag image in that case.
490 if (!IsDragDropInProgress())
491 drag_image_.reset();
492 if (pending_long_tap_) {
493 // If not in a nested message loop, we can forward the long tap right now.
494 if (!should_block_during_drag_drop_)
495 ForwardPendingLongTap();
496 else {
497 // See comment about this in OnGestureEvent().
498 base::MessageLoopForUI::current()->PostTask(
499 FROM_HERE,
500 base::Bind(&DragDropController::ForwardPendingLongTap,
501 weak_factory_.GetWeakPtr()));
506 void DragDropController::DoDragCancel(int drag_cancel_animation_duration_ms) {
507 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
509 // |drag_window_| can be NULL if we have just started the drag and have not
510 // received any DragUpdates, or, if the |drag_window_| gets destroyed during
511 // a drag/drop.
512 aura::client::DragDropDelegate* delegate = drag_window_?
513 aura::client::GetDragDropDelegate(drag_window_) : NULL;
514 if (delegate)
515 delegate->OnDragExited();
517 Cleanup();
518 drag_operation_ = 0;
519 StartCanceledAnimation(drag_cancel_animation_duration_ms);
520 if (should_block_during_drag_drop_)
521 quit_closure_.Run();
524 void DragDropController::AnimationProgressed(const ui::Animation* animation) {
525 gfx::Rect current_bounds = animation->CurrentValueBetween(
526 drag_image_initial_bounds_for_cancel_animation_,
527 drag_image_final_bounds_for_cancel_animation_);
528 drag_image_->SetBoundsInScreen(current_bounds);
531 void DragDropController::AnimationCanceled(const ui::Animation* animation) {
532 AnimationEnded(animation);
535 void DragDropController::StartCanceledAnimation(int animation_duration_ms) {
536 DCHECK(drag_image_.get());
537 drag_image_initial_bounds_for_cancel_animation_ =
538 drag_image_->GetBoundsInScreen();
539 cancel_animation_.reset(CreateCancelAnimation(animation_duration_ms,
540 kCancelAnimationFrameRate,
541 this));
542 cancel_animation_->Start();
545 void DragDropController::ForwardPendingLongTap() {
546 if (drag_source_window_ && drag_source_window_->delegate()) {
547 drag_source_window_->delegate()->OnGestureEvent(pending_long_tap_.get());
548 DispatchGestureEndToWindow(drag_source_window_);
550 pending_long_tap_.reset();
551 if (drag_source_window_)
552 drag_source_window_->RemoveObserver(this);
553 drag_source_window_ = NULL;
556 void DragDropController::Cleanup() {
557 if (drag_window_)
558 drag_window_->RemoveObserver(this);
559 drag_window_ = NULL;
560 drag_data_ = NULL;
561 // Cleanup can be called again while deleting DragDropTracker, so use Pass
562 // instead of reset to avoid double free.
563 drag_drop_tracker_.Pass();
566 } // namespace internal
567 } // namespace ash