Enable icu_use_data_file_flag on Android
[chromium-blink-merge.git] / ash / drag_drop / drag_drop_controller.cc
blob9ea8672aeaacdb81a0594dc1eddaae1d9a107424
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/window.h"
18 #include "ui/aura/window_delegate.h"
19 #include "ui/aura/window_event_dispatcher.h"
20 #include "ui/base/dragdrop/drag_drop_types.h"
21 #include "ui/base/dragdrop/os_exchange_data.h"
22 #include "ui/base/hit_test.h"
23 #include "ui/events/event.h"
24 #include "ui/events/event_utils.h"
25 #include "ui/gfx/animation/linear_animation.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 namespace {
37 // The duration of the drag cancel animation in millisecond.
38 const int kCancelAnimationDuration = 250;
39 const int kTouchCancelAnimationDuration = 20;
40 // The frame rate of the drag cancel animation in hertz.
41 const int kCancelAnimationFrameRate = 60;
43 // For touch initiated dragging, we scale and shift drag image by the following:
44 static const float kTouchDragImageScale = 1.2f;
45 static const int kTouchDragImageVerticalOffset = -25;
47 // Adjusts the drag image bounds such that the new bounds are scaled by |scale|
48 // and translated by the |drag_image_offset| and and additional
49 // |vertical_offset|.
50 gfx::Rect AdjustDragImageBoundsForScaleAndOffset(
51 const gfx::Rect& drag_image_bounds,
52 int vertical_offset,
53 float scale,
54 gfx::Vector2d* drag_image_offset) {
55 gfx::PointF final_origin = drag_image_bounds.origin();
56 gfx::SizeF final_size = drag_image_bounds.size();
57 final_size.Scale(scale);
58 drag_image_offset->set_x(drag_image_offset->x() * scale);
59 drag_image_offset->set_y(drag_image_offset->y() * scale);
60 float total_x_offset = drag_image_offset->x();
61 float total_y_offset = drag_image_offset->y() - vertical_offset;
62 final_origin.Offset(-total_x_offset, -total_y_offset);
63 return gfx::ToEnclosingRect(gfx::RectF(final_origin, final_size));
66 void DispatchGestureEndToWindow(aura::Window* window) {
67 if (window && window->delegate()) {
68 ui::GestureEvent gesture_end(
69 ui::ET_GESTURE_END,
73 ui::EventTimeForNow(),
74 ui::GestureEventDetails(ui::ET_GESTURE_END, 0, 0),
75 0);
76 window->delegate()->OnGestureEvent(&gesture_end);
79 } // namespace
81 class DragDropTrackerDelegate : public aura::WindowDelegate {
82 public:
83 explicit DragDropTrackerDelegate(DragDropController* controller)
84 : drag_drop_controller_(controller) {}
85 virtual ~DragDropTrackerDelegate() {}
87 // Overridden from WindowDelegate:
88 virtual gfx::Size GetMinimumSize() const OVERRIDE {
89 return gfx::Size();
92 virtual gfx::Size GetMaximumSize() const OVERRIDE {
93 return gfx::Size();
96 virtual void OnBoundsChanged(const gfx::Rect& old_bounds,
97 const gfx::Rect& new_bounds) OVERRIDE {}
98 virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE {
99 return gfx::kNullCursor;
101 virtual int GetNonClientComponent(const gfx::Point& point) const OVERRIDE {
102 return HTCAPTION;
104 virtual bool ShouldDescendIntoChildForEventHandling(
105 aura::Window* child,
106 const gfx::Point& location) OVERRIDE {
107 return true;
109 virtual bool CanFocus() OVERRIDE { return true; }
110 virtual void OnCaptureLost() OVERRIDE {
111 if (drag_drop_controller_->IsDragDropInProgress())
112 drag_drop_controller_->DragCancel();
114 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
116 virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE {}
117 virtual void OnWindowDestroying() OVERRIDE {}
118 virtual void OnWindowDestroyed() OVERRIDE {}
119 virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE {}
120 virtual bool HasHitTestMask() const OVERRIDE {
121 return true;
123 virtual void GetHitTestMask(gfx::Path* mask) const OVERRIDE {
124 DCHECK(mask->isEmpty());
126 virtual void DidRecreateLayer(ui::Layer* old_layer,
127 ui::Layer* new_layer) OVERRIDE {}
129 private:
130 DragDropController* drag_drop_controller_;
132 DISALLOW_COPY_AND_ASSIGN(DragDropTrackerDelegate);
135 ////////////////////////////////////////////////////////////////////////////////
136 // DragDropController, public:
138 DragDropController::DragDropController()
139 : drag_data_(NULL),
140 drag_operation_(0),
141 drag_window_(NULL),
142 drag_source_window_(NULL),
143 should_block_during_drag_drop_(true),
144 drag_drop_window_delegate_(new DragDropTrackerDelegate(this)),
145 current_drag_event_source_(ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE),
146 weak_factory_(this) {
147 Shell::GetInstance()->PrependPreTargetHandler(this);
150 DragDropController::~DragDropController() {
151 Shell::GetInstance()->RemovePreTargetHandler(this);
152 Cleanup();
153 if (cancel_animation_)
154 cancel_animation_->End();
155 if (drag_image_)
156 drag_image_.reset();
159 int DragDropController::StartDragAndDrop(
160 const ui::OSExchangeData& data,
161 aura::Window* root_window,
162 aura::Window* source_window,
163 const gfx::Point& root_location,
164 int operation,
165 ui::DragDropTypes::DragEventSource source) {
166 if (IsDragDropInProgress())
167 return 0;
169 const ui::OSExchangeData::Provider* provider = &data.provider();
170 // We do not support touch drag/drop without a drag image.
171 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH &&
172 provider->GetDragImage().size().IsEmpty())
173 return 0;
175 current_drag_event_source_ = source;
176 DragDropTracker* tracker =
177 new DragDropTracker(root_window, drag_drop_window_delegate_.get());
178 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
179 // We need to transfer the current gesture sequence and the GR's touch event
180 // queue to the |drag_drop_tracker_|'s capture window so that when it takes
181 // capture, it still gets a valid gesture state.
182 ui::GestureRecognizer::Get()->TransferEventsTo(source_window,
183 tracker->capture_window());
184 // We also send a gesture end to the source window so it can clear state.
185 // TODO(varunjain): Remove this whole block when gesture sequence
186 // transferring is properly done in the GR (http://crbug.com/160558)
187 DispatchGestureEndToWindow(source_window);
189 tracker->TakeCapture();
190 drag_drop_tracker_.reset(tracker);
191 drag_source_window_ = source_window;
192 if (drag_source_window_)
193 drag_source_window_->AddObserver(this);
194 pending_long_tap_.reset();
196 drag_data_ = &data;
197 drag_operation_ = operation;
199 float drag_image_scale = 1;
200 int drag_image_vertical_offset = 0;
201 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
202 drag_image_scale = kTouchDragImageScale;
203 drag_image_vertical_offset = kTouchDragImageVerticalOffset;
205 gfx::Point start_location = root_location;
206 ash::wm::ConvertPointToScreen(root_window, &start_location);
207 drag_image_final_bounds_for_cancel_animation_ = gfx::Rect(
208 start_location - provider->GetDragImageOffset(),
209 provider->GetDragImage().size());
210 drag_image_.reset(new DragImageView(source_window->GetRootWindow(), source));
211 drag_image_->SetImage(provider->GetDragImage());
212 drag_image_offset_ = provider->GetDragImageOffset();
213 gfx::Rect drag_image_bounds(start_location, drag_image_->GetPreferredSize());
214 drag_image_bounds = AdjustDragImageBoundsForScaleAndOffset(drag_image_bounds,
215 drag_image_vertical_offset, drag_image_scale, &drag_image_offset_);
216 drag_image_->SetBoundsInScreen(drag_image_bounds);
217 drag_image_->SetWidgetVisible(true);
218 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
219 drag_image_->SetTouchDragOperationHintPosition(gfx::Point(
220 drag_image_offset_.x(),
221 drag_image_offset_.y() + drag_image_vertical_offset));
224 drag_window_ = NULL;
226 // Ends cancel animation if it's in progress.
227 if (cancel_animation_)
228 cancel_animation_->End();
230 if (should_block_during_drag_drop_) {
231 base::RunLoop run_loop;
232 quit_closure_ = run_loop.QuitClosure();
233 base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
234 base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
235 run_loop.Run();
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 int op = ui::DragDropTypes::DRAG_NONE;
254 if (target != drag_window_) {
255 if (drag_window_) {
256 if ((delegate = aura::client::GetDragDropDelegate(drag_window_)))
257 delegate->OnDragExited();
258 if (drag_window_ != drag_source_window_)
259 drag_window_->RemoveObserver(this);
261 drag_window_ = target;
262 // We are already an observer of |drag_source_window_| so no need to add.
263 if (drag_window_ != drag_source_window_)
264 drag_window_->AddObserver(this);
265 if ((delegate = aura::client::GetDragDropDelegate(drag_window_))) {
266 ui::DropTargetEvent e(*drag_data_,
267 event.location(),
268 event.root_location(),
269 drag_operation_);
270 e.set_flags(event.flags());
271 delegate->OnDragEntered(e);
273 } else {
274 if ((delegate = aura::client::GetDragDropDelegate(drag_window_))) {
275 ui::DropTargetEvent e(*drag_data_,
276 event.location(),
277 event.root_location(),
278 drag_operation_);
279 e.set_flags(event.flags());
280 op = delegate->OnDragUpdated(e);
281 gfx::NativeCursor cursor = ui::kCursorNoDrop;
282 if (op & ui::DragDropTypes::DRAG_COPY)
283 cursor = ui::kCursorCopy;
284 else if (op & ui::DragDropTypes::DRAG_LINK)
285 cursor = ui::kCursorAlias;
286 else if (op & ui::DragDropTypes::DRAG_MOVE)
287 cursor = ui::kCursorGrabbing;
288 ash::Shell::GetInstance()->cursor_manager()->SetCursor(cursor);
292 DCHECK(drag_image_.get());
293 if (drag_image_->visible()) {
294 gfx::Point root_location_in_screen = event.root_location();
295 ash::wm::ConvertPointToScreen(target->GetRootWindow(),
296 &root_location_in_screen);
297 drag_image_->SetScreenPosition(
298 root_location_in_screen - drag_image_offset_);
299 drag_image_->SetTouchDragOperation(op);
303 void DragDropController::Drop(aura::Window* target,
304 const ui::LocatedEvent& event) {
305 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
306 aura::client::DragDropDelegate* delegate = NULL;
308 // We must guarantee that a target gets a OnDragEntered before Drop. WebKit
309 // depends on not getting a Drop without DragEnter. This behavior is
310 // consistent with drag/drop on other platforms.
311 if (target != drag_window_)
312 DragUpdate(target, event);
313 DCHECK(target == drag_window_);
315 if ((delegate = aura::client::GetDragDropDelegate(target))) {
316 ui::DropTargetEvent e(
317 *drag_data_, event.location(), event.root_location(), drag_operation_);
318 e.set_flags(event.flags());
319 drag_operation_ = delegate->OnPerformDrop(e);
320 if (drag_operation_ == 0)
321 StartCanceledAnimation(kCancelAnimationDuration);
322 else
323 drag_image_.reset();
324 } else {
325 drag_image_.reset();
328 Cleanup();
329 if (should_block_during_drag_drop_)
330 quit_closure_.Run();
333 void DragDropController::DragCancel() {
334 DoDragCancel(kCancelAnimationDuration);
337 bool DragDropController::IsDragDropInProgress() {
338 return !!drag_drop_tracker_.get();
341 void DragDropController::OnKeyEvent(ui::KeyEvent* event) {
342 if (IsDragDropInProgress() && event->key_code() == ui::VKEY_ESCAPE) {
343 DragCancel();
344 event->StopPropagation();
348 void DragDropController::OnMouseEvent(ui::MouseEvent* event) {
349 if (!IsDragDropInProgress())
350 return;
352 // If current drag session was not started by mouse, dont process this mouse
353 // event, but consume it so it does not interfere with current drag session.
354 if (current_drag_event_source_ !=
355 ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE) {
356 event->StopPropagation();
357 return;
360 aura::Window* translated_target = drag_drop_tracker_->GetTarget(*event);
361 if (!translated_target) {
362 DragCancel();
363 event->StopPropagation();
364 return;
366 scoped_ptr<ui::LocatedEvent> translated_event(
367 drag_drop_tracker_->ConvertEvent(translated_target, *event));
368 switch (translated_event->type()) {
369 case ui::ET_MOUSE_DRAGGED:
370 DragUpdate(translated_target, *translated_event.get());
371 break;
372 case ui::ET_MOUSE_RELEASED:
373 Drop(translated_target, *translated_event.get());
374 break;
375 default:
376 // We could also reach here because RootWindow may sometimes generate a
377 // bunch of fake mouse events
378 // (aura::RootWindow::PostMouseMoveEventAfterWindowChange).
379 break;
381 event->StopPropagation();
384 void DragDropController::OnTouchEvent(ui::TouchEvent* event) {
385 if (!IsDragDropInProgress())
386 return;
388 // If current drag session was not started by touch, dont process this touch
389 // event, but consume it so it does not interfere with current drag session.
390 if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
391 event->StopPropagation();
393 if (event->handled())
394 return;
396 if (event->type() == ui::ET_TOUCH_CANCELLED)
397 DragCancel();
400 void DragDropController::OnGestureEvent(ui::GestureEvent* event) {
401 if (!IsDragDropInProgress())
402 return;
404 // No one else should handle gesture events when in drag drop. Note that it is
405 // not enough to just set ER_HANDLED because the dispatcher only stops
406 // dispatching when the event has ER_CONSUMED. If we just set ER_HANDLED, the
407 // event will still be dispatched to other handlers and we depend on
408 // individual handlers' kindness to not touch events marked ER_HANDLED (not
409 // all handlers are so kind and may cause bugs like crbug.com/236493).
410 event->StopPropagation();
412 // If current drag session was not started by touch, dont process this event.
413 if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
414 return;
416 // Apply kTouchDragImageVerticalOffset to the location.
417 ui::GestureEvent touch_offset_event(*event,
418 static_cast<aura::Window*>(NULL),
419 static_cast<aura::Window*>(NULL));
420 gfx::Point touch_offset_location = touch_offset_event.location();
421 gfx::Point touch_offset_root_location = touch_offset_event.root_location();
422 touch_offset_location.Offset(0, kTouchDragImageVerticalOffset);
423 touch_offset_root_location.Offset(0, kTouchDragImageVerticalOffset);
424 touch_offset_event.set_location(touch_offset_location);
425 touch_offset_event.set_root_location(touch_offset_root_location);
427 aura::Window* translated_target =
428 drag_drop_tracker_->GetTarget(touch_offset_event);
429 if (!translated_target) {
430 DragCancel();
431 event->SetHandled();
432 return;
434 scoped_ptr<ui::LocatedEvent> translated_event(
435 drag_drop_tracker_->ConvertEvent(translated_target, touch_offset_event));
437 switch (event->type()) {
438 case ui::ET_GESTURE_SCROLL_UPDATE:
439 DragUpdate(translated_target, *translated_event.get());
440 break;
441 case ui::ET_GESTURE_SCROLL_END:
442 Drop(translated_target, *translated_event.get());
443 break;
444 case ui::ET_SCROLL_FLING_START:
445 case ui::ET_GESTURE_LONG_TAP:
446 // Ideally we would want to just forward this long tap event to the
447 // |drag_source_window_|. However, webkit does not accept events while a
448 // drag drop is still in progress. The drag drop ends only when the nested
449 // message loop ends. Due to this stupidity, we have to defer forwarding
450 // the long tap.
451 pending_long_tap_.reset(
452 new ui::GestureEvent(*event,
453 static_cast<aura::Window*>(drag_drop_tracker_->capture_window()),
454 static_cast<aura::Window*>(drag_source_window_)));
455 DoDragCancel(kTouchCancelAnimationDuration);
456 break;
457 default:
458 break;
460 event->SetHandled();
463 void DragDropController::OnWindowDestroyed(aura::Window* window) {
464 if (drag_window_ == window)
465 drag_window_ = NULL;
466 if (drag_source_window_ == window)
467 drag_source_window_ = NULL;
470 ////////////////////////////////////////////////////////////////////////////////
471 // DragDropController, protected:
473 gfx::LinearAnimation* DragDropController::CreateCancelAnimation(
474 int duration,
475 int frame_rate,
476 gfx::AnimationDelegate* delegate) {
477 return new gfx::LinearAnimation(duration, frame_rate, delegate);
480 ////////////////////////////////////////////////////////////////////////////////
481 // DragDropController, private:
483 void DragDropController::AnimationEnded(const gfx::Animation* animation) {
484 cancel_animation_.reset();
486 // By the time we finish animation, another drag/drop session may have
487 // started. We do not want to destroy the drag image in that case.
488 if (!IsDragDropInProgress())
489 drag_image_.reset();
490 if (pending_long_tap_) {
491 // If not in a nested message loop, we can forward the long tap right now.
492 if (!should_block_during_drag_drop_)
493 ForwardPendingLongTap();
494 else {
495 // See comment about this in OnGestureEvent().
496 base::MessageLoopForUI::current()->PostTask(
497 FROM_HERE,
498 base::Bind(&DragDropController::ForwardPendingLongTap,
499 weak_factory_.GetWeakPtr()));
504 void DragDropController::DoDragCancel(int drag_cancel_animation_duration_ms) {
505 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
507 // |drag_window_| can be NULL if we have just started the drag and have not
508 // received any DragUpdates, or, if the |drag_window_| gets destroyed during
509 // a drag/drop.
510 aura::client::DragDropDelegate* delegate = drag_window_?
511 aura::client::GetDragDropDelegate(drag_window_) : NULL;
512 if (delegate)
513 delegate->OnDragExited();
515 Cleanup();
516 drag_operation_ = 0;
517 StartCanceledAnimation(drag_cancel_animation_duration_ms);
518 if (should_block_during_drag_drop_)
519 quit_closure_.Run();
522 void DragDropController::AnimationProgressed(const gfx::Animation* animation) {
523 gfx::Rect current_bounds = animation->CurrentValueBetween(
524 drag_image_initial_bounds_for_cancel_animation_,
525 drag_image_final_bounds_for_cancel_animation_);
526 drag_image_->SetBoundsInScreen(current_bounds);
529 void DragDropController::AnimationCanceled(const gfx::Animation* animation) {
530 AnimationEnded(animation);
533 void DragDropController::StartCanceledAnimation(int animation_duration_ms) {
534 DCHECK(drag_image_.get());
535 drag_image_->SetTouchDragOperationHintOff();
536 drag_image_initial_bounds_for_cancel_animation_ =
537 drag_image_->GetBoundsInScreen();
538 cancel_animation_.reset(CreateCancelAnimation(animation_duration_ms,
539 kCancelAnimationFrameRate,
540 this));
541 cancel_animation_->Start();
544 void DragDropController::ForwardPendingLongTap() {
545 if (drag_source_window_ && drag_source_window_->delegate()) {
546 drag_source_window_->delegate()->OnGestureEvent(pending_long_tap_.get());
547 DispatchGestureEndToWindow(drag_source_window_);
549 pending_long_tap_.reset();
550 if (drag_source_window_)
551 drag_source_window_->RemoveObserver(this);
552 drag_source_window_ = NULL;
555 void DragDropController::Cleanup() {
556 if (drag_window_)
557 drag_window_->RemoveObserver(this);
558 drag_window_ = NULL;
559 drag_data_ = NULL;
560 // Cleanup can be called again while deleting DragDropTracker, so use Pass
561 // instead of reset to avoid double free.
562 drag_drop_tracker_.Pass();
565 } // namespace internal
566 } // namespace ash