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