75c635969f5dfa9b8a41c8f491762c5d402b6350
[platform/framework/web/crosswalk.git] / src / ui / app_list / views / apps_grid_view.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 "ui/app_list/views/apps_grid_view.h"
6
7 #include <algorithm>
8 #include <set>
9 #include <string>
10
11 #include "base/guid.h"
12 #include "base/message_loop/message_loop.h"
13 #include "content/public/browser/web_contents.h"
14 #include "ui/app_list/app_list_constants.h"
15 #include "ui/app_list/app_list_folder_item.h"
16 #include "ui/app_list/app_list_item.h"
17 #include "ui/app_list/app_list_switches.h"
18 #include "ui/app_list/pagination_model.h"
19 #include "ui/app_list/views/app_list_drag_and_drop_host.h"
20 #include "ui/app_list/views/app_list_folder_view.h"
21 #include "ui/app_list/views/app_list_item_view.h"
22 #include "ui/app_list/views/apps_grid_view_delegate.h"
23 #include "ui/app_list/views/page_switcher.h"
24 #include "ui/app_list/views/pulsing_block_view.h"
25 #include "ui/app_list/views/top_icon_animation_view.h"
26 #include "ui/compositor/scoped_layer_animation_settings.h"
27 #include "ui/events/event.h"
28 #include "ui/gfx/animation/animation.h"
29 #include "ui/views/border.h"
30 #include "ui/views/controls/webview/webview.h"
31 #include "ui/views/view_model_utils.h"
32 #include "ui/views/widget/widget.h"
33
34 #if defined(USE_AURA)
35 #include "ui/aura/window.h"
36 #include "ui/aura/window_event_dispatcher.h"
37 #if defined(OS_WIN)
38 #include "ui/views/win/hwnd_util.h"
39 #endif  // defined(OS_WIN)
40 #endif  // defined(USE_AURA)
41
42 #if defined(OS_WIN)
43 #include "base/command_line.h"
44 #include "base/files/file_path.h"
45 #include "base/win/shortcut.h"
46 #include "ui/base/dragdrop/drag_utils.h"
47 #include "ui/base/dragdrop/drop_target_win.h"
48 #include "ui/base/dragdrop/os_exchange_data.h"
49 #include "ui/base/dragdrop/os_exchange_data_provider_win.h"
50 #endif
51
52 namespace app_list {
53
54 namespace {
55
56 // Distance a drag needs to be from the app grid to be considered 'outside', at
57 // which point we rearrange the apps to their pre-drag configuration, as a drop
58 // then would be canceled. We have a buffer to make it easier to drag apps to
59 // other pages.
60 const int kDragBufferPx = 20;
61
62 // Padding space in pixels for fixed layout.
63 const int kLeftRightPadding = 20;
64 const int kTopPadding = 1;
65
66 // Padding space in pixels between pages.
67 const int kPagePadding = 40;
68
69 // Preferred tile size when showing in fixed layout.
70 const int kPreferredTileWidth = 88;
71 const int kPreferredTileHeight = 98;
72
73 // Width in pixels of the area on the sides that triggers a page flip.
74 const int kPageFlipZoneSize = 40;
75
76 // Delay in milliseconds to do the page flip.
77 const int kPageFlipDelayInMs = 1000;
78
79 // How many pages on either side of the selected one we prerender.
80 const int kPrerenderPages = 1;
81
82 // The drag and drop proxy should get scaled by this factor.
83 const float kDragAndDropProxyScale = 1.5f;
84
85 // Delays in milliseconds to show folder dropping preview circle.
86 const int kFolderDroppingDelay = 150;
87
88 // Delays in milliseconds to show re-order preview.
89 const int kReorderDelay = 120;
90
91 // Delays in milliseconds to show folder item reparent UI.
92 const int kFolderItemReparentDealy = 50;
93
94 // Radius of the circle, in which if entered, show folder dropping preview
95 // UI.
96 const int kFolderDroppingCircleRadius = 15;
97
98
99 // RowMoveAnimationDelegate is used when moving an item into a different row.
100 // Before running the animation, the item's layer is re-created and kept in
101 // the original position, then the item is moved to just before its target
102 // position and opacity set to 0. When the animation runs, this delegate moves
103 // the layer and fades it out while fading in the item at the same time.
104 class RowMoveAnimationDelegate
105     : public views::BoundsAnimator::OwnedAnimationDelegate {
106  public:
107   RowMoveAnimationDelegate(views::View* view,
108                            ui::Layer* layer,
109                            const gfx::Rect& layer_target)
110       : view_(view),
111         layer_(layer),
112         layer_start_(layer ? layer->bounds() : gfx::Rect()),
113         layer_target_(layer_target) {
114   }
115   virtual ~RowMoveAnimationDelegate() {}
116
117   // gfx::AnimationDelegate overrides:
118   virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
119     view_->layer()->SetOpacity(animation->GetCurrentValue());
120     view_->layer()->ScheduleDraw();
121
122     if (layer_) {
123       layer_->SetOpacity(1 - animation->GetCurrentValue());
124       layer_->SetBounds(animation->CurrentValueBetween(layer_start_,
125                                                        layer_target_));
126       layer_->ScheduleDraw();
127     }
128   }
129   virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE {
130     view_->layer()->SetOpacity(1.0f);
131     view_->layer()->ScheduleDraw();
132   }
133   virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE {
134     view_->layer()->SetOpacity(1.0f);
135     view_->layer()->ScheduleDraw();
136   }
137
138  private:
139   // The view that needs to be wrapped. Owned by views hierarchy.
140   views::View* view_;
141
142   scoped_ptr<ui::Layer> layer_;
143   const gfx::Rect layer_start_;
144   const gfx::Rect layer_target_;
145
146   DISALLOW_COPY_AND_ASSIGN(RowMoveAnimationDelegate);
147 };
148
149 // ItemRemoveAnimationDelegate is used to show animation for removing an item.
150 // This happens when user drags an item into a folder. The dragged item will
151 // be removed from the original list after it is dropped into the folder.
152 class ItemRemoveAnimationDelegate
153     : public views::BoundsAnimator::OwnedAnimationDelegate {
154  public:
155   explicit ItemRemoveAnimationDelegate(views::View* view)
156       : view_(view) {
157   }
158
159   virtual ~ItemRemoveAnimationDelegate() {
160   }
161
162   // gfx::AnimationDelegate overrides:
163   virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
164     view_->layer()->SetOpacity(1 - animation->GetCurrentValue());
165     view_->layer()->ScheduleDraw();
166   }
167
168  private:
169   scoped_ptr<views::View> view_;
170
171   DISALLOW_COPY_AND_ASSIGN(ItemRemoveAnimationDelegate);
172 };
173
174 // Gets the distance between the centers of the |rect_1| and |rect_2|.
175 int GetDistanceBetweenRects(gfx::Rect rect_1,
176                             gfx::Rect rect_2) {
177   return (rect_1.CenterPoint() - rect_2.CenterPoint()).Length();
178 }
179
180 // Returns true if the |item| is an folder item.
181 bool IsFolderItem(AppListItem* item) {
182   return (item->GetItemType() == AppListFolderItem::kItemType);
183 }
184
185 bool IsOEMFolderItem(AppListItem* item) {
186   return IsFolderItem(item) &&
187          (static_cast<AppListFolderItem*>(item))->folder_type() ==
188              AppListFolderItem::FOLDER_TYPE_OEM;
189 }
190
191 }  // namespace
192
193 #if defined(OS_WIN)
194 // Interprets drag events sent from Windows via the drag/drop API and forwards
195 // them to AppsGridView.
196 // On Windows, in order to have the OS perform the drag properly we need to
197 // provide it with a shortcut file which may or may not exist at the time the
198 // drag is started. Therefore while waiting for that shortcut to be located we
199 // just do a regular "internal" drag and transition into the synchronous drag
200 // when the shortcut is found/created. Hence a synchronous drag is an optional
201 // phase of a regular drag and non-Windows platforms drags are equivalent to a
202 // Windows drag that never enters the synchronous drag phase.
203 class SynchronousDrag : public ui::DragSourceWin {
204  public:
205   SynchronousDrag(AppsGridView* grid_view,
206                   AppListItemView* drag_view,
207                   const gfx::Point& drag_view_offset)
208       : grid_view_(grid_view),
209         drag_view_(drag_view),
210         drag_view_offset_(drag_view_offset),
211         has_shortcut_path_(false),
212         running_(false),
213         canceled_(false) {}
214
215   void set_shortcut_path(const base::FilePath& shortcut_path) {
216     has_shortcut_path_ = true;
217     shortcut_path_ = shortcut_path;
218   }
219
220   bool CanRun() {
221     return has_shortcut_path_ && !running_;
222   }
223
224   void Run() {
225     DCHECK(CanRun());
226
227     // Prevent the synchronous dragger being destroyed while the drag is
228     // running.
229     scoped_refptr<SynchronousDrag> this_ref = this;
230     running_ = true;
231
232     ui::OSExchangeData data;
233     SetupExchangeData(&data);
234
235     // Hide the dragged view because the OS is going to create its own.
236     const gfx::Size drag_view_size = drag_view_->size();
237     drag_view_->SetSize(gfx::Size(0, 0));
238
239     // Blocks until the drag is finished. Calls into the ui::DragSourceWin
240     // methods.
241     DWORD effects;
242     DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data),
243                this, DROPEFFECT_MOVE | DROPEFFECT_LINK, &effects);
244
245     // If |drag_view_| is NULL the drag was ended by some reentrant code.
246     if (drag_view_) {
247       // Restore the dragged view to its original size.
248       drag_view_->SetSize(drag_view_size);
249       drag_view_->OnSyncDragEnd();
250
251       grid_view_->EndDrag(canceled_ || !IsCursorWithinGridView());
252     }
253   }
254
255   void EndDragExternally() {
256     CancelDrag();
257     drag_view_ = NULL;
258   }
259
260  private:
261   // Overridden from ui::DragSourceWin.
262   virtual void OnDragSourceCancel() OVERRIDE {
263     canceled_ = true;
264   }
265
266   virtual void OnDragSourceDrop() OVERRIDE {
267   }
268
269   virtual void OnDragSourceMove() OVERRIDE {
270     grid_view_->UpdateDrag(AppsGridView::MOUSE, GetCursorInGridViewCoords());
271   }
272
273   void SetupExchangeData(ui::OSExchangeData* data) {
274     data->SetFilename(shortcut_path_);
275     gfx::ImageSkia image(drag_view_->GetDragImage());
276     gfx::Size image_size(image.size());
277     drag_utils::SetDragImageOnDataObject(
278         image,
279         image.size(),
280         drag_view_offset_ - drag_view_->GetDragImageOffset(),
281         data);
282   }
283
284   HWND GetGridViewHWND() {
285     return views::HWNDForView(grid_view_);
286   }
287
288   bool IsCursorWithinGridView() {
289     POINT p;
290     GetCursorPos(&p);
291     return GetGridViewHWND() == WindowFromPoint(p);
292   }
293
294   gfx::Point GetCursorInGridViewCoords() {
295     POINT p;
296     GetCursorPos(&p);
297     ScreenToClient(GetGridViewHWND(), &p);
298     gfx::Point grid_view_pt(p.x, p.y);
299     views::View::ConvertPointFromWidget(grid_view_, &grid_view_pt);
300     return grid_view_pt;
301   }
302
303   AppsGridView* grid_view_;
304   AppListItemView* drag_view_;
305   gfx::Point drag_view_offset_;
306   bool has_shortcut_path_;
307   base::FilePath shortcut_path_;
308   bool running_;
309   bool canceled_;
310
311   DISALLOW_COPY_AND_ASSIGN(SynchronousDrag);
312 };
313 #endif  // defined(OS_WIN)
314
315 AppsGridView::AppsGridView(AppsGridViewDelegate* delegate,
316                            PaginationModel* pagination_model)
317     : model_(NULL),
318       item_list_(NULL),
319       delegate_(delegate),
320       pagination_model_(pagination_model),
321       page_switcher_view_(new PageSwitcher(pagination_model)),
322       cols_(0),
323       rows_per_page_(0),
324       selected_view_(NULL),
325       drag_view_(NULL),
326       drag_start_page_(-1),
327       drag_pointer_(NONE),
328       drop_attempt_(DROP_FOR_NONE),
329       drag_and_drop_host_(NULL),
330       forward_events_to_drag_and_drop_host_(false),
331       page_flip_target_(-1),
332       page_flip_delay_in_ms_(kPageFlipDelayInMs),
333       bounds_animator_(this),
334       is_root_level_(true),
335       activated_item_view_(NULL),
336       dragging_for_reparent_item_(false),
337       weak_factory_(this) {
338   SetPaintToLayer(true);
339   SetFillsBoundsOpaquely(false);
340
341   pagination_model_->AddObserver(this);
342   AddChildView(page_switcher_view_);
343 }
344
345 AppsGridView::~AppsGridView() {
346   // Coming here |drag_view_| should already be canceled since otherwise the
347   // drag would disappear after the app list got animated away and closed,
348   // which would look odd.
349   DCHECK(!drag_view_);
350   if (drag_view_)
351     EndDrag(true);
352
353   if (model_)
354     model_->RemoveObserver(this);
355   pagination_model_->RemoveObserver(this);
356
357   if (item_list_)
358     item_list_->RemoveObserver(this);
359 }
360
361 void AppsGridView::SetLayout(int icon_size, int cols, int rows_per_page) {
362   icon_size_.SetSize(icon_size, icon_size);
363   cols_ = cols;
364   rows_per_page_ = rows_per_page;
365
366   SetBorder(views::Border::CreateEmptyBorder(
367       kTopPadding, kLeftRightPadding, 0, kLeftRightPadding));
368 }
369
370 void AppsGridView::SetModel(AppListModel* model) {
371   if (model_)
372     model_->RemoveObserver(this);
373
374   model_ = model;
375   if (model_)
376     model_->AddObserver(this);
377
378   Update();
379 }
380
381 void AppsGridView::SetItemList(AppListItemList* item_list) {
382   if (item_list_)
383     item_list_->RemoveObserver(this);
384   item_list_ = item_list;
385   if (item_list_)
386     item_list_->AddObserver(this);
387   Update();
388 }
389
390 void AppsGridView::SetSelectedView(views::View* view) {
391   if (IsSelectedView(view) || IsDraggedView(view))
392     return;
393
394   Index index = GetIndexOfView(view);
395   if (IsValidIndex(index))
396     SetSelectedItemByIndex(index);
397 }
398
399 void AppsGridView::ClearSelectedView(views::View* view) {
400   if (view && IsSelectedView(view)) {
401     selected_view_->SchedulePaint();
402     selected_view_ = NULL;
403   }
404 }
405
406 void AppsGridView::ClearAnySelectedView() {
407   if (selected_view_) {
408     selected_view_->SchedulePaint();
409     selected_view_ = NULL;
410   }
411 }
412
413 bool AppsGridView::IsSelectedView(const views::View* view) const {
414   return selected_view_ == view;
415 }
416
417 void AppsGridView::EnsureViewVisible(const views::View* view) {
418   if (pagination_model_->has_transition())
419     return;
420
421   Index index = GetIndexOfView(view);
422   if (IsValidIndex(index))
423     pagination_model_->SelectPage(index.page, false);
424 }
425
426 void AppsGridView::InitiateDrag(AppListItemView* view,
427                                 Pointer pointer,
428                                 const ui::LocatedEvent& event) {
429   DCHECK(view);
430   if (drag_view_ || pulsing_blocks_model_.view_size())
431     return;
432
433   drag_view_ = view;
434   drag_view_init_index_ = GetIndexOfView(drag_view_);
435   drag_view_offset_ = event.location();
436   drag_start_page_ = pagination_model_->selected_page();
437   ExtractDragLocation(event, &drag_start_grid_view_);
438   drag_view_start_ = gfx::Point(drag_view_->x(), drag_view_->y());
439 }
440
441 void AppsGridView::OnGotShortcutPath(const base::FilePath& path) {
442 #if defined(OS_WIN)
443   // Drag may have ended before we get the shortcut path.
444   if (!synchronous_drag_)
445     return;
446   // Setting the shortcut path here means the next time we hit UpdateDrag()
447   // we'll enter the synchronous drag.
448   // NOTE we don't Run() the drag here because that causes animations not to
449   // update for some reason.
450   synchronous_drag_->set_shortcut_path(path);
451   DCHECK(synchronous_drag_->CanRun());
452 #endif
453 }
454
455 void AppsGridView::StartSettingUpSynchronousDrag() {
456 #if defined(OS_WIN)
457   if (!delegate_)
458     return;
459
460   // Favor the drag and drop host over native win32 drag. For the Win8/ash
461   // launcher we want to have ashes drag and drop over win32's.
462   if (drag_and_drop_host_)
463     return;
464
465   delegate_->GetShortcutPathForApp(
466       drag_view_->item()->id(),
467       base::Bind(&AppsGridView::OnGotShortcutPath, base::Unretained(this)));
468   synchronous_drag_ = new SynchronousDrag(this, drag_view_, drag_view_offset_);
469 #endif
470 }
471
472 bool AppsGridView::RunSynchronousDrag() {
473 #if defined(OS_WIN)
474   if (synchronous_drag_ && synchronous_drag_->CanRun()) {
475     synchronous_drag_->Run();
476     synchronous_drag_ = NULL;
477     return true;
478   }
479 #endif
480   return false;
481 }
482
483 void AppsGridView::CleanUpSynchronousDrag() {
484 #if defined(OS_WIN)
485   if (synchronous_drag_)
486     synchronous_drag_->EndDragExternally();
487
488   synchronous_drag_ = NULL;
489 #endif
490 }
491
492 bool AppsGridView::UpdateDragFromItem(Pointer pointer,
493                                       const ui::LocatedEvent& event) {
494   DCHECK(drag_view_);
495
496   if (!is_root_level_)
497     UpdateDragStateInsideFolder(pointer, event);
498
499   gfx::Point drag_point_in_grid_view;
500   ExtractDragLocation(event, &drag_point_in_grid_view);
501   UpdateDrag(pointer, drag_point_in_grid_view);
502   if (!dragging())
503     return false;
504
505   // If a drag and drop host is provided, see if the drag operation needs to be
506   // forwarded.
507   gfx::Point location_in_screen = drag_point_in_grid_view;
508   views::View::ConvertPointToScreen(this, &location_in_screen);
509   DispatchDragEventToDragAndDropHost(location_in_screen);
510   if (drag_and_drop_host_)
511     drag_and_drop_host_->UpdateDragIconProxy(location_in_screen);
512   return true;
513 }
514
515 void AppsGridView::UpdateDrag(Pointer pointer, const gfx::Point& point) {
516   // EndDrag was called before if |drag_view_| is NULL.
517   if (!drag_view_)
518     return;
519
520   if (RunSynchronousDrag())
521     return;
522
523   gfx::Vector2d drag_vector(point - drag_start_grid_view_);
524   if (!dragging() && ExceededDragThreshold(drag_vector)) {
525     drag_pointer_ = pointer;
526     // Move the view to the front so that it appears on top of other views.
527     ReorderChildView(drag_view_, -1);
528     bounds_animator_.StopAnimatingView(drag_view_);
529     StartSettingUpSynchronousDrag();
530     if (!dragging_for_reparent_item_)
531       StartDragAndDropHostDrag(point);
532   }
533
534   if (drag_pointer_ != pointer)
535     return;
536
537   last_drag_point_ = point;
538   const Index last_drop_target = drop_target_;
539   DropAttempt last_drop_attempt = drop_attempt_;
540   CalculateDropTarget(last_drag_point_, false);
541
542   if (IsPointWithinDragBuffer(last_drag_point_))
543     MaybeStartPageFlipTimer(last_drag_point_);
544   else
545     StopPageFlipTimer();
546
547   gfx::Point page_switcher_point(last_drag_point_);
548   views::View::ConvertPointToTarget(this, page_switcher_view_,
549                                     &page_switcher_point);
550   page_switcher_view_->UpdateUIForDragPoint(page_switcher_point);
551
552   if (!EnableFolderDragDropUI()) {
553     if (last_drop_target != drop_target_)
554       AnimateToIdealBounds();
555     drag_view_->SetPosition(drag_view_start_ + drag_vector);
556     return;
557   }
558
559   // Update drag with folder UI enabled.
560   if (last_drop_target != drop_target_ ||
561       last_drop_attempt != drop_attempt_) {
562     if (drop_attempt_ == DROP_FOR_REORDER) {
563       folder_dropping_timer_.Stop();
564       reorder_timer_.Start(FROM_HERE,
565           base::TimeDelta::FromMilliseconds(kReorderDelay),
566           this, &AppsGridView::OnReorderTimer);
567     } else if (drop_attempt_ == DROP_FOR_FOLDER) {
568       reorder_timer_.Stop();
569       folder_dropping_timer_.Start(FROM_HERE,
570           base::TimeDelta::FromMilliseconds(kFolderDroppingDelay),
571           this, &AppsGridView::OnFolderDroppingTimer);
572     }
573
574     // Reset the previous drop target.
575     SetAsFolderDroppingTarget(last_drop_target, false);
576   }
577
578   drag_view_->SetPosition(drag_view_start_ + drag_vector);
579 }
580
581 void AppsGridView::EndDrag(bool cancel) {
582   // EndDrag was called before if |drag_view_| is NULL.
583   if (!drag_view_)
584     return;
585
586   // Coming here a drag and drop was in progress.
587   bool landed_in_drag_and_drop_host = forward_events_to_drag_and_drop_host_;
588   if (forward_events_to_drag_and_drop_host_) {
589     DCHECK(!IsDraggingForReparentInRootLevelGridView());
590     forward_events_to_drag_and_drop_host_ = false;
591     drag_and_drop_host_->EndDrag(cancel);
592     if (IsDraggingForReprentInHiddenGridView()) {
593       static_cast<AppListFolderView*>(parent())->
594           DispatchEndDragEventForReparent(true);
595     }
596   } else if (!cancel && dragging()) {
597     if (IsDraggingForReprentInHiddenGridView()) {
598       // Forward the EndDrag event to the root level grid view.
599       static_cast<AppListFolderView*>(parent())->
600           DispatchEndDragEventForReparent(false);
601       EndDragForReparentInHiddenFolderGridView();
602       return;
603     } else {
604       // Regular drag ending path, ie, not for reparenting.
605       CalculateDropTarget(last_drag_point_, true);
606       if (IsValidIndex(drop_target_)) {
607         if (!EnableFolderDragDropUI()) {
608             MoveItemInModel(drag_view_, drop_target_);
609         } else {
610           if (drop_attempt_ == DROP_FOR_REORDER)
611             MoveItemInModel(drag_view_, drop_target_);
612           else if (drop_attempt_ == DROP_FOR_FOLDER)
613             MoveItemToFolder(drag_view_, drop_target_);
614         }
615       }
616     }
617   }
618
619   if (drag_and_drop_host_) {
620     // If we had a drag and drop proxy icon, we delete it and make the real
621     // item visible again.
622     drag_and_drop_host_->DestroyDragIconProxy();
623     if (landed_in_drag_and_drop_host) {
624       // Move the item directly to the target location, avoiding the "zip back"
625       // animation if the user was pinning it to the shelf.
626       int i = drop_target_.slot;
627       gfx::Rect bounds = view_model_.ideal_bounds(i);
628       drag_view_->SetBoundsRect(bounds);
629     }
630     // Fade in slowly if it landed in the shelf.
631     SetViewHidden(drag_view_,
632                   false /* show */,
633                   !landed_in_drag_and_drop_host /* animate */);
634   }
635
636   // The drag can be ended after the synchronous drag is created but before it
637   // is Run().
638   CleanUpSynchronousDrag();
639
640   SetAsFolderDroppingTarget(drop_target_, false);
641   drop_attempt_ = DROP_FOR_NONE;
642   drag_pointer_ = NONE;
643   drop_target_ = Index();
644   drag_view_->OnDragEnded();
645   drag_view_ = NULL;
646   drag_start_grid_view_ = gfx::Point();
647   drag_start_page_ = -1;
648   drag_view_offset_ = gfx::Point();
649   AnimateToIdealBounds();
650
651   StopPageFlipTimer();
652
653   // If user releases mouse inside a folder's grid view, burst the folder
654   // container ink bubble.
655   if (!cancel && !is_root_level_ && !IsDraggingForReprentInHiddenGridView()) {
656     static_cast<AppListFolderView*>(parent())->
657         UpdateFolderViewBackground(false);
658   }
659
660   if (IsDraggingForReprentInHiddenGridView())
661     dragging_for_reparent_item_ = false;
662 }
663
664 void AppsGridView::StopPageFlipTimer() {
665   page_flip_timer_.Stop();
666   page_flip_target_ = -1;
667 }
668
669 AppListItemView* AppsGridView::GetItemViewAt(int index) const {
670   DCHECK(index >= 0 && index < view_model_.view_size());
671   return static_cast<AppListItemView*>(view_model_.view_at(index));
672 }
673
674 void AppsGridView::SetTopItemViewsVisible(bool visible) {
675   int top_item_count = std::min(static_cast<int>(kNumFolderTopItems),
676                                 view_model_.view_size());
677   for (int i = 0; i < top_item_count; ++i)
678     GetItemViewAt(i)->SetVisible(visible);
679 }
680
681 void AppsGridView::ScheduleShowHideAnimation(bool show) {
682   // Stop any previous animation.
683   layer()->GetAnimator()->StopAnimating();
684
685   // Set initial state.
686   SetVisible(true);
687   layer()->SetOpacity(show ? 0.0f : 1.0f);
688
689   ui::ScopedLayerAnimationSettings animation(layer()->GetAnimator());
690   animation.AddObserver(this);
691   animation.SetTweenType(
692       show ? kFolderFadeInTweenType : kFolderFadeOutTweenType);
693   animation.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
694       show ? kFolderTransitionInDurationMs : kFolderTransitionOutDurationMs));
695
696   layer()->SetOpacity(show ? 1.0f : 0.0f);
697 }
698
699 void AppsGridView::InitiateDragFromReparentItemInRootLevelGridView(
700     AppListItemView* original_drag_view,
701     const gfx::Rect& drag_view_rect,
702     const gfx::Point& drag_point) {
703   DCHECK(original_drag_view && !drag_view_);
704   DCHECK(!dragging_for_reparent_item_);
705
706   // Create a new AppListItemView to duplicate the original_drag_view in the
707   // folder's grid view.
708   AppListItemView* view = new AppListItemView(this, original_drag_view->item());
709   AddChildView(view);
710   drag_view_ = view;
711   drag_view_->SetPaintToLayer(true);
712   // Note: For testing purpose, SetFillsBoundsOpaquely can be set to true to
713   // show the gray background.
714   drag_view_->SetFillsBoundsOpaquely(false);
715   drag_view_->SetIconSize(icon_size_);
716   drag_view_->SetBoundsRect(drag_view_rect);
717   drag_view_->SetDragUIState();  // Hide the title of the drag_view_.
718
719   // Hide the drag_view_ for drag icon proxy.
720   SetViewHidden(drag_view_,
721                 true /* hide */,
722                 true /* no animate */);
723
724   // Add drag_view_ to the end of the view_model_.
725   view_model_.Add(drag_view_, view_model_.view_size());
726
727   drag_start_page_ = pagination_model_->selected_page();
728   drag_start_grid_view_ = drag_point;
729
730   drag_view_start_ = gfx::Point(drag_view_->x(), drag_view_->y());
731
732   // Set the flag in root level grid view.
733   dragging_for_reparent_item_ = true;
734 }
735
736 void AppsGridView::UpdateDragFromReparentItem(
737     Pointer pointer,
738     const ui::LocatedEvent& event) {
739   DCHECK(drag_view_);
740   DCHECK(IsDraggingForReparentInRootLevelGridView());
741
742   gfx::Point drag_point_in_grid_view;
743   ExtractDragLocation(event, &drag_point_in_grid_view);
744   UpdateDrag(pointer, drag_point_in_grid_view);
745 }
746
747 bool AppsGridView::IsDraggedView(const views::View* view) const {
748   return drag_view_ == view;
749 }
750
751 void AppsGridView::SetDragAndDropHostOfCurrentAppList(
752     ApplicationDragAndDropHost* drag_and_drop_host) {
753   drag_and_drop_host_ = drag_and_drop_host;
754 }
755
756 void AppsGridView::Prerender(int page_index) {
757   Layout();
758   int start = std::max(0, (page_index - kPrerenderPages) * tiles_per_page());
759   int end = std::min(view_model_.view_size(),
760                      (page_index + 1 + kPrerenderPages) * tiles_per_page());
761   for (int i = start; i < end; i++) {
762     AppListItemView* v = static_cast<AppListItemView*>(view_model_.view_at(i));
763     v->Prerender();
764   }
765 }
766
767 gfx::Size AppsGridView::GetPreferredSize() {
768   const gfx::Insets insets(GetInsets());
769   const gfx::Size tile_size = gfx::Size(kPreferredTileWidth,
770                                         kPreferredTileHeight);
771   const int page_switcher_height =
772       page_switcher_view_->GetPreferredSize().height();
773   return gfx::Size(
774       tile_size.width() * cols_ + insets.width(),
775       tile_size.height() * rows_per_page_ +
776           page_switcher_height + insets.height());
777 }
778
779 bool AppsGridView::GetDropFormats(
780     int* formats,
781     std::set<OSExchangeData::CustomFormat>* custom_formats) {
782   // TODO(koz): Only accept a specific drag type for app shortcuts.
783   *formats = OSExchangeData::FILE_NAME;
784   return true;
785 }
786
787 bool AppsGridView::CanDrop(const OSExchangeData& data) {
788   return true;
789 }
790
791 int AppsGridView::OnDragUpdated(const ui::DropTargetEvent& event) {
792   return ui::DragDropTypes::DRAG_MOVE;
793 }
794
795 void AppsGridView::Layout() {
796   if (bounds_animator_.IsAnimating())
797     bounds_animator_.Cancel();
798
799   CalculateIdealBounds();
800   for (int i = 0; i < view_model_.view_size(); ++i) {
801     views::View* view = view_model_.view_at(i);
802     if (view != drag_view_)
803       view->SetBoundsRect(view_model_.ideal_bounds(i));
804   }
805   views::ViewModelUtils::SetViewBoundsToIdealBounds(pulsing_blocks_model_);
806
807   const int page_switcher_height =
808       page_switcher_view_->GetPreferredSize().height();
809   gfx::Rect rect(GetContentsBounds());
810   rect.set_y(rect.bottom() - page_switcher_height);
811   rect.set_height(page_switcher_height);
812   page_switcher_view_->SetBoundsRect(rect);
813 }
814
815 bool AppsGridView::OnKeyPressed(const ui::KeyEvent& event) {
816   bool handled = false;
817   if (selected_view_)
818     handled = selected_view_->OnKeyPressed(event);
819
820   if (!handled) {
821     const int forward_dir = base::i18n::IsRTL() ? -1 : 1;
822     switch (event.key_code()) {
823       case ui::VKEY_LEFT:
824         MoveSelected(0, -forward_dir, 0);
825         return true;
826       case ui::VKEY_RIGHT:
827         MoveSelected(0, forward_dir, 0);
828         return true;
829       case ui::VKEY_UP:
830         MoveSelected(0, 0, -1);
831         return true;
832       case ui::VKEY_DOWN:
833         MoveSelected(0, 0, 1);
834         return true;
835       case ui::VKEY_PRIOR: {
836         MoveSelected(-1, 0, 0);
837         return true;
838       }
839       case ui::VKEY_NEXT: {
840         MoveSelected(1, 0, 0);
841         return true;
842       }
843       default:
844         break;
845     }
846   }
847
848   return handled;
849 }
850
851 bool AppsGridView::OnKeyReleased(const ui::KeyEvent& event) {
852   bool handled = false;
853   if (selected_view_)
854     handled = selected_view_->OnKeyReleased(event);
855
856   return handled;
857 }
858
859 void AppsGridView::ViewHierarchyChanged(
860     const ViewHierarchyChangedDetails& details) {
861   if (!details.is_add && details.parent == this) {
862     if (selected_view_ == details.child)
863       selected_view_ = NULL;
864
865     if (drag_view_ == details.child)
866       EndDrag(true);
867
868     bounds_animator_.StopAnimatingView(details.child);
869   }
870 }
871
872 void AppsGridView::Update() {
873   DCHECK(!selected_view_ && !drag_view_);
874   view_model_.Clear();
875   if (!item_list_ || !item_list_->item_count())
876     return;
877   for (size_t i = 0; i < item_list_->item_count(); ++i) {
878     views::View* view = CreateViewForItemAtIndex(i);
879     view_model_.Add(view, i);
880     AddChildView(view);
881   }
882   UpdatePaging();
883   UpdatePulsingBlockViews();
884   Layout();
885   SchedulePaint();
886 }
887
888 void AppsGridView::UpdatePaging() {
889   int total_page = view_model_.view_size() && tiles_per_page()
890                        ? (view_model_.view_size() - 1) / tiles_per_page() + 1
891                        : 0;
892
893   pagination_model_->SetTotalPages(total_page);
894 }
895
896 void AppsGridView::UpdatePulsingBlockViews() {
897   const int existing_items = item_list_ ? item_list_->item_count() : 0;
898   const int available_slots =
899       tiles_per_page() - existing_items % tiles_per_page();
900   const int desired = model_->status() == AppListModel::STATUS_SYNCING ?
901       available_slots : 0;
902
903   if (pulsing_blocks_model_.view_size() == desired)
904     return;
905
906   while (pulsing_blocks_model_.view_size() > desired) {
907     views::View* view = pulsing_blocks_model_.view_at(0);
908     pulsing_blocks_model_.Remove(0);
909     delete view;
910   }
911
912   while (pulsing_blocks_model_.view_size() < desired) {
913     views::View* view = new PulsingBlockView(
914         gfx::Size(kPreferredTileWidth, kPreferredTileHeight), true);
915     pulsing_blocks_model_.Add(view, 0);
916     AddChildView(view);
917   }
918 }
919
920 views::View* AppsGridView::CreateViewForItemAtIndex(size_t index) {
921   // The drag_view_ might be pending for deletion, therefore view_model_
922   // may have one more item than item_list_.
923   DCHECK_LE(index, item_list_->item_count());
924   AppListItemView* view = new AppListItemView(this,
925                                               item_list_->item_at(index));
926   view->SetIconSize(icon_size_);
927 #if defined(USE_AURA)
928   view->SetPaintToLayer(true);
929   view->SetFillsBoundsOpaquely(false);
930 #endif
931   return view;
932 }
933
934 AppsGridView::Index AppsGridView::GetIndexFromModelIndex(
935     int model_index) const {
936   return Index(model_index / tiles_per_page(), model_index % tiles_per_page());
937 }
938
939 int AppsGridView::GetModelIndexFromIndex(const Index& index) const {
940   return index.page * tiles_per_page() + index.slot;
941 }
942
943 void AppsGridView::SetSelectedItemByIndex(const Index& index) {
944   if (GetIndexOfView(selected_view_) == index)
945     return;
946
947   views::View* new_selection = GetViewAtIndex(index);
948   if (!new_selection)
949     return;  // Keep current selection.
950
951   if (selected_view_)
952     selected_view_->SchedulePaint();
953
954   EnsureViewVisible(new_selection);
955   selected_view_ = new_selection;
956   selected_view_->SchedulePaint();
957   selected_view_->NotifyAccessibilityEvent(
958       ui::AX_EVENT_FOCUS, true);
959 }
960
961 bool AppsGridView::IsValidIndex(const Index& index) const {
962   return index.page >= 0 && index.page < pagination_model_->total_pages() &&
963          index.slot >= 0 && index.slot < tiles_per_page() &&
964          GetModelIndexFromIndex(index) < view_model_.view_size();
965 }
966
967 AppsGridView::Index AppsGridView::GetIndexOfView(
968     const views::View* view) const {
969   const int model_index = view_model_.GetIndexOfView(view);
970   if (model_index == -1)
971     return Index();
972
973   return GetIndexFromModelIndex(model_index);
974 }
975
976 views::View* AppsGridView::GetViewAtIndex(const Index& index) const {
977   if (!IsValidIndex(index))
978     return NULL;
979
980   const int model_index = GetModelIndexFromIndex(index);
981   return view_model_.view_at(model_index);
982 }
983
984 void AppsGridView::MoveSelected(int page_delta,
985                                 int slot_x_delta,
986                                 int slot_y_delta) {
987   if (!selected_view_)
988     return SetSelectedItemByIndex(Index(pagination_model_->selected_page(), 0));
989
990   const Index& selected = GetIndexOfView(selected_view_);
991   int target_slot = selected.slot + slot_x_delta + slot_y_delta * cols_;
992
993   if (selected.slot % cols_ == 0 && slot_x_delta == -1) {
994     if (selected.page > 0) {
995       page_delta = -1;
996       target_slot = selected.slot + cols_ - 1;
997     } else {
998       target_slot = selected.slot;
999     }
1000   }
1001
1002   if (selected.slot % cols_ == cols_ - 1 && slot_x_delta == 1) {
1003     if (selected.page < pagination_model_->total_pages() - 1) {
1004       page_delta = 1;
1005       target_slot = selected.slot - cols_ + 1;
1006     } else {
1007       target_slot = selected.slot;
1008     }
1009   }
1010
1011   // Clamp the target slot to the last item if we are moving to the last page
1012   // but our target slot is past the end of the item list.
1013   if (page_delta &&
1014       selected.page + page_delta == pagination_model_->total_pages() - 1) {
1015     int last_item_slot = (view_model_.view_size() - 1) % tiles_per_page();
1016     if (last_item_slot < target_slot) {
1017       target_slot = last_item_slot;
1018     }
1019   }
1020
1021   int target_page = std::min(pagination_model_->total_pages() - 1,
1022                              std::max(selected.page + page_delta, 0));
1023   SetSelectedItemByIndex(Index(target_page, target_slot));
1024 }
1025
1026 void AppsGridView::CalculateIdealBounds() {
1027   gfx::Rect rect(GetContentsBounds());
1028   if (rect.IsEmpty())
1029     return;
1030
1031   gfx::Size tile_size(kPreferredTileWidth, kPreferredTileHeight);
1032
1033   gfx::Rect grid_rect(gfx::Size(tile_size.width() * cols_,
1034                                 tile_size.height() * rows_per_page_));
1035   grid_rect.Intersect(rect);
1036
1037   // Page width including padding pixels. A tile.x + page_width means the same
1038   // tile slot in the next page.
1039   const int page_width = grid_rect.width() + kPagePadding;
1040
1041   // If there is a transition, calculates offset for current and target page.
1042   const int current_page = pagination_model_->selected_page();
1043   const PaginationModel::Transition& transition =
1044       pagination_model_->transition();
1045   const bool is_valid =
1046       pagination_model_->is_valid_page(transition.target_page);
1047
1048   // Transition to right means negative offset.
1049   const int dir = transition.target_page > current_page ? -1 : 1;
1050   const int transition_offset = is_valid ?
1051       transition.progress * page_width * dir : 0;
1052
1053   const int total_views =
1054       view_model_.view_size() + pulsing_blocks_model_.view_size();
1055   int slot_index = 0;
1056   for (int i = 0; i < total_views; ++i) {
1057     if (i < view_model_.view_size() && view_model_.view_at(i) == drag_view_) {
1058       if (EnableFolderDragDropUI() && drop_attempt_ == DROP_FOR_FOLDER)
1059         ++slot_index;
1060       continue;
1061     }
1062
1063     Index view_index = GetIndexFromModelIndex(slot_index);
1064
1065     if (drop_target_ == view_index) {
1066       if (EnableFolderDragDropUI() && drop_attempt_ == DROP_FOR_FOLDER) {
1067         view_index = GetIndexFromModelIndex(slot_index);
1068       } else if (!EnableFolderDragDropUI() ||
1069                  drop_attempt_ == DROP_FOR_REORDER) {
1070         ++slot_index;
1071         view_index = GetIndexFromModelIndex(slot_index);
1072       }
1073     }
1074
1075     // Decides an x_offset for current item.
1076     int x_offset = 0;
1077     if (view_index.page < current_page)
1078       x_offset = -page_width;
1079     else if (view_index.page > current_page)
1080       x_offset = page_width;
1081
1082     if (is_valid) {
1083       if (view_index.page == current_page ||
1084           view_index.page == transition.target_page) {
1085         x_offset += transition_offset;
1086       }
1087     }
1088
1089     const int row = view_index.slot / cols_;
1090     const int col = view_index.slot % cols_;
1091     gfx::Rect tile_slot(
1092         gfx::Point(grid_rect.x() + col * tile_size.width() + x_offset,
1093                    grid_rect.y() + row * tile_size.height()),
1094         tile_size);
1095     if (i < view_model_.view_size()) {
1096       view_model_.set_ideal_bounds(i, tile_slot);
1097     } else {
1098       pulsing_blocks_model_.set_ideal_bounds(i - view_model_.view_size(),
1099                                              tile_slot);
1100     }
1101
1102     ++slot_index;
1103   }
1104 }
1105
1106 void AppsGridView::AnimateToIdealBounds() {
1107   const gfx::Rect visible_bounds(GetVisibleBounds());
1108
1109   CalculateIdealBounds();
1110   for (int i = 0; i < view_model_.view_size(); ++i) {
1111     views::View* view = view_model_.view_at(i);
1112     if (view == drag_view_)
1113       continue;
1114
1115     const gfx::Rect& target = view_model_.ideal_bounds(i);
1116     if (bounds_animator_.GetTargetBounds(view) == target)
1117       continue;
1118
1119     const gfx::Rect& current = view->bounds();
1120     const bool current_visible = visible_bounds.Intersects(current);
1121     const bool target_visible = visible_bounds.Intersects(target);
1122     const bool visible = current_visible || target_visible;
1123
1124     const int y_diff = target.y() - current.y();
1125     if (visible && y_diff && y_diff % kPreferredTileHeight == 0) {
1126       AnimationBetweenRows(view,
1127                            current_visible,
1128                            current,
1129                            target_visible,
1130                            target);
1131     } else {
1132       bounds_animator_.AnimateViewTo(view, target);
1133     }
1134   }
1135 }
1136
1137 void AppsGridView::AnimationBetweenRows(views::View* view,
1138                                         bool animate_current,
1139                                         const gfx::Rect& current,
1140                                         bool animate_target,
1141                                         const gfx::Rect& target) {
1142   // Determine page of |current| and |target|. -1 means in the left invisible
1143   // page, 0 is the center visible page and 1 means in the right invisible page.
1144   const int current_page = current.x() < 0 ? -1 :
1145       current.x() >= width() ? 1 : 0;
1146   const int target_page = target.x() < 0 ? -1 :
1147       target.x() >= width() ? 1 : 0;
1148
1149   const int dir = current_page < target_page ||
1150       (current_page == target_page && current.y() < target.y()) ? 1 : -1;
1151
1152 #if defined(USE_AURA)
1153   scoped_ptr<ui::Layer> layer;
1154   if (animate_current) {
1155     layer = view->RecreateLayer();
1156     layer->SuppressPaint();
1157
1158     view->SetFillsBoundsOpaquely(false);
1159     view->layer()->SetOpacity(0.f);
1160   }
1161
1162   gfx::Rect current_out(current);
1163   current_out.Offset(dir * kPreferredTileWidth, 0);
1164 #endif
1165
1166   gfx::Rect target_in(target);
1167   if (animate_target)
1168     target_in.Offset(-dir * kPreferredTileWidth, 0);
1169   view->SetBoundsRect(target_in);
1170   bounds_animator_.AnimateViewTo(view, target);
1171
1172 #if defined(USE_AURA)
1173   bounds_animator_.SetAnimationDelegate(
1174       view,
1175       new RowMoveAnimationDelegate(view, layer.release(), current_out),
1176       true);
1177 #endif
1178 }
1179
1180 void AppsGridView::ExtractDragLocation(const ui::LocatedEvent& event,
1181                                        gfx::Point* drag_point) {
1182 #if defined(USE_AURA) && !defined(OS_WIN)
1183   // Use root location of |event| instead of location in |drag_view_|'s
1184   // coordinates because |drag_view_| has a scale transform and location
1185   // could have integer round error and causes jitter.
1186   *drag_point = event.root_location();
1187
1188   // GetWidget() could be NULL for tests.
1189   if (GetWidget()) {
1190     aura::Window::ConvertPointToTarget(
1191         GetWidget()->GetNativeWindow()->GetRootWindow(),
1192         GetWidget()->GetNativeWindow(),
1193         drag_point);
1194   }
1195
1196   views::View::ConvertPointFromWidget(this, drag_point);
1197 #else
1198   // For non-aura, root location is not clearly defined but |drag_view_| does
1199   // not have the scale transform. So no round error would be introduced and
1200   // it's okay to use View::ConvertPointToTarget.
1201   *drag_point = event.location();
1202   views::View::ConvertPointToTarget(drag_view_, this, drag_point);
1203 #endif
1204 }
1205
1206 void AppsGridView::CalculateDropTarget(const gfx::Point& drag_point,
1207                                        bool use_page_button_hovering) {
1208   if (EnableFolderDragDropUI()) {
1209     CalculateDropTargetWithFolderEnabled(drag_point, use_page_button_hovering);
1210     return;
1211   }
1212
1213   int current_page = pagination_model_->selected_page();
1214   gfx::Point point(drag_point);
1215   if (!IsPointWithinDragBuffer(drag_point)) {
1216     point = drag_start_grid_view_;
1217     current_page = drag_start_page_;
1218   }
1219
1220   if (use_page_button_hovering &&
1221       page_switcher_view_->bounds().Contains(point)) {
1222     gfx::Point page_switcher_point(point);
1223     views::View::ConvertPointToTarget(this, page_switcher_view_,
1224                                       &page_switcher_point);
1225     int page = page_switcher_view_->GetPageForPoint(page_switcher_point);
1226     if (pagination_model_->is_valid_page(page)) {
1227       drop_target_.page = page;
1228       drop_target_.slot = tiles_per_page() - 1;
1229     }
1230   } else {
1231     gfx::Rect bounds(GetContentsBounds());
1232     const int drop_row = (point.y() - bounds.y()) / kPreferredTileHeight;
1233     const int drop_col = std::min(cols_ - 1,
1234         (point.x() - bounds.x()) / kPreferredTileWidth);
1235
1236     drop_target_.page = current_page;
1237     drop_target_.slot = std::max(0, std::min(
1238         tiles_per_page() - 1,
1239         drop_row * cols_ + drop_col));
1240   }
1241
1242   // Limits to the last possible slot on last page.
1243   if (drop_target_.page == pagination_model_->total_pages() - 1) {
1244     drop_target_.slot = std::min(
1245         (view_model_.view_size() - 1) % tiles_per_page(),
1246         drop_target_.slot);
1247   }
1248 }
1249
1250
1251 void AppsGridView::CalculateDropTargetWithFolderEnabled(
1252     const gfx::Point& drag_point,
1253     bool use_page_button_hovering) {
1254   gfx::Point point(drag_point);
1255   if (!IsPointWithinDragBuffer(drag_point)) {
1256     point = drag_start_grid_view_;
1257   }
1258
1259   if (use_page_button_hovering &&
1260       page_switcher_view_->bounds().Contains(point)) {
1261     gfx::Point page_switcher_point(point);
1262     views::View::ConvertPointToTarget(this, page_switcher_view_,
1263                                       &page_switcher_point);
1264     int page = page_switcher_view_->GetPageForPoint(page_switcher_point);
1265     if (pagination_model_->is_valid_page(page))
1266       drop_attempt_ = DROP_FOR_NONE;
1267   } else {
1268     DCHECK(drag_view_);
1269     // Try to find the nearest target for folder dropping or re-ordering.
1270     drop_target_ = GetNearestTileForDragView();
1271   }
1272 }
1273
1274 void AppsGridView::OnReorderTimer() {
1275   if (drop_attempt_ == DROP_FOR_REORDER)
1276     AnimateToIdealBounds();
1277 }
1278
1279 void AppsGridView::OnFolderItemReparentTimer() {
1280   DCHECK(!is_root_level_);
1281   if (drag_out_of_folder_container_) {
1282     static_cast<AppListFolderView*>(parent())->ReparentItem(
1283         drag_view_, last_drag_point_);
1284
1285     // Set the flag in the folder's grid view.
1286     dragging_for_reparent_item_ = true;
1287
1288     // Do not observe any data change since it is going to be hidden.
1289     item_list_->RemoveObserver(this);
1290     item_list_ = NULL;
1291   }
1292 }
1293
1294 void AppsGridView::OnFolderDroppingTimer() {
1295   if (drop_attempt_ == DROP_FOR_FOLDER)
1296     SetAsFolderDroppingTarget(drop_target_, true);
1297 }
1298
1299 void AppsGridView::UpdateDragStateInsideFolder(
1300     Pointer pointer,
1301     const ui::LocatedEvent& event) {
1302   if (IsUnderOEMFolder())
1303     return;
1304
1305   if (IsDraggingForReprentInHiddenGridView()) {
1306     // Dispatch drag event to root level grid view for re-parenting folder
1307     // folder item purpose.
1308     DispatchDragEventForReparent(pointer, event);
1309     return;
1310   }
1311
1312   // Regular drag and drop in a folder's grid view.
1313   AppListFolderView* folder_view = static_cast<AppListFolderView*>(parent());
1314   folder_view->UpdateFolderViewBackground(true);
1315
1316   // Calculate if the drag_view_ is dragged out of the folder's container
1317   // ink bubble.
1318   gfx::Rect bounds_to_folder_view = ConvertRectToParent(drag_view_->bounds());
1319   gfx::Point pt = bounds_to_folder_view.CenterPoint();
1320   bool is_item_dragged_out_of_folder =
1321       folder_view->IsPointOutsideOfFolderBoundray(pt);
1322   if (is_item_dragged_out_of_folder) {
1323     if (!drag_out_of_folder_container_) {
1324       folder_item_reparent_timer_.Start(FROM_HERE,
1325           base::TimeDelta::FromMilliseconds(kFolderItemReparentDealy),
1326           this, &AppsGridView::OnFolderItemReparentTimer);
1327       drag_out_of_folder_container_ = true;
1328     }
1329   } else {
1330     folder_item_reparent_timer_.Stop();
1331     drag_out_of_folder_container_ = false;
1332   }
1333 }
1334
1335 bool AppsGridView::IsDraggingForReparentInRootLevelGridView() const {
1336   return (is_root_level_ && dragging_for_reparent_item_);
1337 }
1338
1339 bool AppsGridView::IsDraggingForReprentInHiddenGridView() const {
1340   return (!is_root_level_ && dragging_for_reparent_item_);
1341 }
1342
1343 gfx::Rect AppsGridView::GetTargetIconRectInFolder(
1344     AppListItemView* drag_item_view,
1345     AppListItemView* folder_item_view) {
1346   gfx::Rect view_ideal_bounds = view_model_.ideal_bounds(
1347       view_model_.GetIndexOfView(folder_item_view));
1348   gfx::Rect icon_ideal_bounds =
1349       folder_item_view->GetIconBoundsForTargetViewBounds(view_ideal_bounds);
1350   AppListFolderItem* folder_item =
1351       static_cast<AppListFolderItem*>(folder_item_view->item());
1352   return folder_item->GetTargetIconRectInFolderForItem(
1353       drag_item_view->item(), icon_ideal_bounds);
1354 }
1355
1356 bool AppsGridView::IsUnderOEMFolder() {
1357   if (is_root_level_)
1358     return false;
1359
1360   return static_cast<AppListFolderView*>(parent())->IsOEMFolder();
1361 }
1362
1363 void AppsGridView::DispatchDragEventForReparent(
1364     Pointer pointer,
1365     const ui::LocatedEvent& event) {
1366   static_cast<AppListFolderView*>(parent())->
1367       DispatchDragEventForReparent(pointer, event);
1368 }
1369
1370 void AppsGridView::EndDragFromReparentItemInRootLevel(
1371     bool events_forwarded_to_drag_drop_host) {
1372   // EndDrag was called before if |drag_view_| is NULL.
1373   if (!drag_view_)
1374     return;
1375
1376   DCHECK(IsDraggingForReparentInRootLevelGridView());
1377   bool cancel_reparent = false;
1378   scoped_ptr<AppListItemView> cached_drag_view;
1379   if (!events_forwarded_to_drag_drop_host) {
1380     CalculateDropTarget(last_drag_point_, true);
1381     if (IsValidIndex(drop_target_)) {
1382       if (drop_attempt_ == DROP_FOR_REORDER) {
1383         ReparentItemForReorder(drag_view_, drop_target_);
1384       } else if (drop_attempt_ == DROP_FOR_FOLDER) {
1385         ReparentItemToAnotherFolder(drag_view_, drop_target_);
1386       } else {  // DROP_FOR_NONE_
1387         cancel_reparent = true;
1388         // Note(jennyz): cached_drag_view makes sure drag_view_ will be deleted
1389         // after AnimateToIdealBounds() is called.
1390         // There is a problem in layer() animation which cause DCHECK failure
1391         // if a child view is deleted immediately before re-creating layer in
1392         // layer animation. The layer tree seems marked dirty, and complaining
1393         // when we try to re-create layer in AnimationBetweenRows when calling
1394         // AnimateToIdealBounds.
1395         cached_drag_view.reset(drag_view_);
1396       }
1397     }
1398
1399     if (!cancel_reparent) {
1400       SetViewHidden(drag_view_, false /* show */, true /* no animate */);
1401     }
1402   }
1403
1404   // The drag can be ended after the synchronous drag is created but before it
1405   // is Run().
1406   CleanUpSynchronousDrag();
1407
1408   SetAsFolderDroppingTarget(drop_target_, false);
1409   drop_attempt_ = DROP_FOR_NONE;
1410   drag_pointer_ = NONE;
1411   drop_target_ = Index();
1412   if (!cancel_reparent)
1413     drag_view_->OnDragEnded();
1414   drag_view_ = NULL;
1415   drag_start_grid_view_ = gfx::Point();
1416   drag_start_page_ = -1;
1417   drag_view_offset_ = gfx::Point();
1418   dragging_for_reparent_item_ = false;
1419   if (cancel_reparent)
1420     CancelFolderItemReparent(cached_drag_view.get());
1421   AnimateToIdealBounds();
1422
1423   StopPageFlipTimer();
1424 }
1425
1426 void AppsGridView::EndDragForReparentInHiddenFolderGridView() {
1427   if (drag_and_drop_host_) {
1428     // If we had a drag and drop proxy icon, we delete it and make the real
1429     // item visible again.
1430     drag_and_drop_host_->DestroyDragIconProxy();
1431   }
1432
1433   // The drag can be ended after the synchronous drag is created but before it
1434   // is Run().
1435   CleanUpSynchronousDrag();
1436
1437   SetAsFolderDroppingTarget(drop_target_, false);
1438   drop_attempt_ = DROP_FOR_NONE;
1439   drag_pointer_ = NONE;
1440   drop_target_ = Index();
1441   drag_view_->OnDragEnded();
1442   drag_view_ = NULL;
1443   drag_start_grid_view_ = gfx::Point();
1444   drag_start_page_ = -1;
1445   drag_view_offset_ = gfx::Point();
1446   dragging_for_reparent_item_ = false;
1447 }
1448
1449 void AppsGridView::OnFolderItemRemoved() {
1450   DCHECK(!is_root_level_);
1451   item_list_ = NULL;
1452 }
1453
1454 void AppsGridView::StartDragAndDropHostDrag(const gfx::Point& grid_location) {
1455   // When a drag and drop host is given, the item can be dragged out of the app
1456   // list window. In that case a proxy widget needs to be used.
1457   // Note: This code has very likely to be changed for Windows (non metro mode)
1458   // when a |drag_and_drop_host_| gets implemented.
1459   if (!drag_view_ || !drag_and_drop_host_)
1460     return;
1461
1462   gfx::Point screen_location = grid_location;
1463   views::View::ConvertPointToScreen(this, &screen_location);
1464
1465   // Determine the mouse offset to the center of the icon so that the drag and
1466   // drop host follows this layer.
1467   gfx::Vector2d delta = drag_view_offset_ -
1468                         drag_view_->GetLocalBounds().CenterPoint();
1469   delta.set_y(delta.y() + drag_view_->title()->size().height() / 2);
1470
1471   // We have to hide the original item since the drag and drop host will do
1472   // the OS dependent code to "lift off the dragged item".
1473   DCHECK(!IsDraggingForReparentInRootLevelGridView());
1474   drag_and_drop_host_->CreateDragIconProxy(screen_location,
1475                                            drag_view_->item()->icon(),
1476                                            drag_view_,
1477                                            delta,
1478                                            kDragAndDropProxyScale);
1479   SetViewHidden(drag_view_,
1480            true /* hide */,
1481            true /* no animation */);
1482 }
1483
1484 void AppsGridView::DispatchDragEventToDragAndDropHost(
1485     const gfx::Point& location_in_screen_coordinates) {
1486   if (!drag_view_ || !drag_and_drop_host_)
1487     return;
1488
1489   if (GetLocalBounds().Contains(last_drag_point_)) {
1490     // The event was issued inside the app menu and we should get all events.
1491     if (forward_events_to_drag_and_drop_host_) {
1492       // The DnD host was previously called and needs to be informed that the
1493       // session returns to the owner.
1494       forward_events_to_drag_and_drop_host_ = false;
1495       drag_and_drop_host_->EndDrag(true);
1496     }
1497   } else {
1498     if (IsFolderItem(drag_view_->item()))
1499       return;
1500
1501     // The event happened outside our app menu and we might need to dispatch.
1502     if (forward_events_to_drag_and_drop_host_) {
1503       // Dispatch since we have already started.
1504       if (!drag_and_drop_host_->Drag(location_in_screen_coordinates)) {
1505         // The host is not active any longer and we cancel the operation.
1506         forward_events_to_drag_and_drop_host_ = false;
1507         drag_and_drop_host_->EndDrag(true);
1508       }
1509     } else {
1510       if (drag_and_drop_host_->StartDrag(drag_view_->item()->id(),
1511                                          location_in_screen_coordinates)) {
1512         // From now on we forward the drag events.
1513         forward_events_to_drag_and_drop_host_ = true;
1514         // Any flip operations are stopped.
1515         StopPageFlipTimer();
1516       }
1517     }
1518   }
1519 }
1520
1521 void AppsGridView::MaybeStartPageFlipTimer(const gfx::Point& drag_point) {
1522   if (!IsPointWithinDragBuffer(drag_point))
1523     StopPageFlipTimer();
1524   int new_page_flip_target = -1;
1525
1526   if (page_switcher_view_->bounds().Contains(drag_point)) {
1527     gfx::Point page_switcher_point(drag_point);
1528     views::View::ConvertPointToTarget(this, page_switcher_view_,
1529                                       &page_switcher_point);
1530     new_page_flip_target =
1531         page_switcher_view_->GetPageForPoint(page_switcher_point);
1532   }
1533
1534   // TODO(xiyuan): Fix this for RTL.
1535   if (new_page_flip_target == -1 && drag_point.x() < kPageFlipZoneSize)
1536     new_page_flip_target = pagination_model_->selected_page() - 1;
1537
1538   if (new_page_flip_target == -1 &&
1539       drag_point.x() > width() - kPageFlipZoneSize) {
1540     new_page_flip_target = pagination_model_->selected_page() + 1;
1541   }
1542
1543   if (new_page_flip_target == page_flip_target_)
1544     return;
1545
1546   StopPageFlipTimer();
1547   if (pagination_model_->is_valid_page(new_page_flip_target)) {
1548     page_flip_target_ = new_page_flip_target;
1549
1550     if (page_flip_target_ != pagination_model_->selected_page()) {
1551       page_flip_timer_.Start(FROM_HERE,
1552           base::TimeDelta::FromMilliseconds(page_flip_delay_in_ms_),
1553           this, &AppsGridView::OnPageFlipTimer);
1554     }
1555   }
1556 }
1557
1558 void AppsGridView::OnPageFlipTimer() {
1559   DCHECK(pagination_model_->is_valid_page(page_flip_target_));
1560   pagination_model_->SelectPage(page_flip_target_, true);
1561 }
1562
1563 void AppsGridView::MoveItemInModel(views::View* item_view,
1564                                    const Index& target) {
1565   int current_model_index = view_model_.GetIndexOfView(item_view);
1566   DCHECK_GE(current_model_index, 0);
1567
1568   int target_model_index = GetModelIndexFromIndex(target);
1569   if (target_model_index == current_model_index)
1570     return;
1571
1572   item_list_->RemoveObserver(this);
1573   item_list_->MoveItem(current_model_index, target_model_index);
1574   view_model_.Move(current_model_index, target_model_index);
1575   item_list_->AddObserver(this);
1576
1577   if (pagination_model_->selected_page() != target.page)
1578     pagination_model_->SelectPage(target.page, false);
1579 }
1580
1581 void AppsGridView::MoveItemToFolder(views::View* item_view,
1582                                     const Index& target) {
1583   const std::string& source_item_id =
1584       static_cast<AppListItemView*>(item_view)->item()->id();
1585   AppListItemView* target_view =
1586       static_cast<AppListItemView*>(GetViewAtSlotOnCurrentPage(target.slot));
1587   const std::string&  target_view_item_id = target_view->item()->id();
1588
1589   // Make change to data model.
1590   item_list_->RemoveObserver(this);
1591   std::string folder_item_id =
1592       model_->MergeItems(target_view_item_id, source_item_id);
1593   item_list_->AddObserver(this);
1594   if (folder_item_id.empty()) {
1595     LOG(ERROR) << "Unable to merge into item id: " << target_view_item_id;
1596     return;
1597   }
1598   if (folder_item_id != target_view_item_id) {
1599     // New folder was created, change the view model to replace the old target
1600     // view with the new folder item view.
1601     size_t folder_item_index;
1602     if (item_list_->FindItemIndex(folder_item_id, &folder_item_index)) {
1603       int target_view_index = view_model_.GetIndexOfView(target_view);
1604       view_model_.Remove(target_view_index);
1605       gfx::Rect target_view_bounds = target_view->bounds();
1606       delete target_view;
1607       views::View* target_folder_view =
1608           CreateViewForItemAtIndex(folder_item_index);
1609       target_folder_view->SetBoundsRect(target_view_bounds);
1610       view_model_.Add(target_folder_view, target_view_index);
1611       AddChildView(target_folder_view);
1612     } else {
1613       LOG(ERROR) << "Folder no longer in item_list: " << folder_item_id;
1614     }
1615   }
1616
1617   // Fade out the drag_view_ and delete it when animation ends.
1618   int drag_view_index = view_model_.GetIndexOfView(drag_view_);
1619   view_model_.Remove(drag_view_index);
1620   bounds_animator_.AnimateViewTo(drag_view_, drag_view_->bounds());
1621   bounds_animator_.SetAnimationDelegate(
1622       drag_view_, new ItemRemoveAnimationDelegate(drag_view_), true);
1623   UpdatePaging();
1624 }
1625
1626 void AppsGridView::ReparentItemForReorder(views::View* item_view,
1627                                           const Index& target) {
1628   item_list_->RemoveObserver(this);
1629   model_->RemoveObserver(this);
1630
1631   AppListItem* reparent_item = static_cast<AppListItemView*>(item_view)->item();
1632   DCHECK(reparent_item->IsInFolder());
1633   AppListFolderItem* source_folder = static_cast<AppListFolderItem*>(
1634         item_list_->FindItem(reparent_item->folder_id()));
1635
1636   // Move the item from its parent folder to top level item list.
1637   // Must move to target_model_index, the location we expect the target item
1638   // to be, not the item location we want to insert before.
1639   int target_model_index = GetModelIndexFromIndex(target);
1640   int current_model_index = view_model_.GetIndexOfView(item_view);
1641   syncer::StringOrdinal target_position;
1642   if (target_model_index < static_cast<int>(item_list_->item_count()))
1643     target_position = item_list_->item_at(target_model_index)->position();
1644   model_->MoveItemToFolderAt(reparent_item, "", target_position);
1645   view_model_.Move(current_model_index, target_model_index);
1646
1647   if (source_folder->ChildItemCount() == 1)
1648     RemoveLastItemFromReparentItemFolder(source_folder);
1649
1650   item_list_->AddObserver(this);
1651   model_->AddObserver(this);
1652   UpdatePaging();
1653 }
1654
1655 void AppsGridView::ReparentItemToAnotherFolder(views::View* item_view,
1656                                                const Index& target) {
1657   DCHECK(IsDraggingForReparentInRootLevelGridView());
1658
1659   // Make change to data model.
1660   item_list_->RemoveObserver(this);
1661
1662   AppListItem* reparent_item = static_cast<AppListItemView*>(item_view)->item();
1663   DCHECK(reparent_item->IsInFolder());
1664   AppListFolderItem* source_folder = static_cast<AppListFolderItem*>(
1665       item_list_->FindItem(reparent_item->folder_id()));
1666
1667   AppListItemView* target_view =
1668       static_cast<AppListItemView*>(GetViewAtSlotOnCurrentPage(target.slot));
1669   AppListItem* target_item = target_view->item();
1670
1671   // Move item to the target folder.
1672   std::string target_id_after_merge =
1673       model_->MergeItems(target_item->id(), reparent_item->id());
1674   if (target_id_after_merge.empty()) {
1675     LOG(ERROR) << "Unable to reparent to item id: " << target_item->id();
1676     item_list_->AddObserver(this);
1677     return;
1678   }
1679
1680   if (target_id_after_merge != target_item->id()) {
1681     // New folder was created, change the view model to replace the old target
1682     // view with the new folder item view.
1683     const std::string& new_folder_id = reparent_item->folder_id();
1684     size_t new_folder_index;
1685     if (item_list_->FindItemIndex(new_folder_id, &new_folder_index)) {
1686       int target_view_index = view_model_.GetIndexOfView(target_view);
1687       view_model_.Remove(target_view_index);
1688       delete target_view;
1689       views::View* new_folder_view =
1690           CreateViewForItemAtIndex(new_folder_index);
1691       view_model_.Add(new_folder_view, target_view_index);
1692       AddChildView(new_folder_view);
1693     } else {
1694       LOG(ERROR) << "Folder no longer in item_list: " << new_folder_id;
1695     }
1696   }
1697
1698   if (source_folder->ChildItemCount() == 1)
1699     RemoveLastItemFromReparentItemFolder(source_folder);
1700
1701   item_list_->AddObserver(this);
1702
1703   // Fade out the drag_view_ and delete it when animation ends.
1704   int drag_view_index = view_model_.GetIndexOfView(drag_view_);
1705   view_model_.Remove(drag_view_index);
1706   bounds_animator_.AnimateViewTo(drag_view_, drag_view_->bounds());
1707   bounds_animator_.SetAnimationDelegate(
1708       drag_view_, new ItemRemoveAnimationDelegate(drag_view_), true);
1709   UpdatePaging();
1710 }
1711
1712 // After moving the re-parenting item out of the folder, if there is only 1 item
1713 // left, remove the last item out of the folder, delete the folder and insert it
1714 // to the data model at the same position. Make the same change to view_model_
1715 // accordingly.
1716 void AppsGridView::RemoveLastItemFromReparentItemFolder(
1717     AppListFolderItem* source_folder) {
1718   DCHECK_EQ(1u, source_folder->ChildItemCount());
1719
1720   // Delete view associated with the folder item to be removed.
1721   AppListItemView* folder_item_view = activated_item_view();
1722   int folder_model_index = view_model_.GetIndexOfView(folder_item_view);
1723   view_model_.Remove(folder_model_index);
1724   delete folder_item_view;
1725
1726   // Now make the data change to remove the folder item in model.
1727   AppListItem* last_item = source_folder->item_list()->item_at(0);
1728   model_->MoveItemToFolderAt(last_item, "", source_folder->position());
1729
1730   // Create a new item view for the last item in folder.
1731   size_t last_item_index;
1732   item_list_->FindItemIndex(last_item->id(), &last_item_index);
1733   views::View* last_item_view = CreateViewForItemAtIndex(last_item_index);
1734   view_model_.Add(last_item_view, last_item_index);
1735   AddChildView(last_item_view);
1736 }
1737
1738 void AppsGridView::CancelFolderItemReparent(AppListItemView* drag_item_view) {
1739   // The icon of the dragged item must target to its final ideal bounds after
1740   // the animation completes.
1741   CalculateIdealBounds();
1742
1743   // Remove drag_view_ from view_model_, it will be deleted after the animation.
1744   int drag_view_index = view_model_.GetIndexOfView(drag_item_view);
1745   view_model_.Remove(drag_view_index);
1746
1747   gfx::Rect target_icon_rect =
1748       GetTargetIconRectInFolder(drag_item_view, activated_item_view_);
1749
1750   gfx::Rect drag_view_icon_to_grid =
1751       drag_item_view->ConvertRectToParent(drag_item_view->GetIconBounds());
1752   drag_view_icon_to_grid.ClampToCenteredSize(
1753         gfx::Size(kPreferredIconDimension, kPreferredIconDimension));
1754   TopIconAnimationView* icon_view = new TopIconAnimationView(
1755       drag_item_view->item()->icon(),
1756       target_icon_rect,
1757       false);    /* animate like closing folder */
1758   AddChildView(icon_view);
1759   icon_view->SetBoundsRect(drag_view_icon_to_grid);
1760   icon_view->TransformView();
1761 }
1762
1763 void AppsGridView::RemoveFolderIfOnlyOneItemLeft(const std::string& folder_id) {
1764   AppListFolderItem* folder_item = model_->FindFolderItem(folder_id);
1765   DCHECK(folder_item);
1766
1767   if (folder_item->ChildItemCount() != 1u)
1768     return;
1769
1770   model_->MoveItemToFolderAt(
1771       folder_item->item_list()->item_at(0), "", folder_item->position());
1772 }
1773
1774 void AppsGridView::CancelContextMenusOnCurrentPage() {
1775   int start = pagination_model_->selected_page() * tiles_per_page();
1776   int end = std::min(view_model_.view_size(), start + tiles_per_page());
1777   for (int i = start; i < end; ++i) {
1778     AppListItemView* view =
1779         static_cast<AppListItemView*>(view_model_.view_at(i));
1780     view->CancelContextMenu();
1781   }
1782 }
1783
1784 bool AppsGridView::IsPointWithinDragBuffer(const gfx::Point& point) const {
1785   gfx::Rect rect(GetLocalBounds());
1786   rect.Inset(-kDragBufferPx, -kDragBufferPx, -kDragBufferPx, -kDragBufferPx);
1787   return rect.Contains(point);
1788 }
1789
1790 void AppsGridView::ButtonPressed(views::Button* sender,
1791                                  const ui::Event& event) {
1792   if (dragging())
1793     return;
1794
1795   if (strcmp(sender->GetClassName(), AppListItemView::kViewClassName))
1796     return;
1797
1798   if (delegate_) {
1799     activated_item_view_ = static_cast<AppListItemView*>(sender);
1800     delegate_->ActivateApp(static_cast<AppListItemView*>(sender)->item(),
1801                            event.flags());
1802   }
1803 }
1804
1805 void AppsGridView::OnListItemAdded(size_t index, AppListItem* item) {
1806   EndDrag(true);
1807
1808   views::View* view = CreateViewForItemAtIndex(index);
1809   view_model_.Add(view, index);
1810   AddChildView(view);
1811
1812   UpdatePaging();
1813   UpdatePulsingBlockViews();
1814   Layout();
1815   SchedulePaint();
1816 }
1817
1818 void AppsGridView::OnListItemRemoved(size_t index, AppListItem* item) {
1819   EndDrag(true);
1820
1821   views::View* view = view_model_.view_at(index);
1822   view_model_.Remove(index);
1823   delete view;
1824
1825   // If there is only one item left under the folder, remove the folder.
1826   // We do allow OEM folder to contain only one item.
1827   if (!is_root_level_ && item_list_->item_count() == 1 && !IsUnderOEMFolder()) {
1828     std::string folder_id = item_list_->item_at(0)->folder_id();
1829     // TODO(jennyz): Don't remove the folder if this is an OEM folder, this
1830     // depends on https://codereview.chromium.org/197403005/.
1831     // Post the delayed task to modify data, so that it won't break the atomic
1832     // data operation in the data model of RemoveItemFromFolder which originates
1833     // the OnListItemRemove when calling RemoveItem.
1834     base::MessageLoopForUI::current()->PostTask(
1835         FROM_HERE,
1836         base::Bind(&AppsGridView::RemoveFolderIfOnlyOneItemLeft,
1837                    weak_factory_.GetWeakPtr(),
1838                    folder_id));
1839   }
1840
1841   UpdatePaging();
1842   UpdatePulsingBlockViews();
1843   Layout();
1844   SchedulePaint();
1845 }
1846
1847 void AppsGridView::OnListItemMoved(size_t from_index,
1848                                    size_t to_index,
1849                                    AppListItem* item) {
1850   EndDrag(true);
1851   view_model_.Move(from_index, to_index);
1852
1853   UpdatePaging();
1854   AnimateToIdealBounds();
1855 }
1856
1857 void AppsGridView::TotalPagesChanged() {
1858 }
1859
1860 void AppsGridView::SelectedPageChanged(int old_selected, int new_selected) {
1861   if (dragging()) {
1862     CalculateDropTarget(last_drag_point_, true);
1863     Layout();
1864     MaybeStartPageFlipTimer(last_drag_point_);
1865   } else {
1866     ClearSelectedView(selected_view_);
1867     Layout();
1868   }
1869 }
1870
1871 void AppsGridView::TransitionStarted() {
1872   CancelContextMenusOnCurrentPage();
1873 }
1874
1875 void AppsGridView::TransitionChanged() {
1876   // Update layout for valid page transition only since over-scroll no longer
1877   // animates app icons.
1878   const PaginationModel::Transition& transition =
1879       pagination_model_->transition();
1880   if (pagination_model_->is_valid_page(transition.target_page))
1881     Layout();
1882 }
1883
1884 void AppsGridView::OnAppListModelStatusChanged() {
1885   UpdatePulsingBlockViews();
1886   Layout();
1887   SchedulePaint();
1888 }
1889
1890 void AppsGridView::SetViewHidden(views::View* view, bool hide, bool immediate) {
1891 #if defined(USE_AURA)
1892   ui::ScopedLayerAnimationSettings animator(view->layer()->GetAnimator());
1893   animator.SetPreemptionStrategy(
1894       immediate ? ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET :
1895                   ui::LayerAnimator::BLEND_WITH_CURRENT_ANIMATION);
1896   view->layer()->SetOpacity(hide ? 0 : 1);
1897 #endif
1898 }
1899
1900 void AppsGridView::OnImplicitAnimationsCompleted() {
1901   if (layer()->opacity() == 0.0f)
1902     SetVisible(false);
1903 }
1904
1905 bool AppsGridView::EnableFolderDragDropUI() {
1906   // Enable drag and drop folder UI only if it is at the app list root level
1907   // and the switch is on and the target folder can still accept new items.
1908   return switches::IsFolderUIEnabled() && is_root_level_ &&
1909       CanDropIntoTarget(drop_target_);
1910 }
1911
1912 bool AppsGridView::CanDropIntoTarget(const Index& drop_target) {
1913   views::View* target_view = GetViewAtSlotOnCurrentPage(drop_target.slot);
1914   if (!target_view)
1915     return true;
1916
1917   AppListItem* target_item =
1918       static_cast<AppListItemView*>(target_view)->item();
1919   // Items can be dropped into non-folders (which have no children) or folders
1920   // that have fewer than the max allowed items.
1921   // OEM folder does not allow to drag/drop other items in it.
1922   return target_item->ChildItemCount() < kMaxFolderItems &&
1923          !IsOEMFolderItem(target_item);
1924 }
1925
1926 // TODO(jennyz): Optimize the calculation for finding nearest tile.
1927 AppsGridView::Index AppsGridView::GetNearestTileForDragView() {
1928   Index nearest_tile;
1929   nearest_tile.page = -1;
1930   nearest_tile.slot = -1;
1931   int d_min = -1;
1932
1933   // Calculate the top left tile |drag_view| intersects.
1934   gfx::Point pt = drag_view_->bounds().origin();
1935   CalculateNearestTileForVertex(pt, &nearest_tile, &d_min);
1936
1937   // Calculate the top right tile |drag_view| intersects.
1938   pt = drag_view_->bounds().top_right();
1939   CalculateNearestTileForVertex(pt, &nearest_tile, &d_min);
1940
1941   // Calculate the bottom left tile |drag_view| intersects.
1942   pt = drag_view_->bounds().bottom_left();
1943   CalculateNearestTileForVertex(pt, &nearest_tile, &d_min);
1944
1945   // Calculate the bottom right tile |drag_view| intersects.
1946   pt = drag_view_->bounds().bottom_right();
1947   CalculateNearestTileForVertex(pt, &nearest_tile, &d_min);
1948
1949   const int d_folder_dropping =
1950       kFolderDroppingCircleRadius + kPreferredIconDimension / 2;
1951   const int d_reorder =
1952       kReorderDroppingCircleRadius + kPreferredIconDimension / 2;
1953
1954   // If user drags an item across pages to the last page, and targets it
1955   // to the last empty slot on it, push the last item for re-ordering.
1956   if (IsFirstEmptySlot(nearest_tile) && d_min < d_reorder) {
1957     drop_attempt_ = DROP_FOR_REORDER;
1958     nearest_tile.slot = nearest_tile.slot - 1;
1959     return nearest_tile;
1960   }
1961
1962   if (IsValidIndex(nearest_tile)) {
1963     if (d_min < d_folder_dropping) {
1964       views::View* target_view = GetViewAtSlotOnCurrentPage(nearest_tile.slot);
1965       if (target_view &&
1966           !IsFolderItem(static_cast<AppListItemView*>(drag_view_)->item())) {
1967         // If a non-folder item is dragged to the target slot with an item
1968         // sitting on it, attempt to drop the dragged item into the folder
1969         // containing the item on nearest_tile.
1970         drop_attempt_ = DROP_FOR_FOLDER;
1971         return nearest_tile;
1972       } else {
1973         // If the target slot is blank, or the dragged item is a folder, attempt
1974         // to re-order.
1975         drop_attempt_ = DROP_FOR_REORDER;
1976         return nearest_tile;
1977       }
1978     } else if (d_min < d_reorder) {
1979       // Entering the re-order circle of the slot.
1980       drop_attempt_ = DROP_FOR_REORDER;
1981       return nearest_tile;
1982     }
1983   }
1984
1985   // If |drag_view| is not entering the re-order or fold dropping region of
1986   // any items, cancel any previous re-order or folder dropping timer, and
1987   // return itself.
1988   drop_attempt_ = DROP_FOR_NONE;
1989   reorder_timer_.Stop();
1990   folder_dropping_timer_.Stop();
1991
1992   // When dragging for reparent a folder item, it should go back to its parent
1993   // folder item if there is no drop target.
1994   if (IsDraggingForReparentInRootLevelGridView()) {
1995     DCHECK(activated_item_view_);
1996     return GetIndexOfView(activated_item_view_);
1997   }
1998
1999   return GetIndexOfView(drag_view_);
2000 }
2001
2002 void AppsGridView::CalculateNearestTileForVertex(const gfx::Point& vertex,
2003                                                  Index* nearest_tile,
2004                                                  int* d_min) {
2005   Index target_index;
2006   gfx::Rect target_bounds = GetTileBoundsForPoint(vertex, &target_index);
2007
2008   if (target_bounds.IsEmpty() || target_index == *nearest_tile)
2009     return;
2010
2011   // Do not count the tile, where drag_view_ used to sit on and is still moving
2012   // on top of it, in calculating nearest tile for drag_view_.
2013   views::View* target_view = GetViewAtSlotOnCurrentPage(target_index.slot);
2014   if (target_index == drag_view_init_index_ && !target_view &&
2015       !IsDraggingForReparentInRootLevelGridView()) {
2016     return;
2017   }
2018
2019   int d_center = GetDistanceBetweenRects(drag_view_->bounds(), target_bounds);
2020   if (*d_min < 0 || d_center < *d_min) {
2021     *d_min = d_center;
2022     *nearest_tile = target_index;
2023   }
2024 }
2025
2026 gfx::Rect AppsGridView::GetTileBoundsForPoint(const gfx::Point& point,
2027                                               Index *tile_index) {
2028   // Check if |point| is outside of contents bounds.
2029   gfx::Rect bounds(GetContentsBounds());
2030   if (!bounds.Contains(point))
2031     return gfx::Rect();
2032
2033   // Calculate which tile |point| is enclosed in.
2034   int x = point.x();
2035   int y = point.y();
2036   int col = (x - bounds.x()) / kPreferredTileWidth;
2037   int row = (y - bounds.y()) / kPreferredTileHeight;
2038   gfx::Rect tile_rect = GetTileBounds(row, col);
2039
2040   // Check if |point| is outside a valid item's tile.
2041   Index index(pagination_model_->selected_page(), row * cols_ + col);
2042   *tile_index = index;
2043   return tile_rect;
2044 }
2045
2046 gfx::Rect AppsGridView::GetTileBounds(int row, int col) const {
2047   gfx::Rect bounds(GetContentsBounds());
2048   gfx::Size tile_size(kPreferredTileWidth, kPreferredTileHeight);
2049   gfx::Rect grid_rect(gfx::Size(tile_size.width() * cols_,
2050                                 tile_size.height() * rows_per_page_));
2051   grid_rect.Intersect(bounds);
2052   gfx::Rect tile_rect(
2053       gfx::Point(grid_rect.x() + col * tile_size.width(),
2054                  grid_rect.y() + row * tile_size.height()),
2055       tile_size);
2056   return tile_rect;
2057 }
2058
2059 bool AppsGridView::IsFirstEmptySlot(const Index& index) const {
2060   // Don't count the hidden drag_view_ created from the item_dragged out of a
2061   // folder during re-parenting into the total number of views that are visible
2062   // on all grid view pages.
2063   int total_views = IsDraggingForReparentInRootLevelGridView()
2064                         ? view_model_.view_size() - 1
2065                         : view_model_.view_size();
2066   int last_possible_slot = (total_views - 1) % tiles_per_page();
2067   return (index.page == pagination_model_->total_pages() - 1 &&
2068           index.slot == last_possible_slot + 1);
2069 }
2070
2071 views::View* AppsGridView::GetViewAtSlotOnCurrentPage(int slot) {
2072   if (slot < 0)
2073     return NULL;
2074
2075   // Calculate the original bound of the tile at |index|.
2076   int row = slot / cols_;
2077   int col = slot % cols_;
2078   gfx::Rect tile_rect = GetTileBounds(row, col);
2079
2080   for (int i = 0; i < view_model_.view_size(); ++i) {
2081     views::View* view = view_model_.view_at(i);
2082     if (view->bounds() == tile_rect && view != drag_view_)
2083       return view;
2084   }
2085   return NULL;
2086 }
2087
2088 void AppsGridView::SetAsFolderDroppingTarget(const Index& target_index,
2089                                              bool is_target_folder) {
2090   AppListItemView* target_view =
2091       static_cast<AppListItemView*>(
2092           GetViewAtSlotOnCurrentPage(target_index.slot));
2093   if (target_view)
2094     target_view->SetAsAttemptedFolderTarget(is_target_folder);
2095 }
2096
2097 }  // namespace app_list