Home | History | Annotate | Download | only in views
      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 "content/public/browser/web_contents.h"
     13 #include "ui/app_list/app_list_constants.h"
     14 #include "ui/app_list/app_list_folder_item.h"
     15 #include "ui/app_list/app_list_item_model.h"
     16 #include "ui/app_list/app_list_switches.h"
     17 #include "ui/app_list/pagination_model.h"
     18 #include "ui/app_list/views/app_list_drag_and_drop_host.h"
     19 #include "ui/app_list/views/app_list_item_view.h"
     20 #include "ui/app_list/views/apps_grid_view_delegate.h"
     21 #include "ui/app_list/views/page_switcher.h"
     22 #include "ui/app_list/views/pulsing_block_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/controls/webview/webview.h"
     28 #include "ui/views/view_model_utils.h"
     29 #include "ui/views/widget/widget.h"
     30 
     31 #if defined(USE_AURA)
     32 #include "ui/aura/root_window.h"
     33 #include "ui/aura/window.h"
     34 #if defined(OS_WIN)
     35 #include "ui/views/win/hwnd_util.h"
     36 #endif  // defined(OS_WIN)
     37 #endif  // defined(USE_AURA)
     38 
     39 #if defined(OS_WIN)
     40 #include "base/command_line.h"
     41 #include "base/files/file_path.h"
     42 #include "base/win/shortcut.h"
     43 #include "ui/base/dragdrop/drag_utils.h"
     44 #include "ui/base/dragdrop/drop_target_win.h"
     45 #include "ui/base/dragdrop/os_exchange_data.h"
     46 #include "ui/base/dragdrop/os_exchange_data_provider_win.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 = 250;
     84 
     85 // Delays in milliseconds to show re-order preview.
     86 const int kReorderDelay = 50;
     87 
     88 // Radius of the circle, in which if entered, show folder dropping preview
     89 // UI.
     90 const int kFolderDroppingCircleRadius = 15;
     91 
     92 // Radius of the circle, in which if entered, show re-order preview.
     93 const int kReorderDroppingCircleRadius = 30;
     94 
     95 // Max items allowed in a folder.
     96 size_t kMaxFolderItems = 16;
     97 
     98 // RowMoveAnimationDelegate is used when moving an item into a different row.
     99 // Before running the animation, the item's layer is re-created and kept in
    100 // the original position, then the item is moved to just before its target
    101 // position and opacity set to 0. When the animation runs, this delegate moves
    102 // the layer and fades it out while fading in the item at the same time.
    103 class RowMoveAnimationDelegate
    104     : public views::BoundsAnimator::OwnedAnimationDelegate {
    105  public:
    106   RowMoveAnimationDelegate(views::View* view,
    107                            ui::Layer* layer,
    108                            const gfx::Rect& layer_target)
    109       : view_(view),
    110         layer_(layer),
    111         layer_start_(layer ? layer->bounds() : gfx::Rect()),
    112         layer_target_(layer_target) {
    113   }
    114   virtual ~RowMoveAnimationDelegate() {}
    115 
    116   // gfx::AnimationDelegate overrides:
    117   virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
    118     view_->layer()->SetOpacity(animation->GetCurrentValue());
    119     view_->layer()->ScheduleDraw();
    120 
    121     if (layer_) {
    122       layer_->SetOpacity(1 - animation->GetCurrentValue());
    123       layer_->SetBounds(animation->CurrentValueBetween(layer_start_,
    124                                                        layer_target_));
    125       layer_->ScheduleDraw();
    126     }
    127   }
    128   virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE {
    129     view_->layer()->SetOpacity(1.0f);
    130     view_->layer()->ScheduleDraw();
    131   }
    132   virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE {
    133     view_->layer()->SetOpacity(1.0f);
    134     view_->layer()->ScheduleDraw();
    135   }
    136 
    137  private:
    138   // The view that needs to be wrapped. Owned by views hierarchy.
    139   views::View* view_;
    140 
    141   scoped_ptr<ui::Layer> layer_;
    142   const gfx::Rect layer_start_;
    143   const gfx::Rect layer_target_;
    144 
    145   DISALLOW_COPY_AND_ASSIGN(RowMoveAnimationDelegate);
    146 };
    147 
    148 // ItemRemoveAnimationDelegate is used to show animation for removing an item.
    149 // This happens when user drags an item into a folder. The dragged item will
    150 // be removed from the original list after it is dropped into the folder.
    151 class ItemRemoveAnimationDelegate
    152     : public views::BoundsAnimator::OwnedAnimationDelegate {
    153  public:
    154   explicit ItemRemoveAnimationDelegate(views::View* view)
    155       : view_(view) {
    156   }
    157 
    158   virtual ~ItemRemoveAnimationDelegate() {
    159   }
    160 
    161   // gfx::AnimationDelegate overrides:
    162   virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
    163     view_->layer()->SetOpacity(1 - animation->GetCurrentValue());
    164     view_->layer()->ScheduleDraw();
    165   }
    166 
    167  private:
    168   scoped_ptr<views::View> view_;
    169 
    170   DISALLOW_COPY_AND_ASSIGN(ItemRemoveAnimationDelegate);
    171 };
    172 
    173 // Gets the distance between the centers of the |rect_1| and |rect_2|.
    174 int GetDistanceBetweenRects(gfx::Rect rect_1,
    175                             gfx::Rect rect_2) {
    176   return (rect_1.CenterPoint() - rect_2.CenterPoint()).Length();
    177 }
    178 
    179 // Returns true if the |item| is an folder item.
    180 bool IsFolderItem(AppListItemModel* item) {
    181   return (item->GetAppType() == AppListFolderItem::kAppType);
    182 }
    183 
    184 // Merges |source_item| into the folder containing the target item specified
    185 // by |target_item_id|. Both |source_item| and target item belongs to
    186 // |item_list|.
    187 // Returns the index of the target folder.
    188 size_t MergeItems(AppListItemList* item_list,
    189                   const std::string& target_item_id,
    190                   AppListItemModel* source_item) {
    191   scoped_ptr<AppListItemModel> source_item_ptr =
    192       item_list->RemoveItem(source_item->id());
    193   DCHECK_EQ(source_item, source_item_ptr.get());
    194   size_t target_index;
    195   bool found_target_item = item_list->FindItemIndex(target_item_id,
    196                                                     &target_index);
    197   DCHECK(found_target_item);
    198   AppListItemModel* target_item = item_list->item_at(target_index);
    199   if (IsFolderItem(target_item)) {
    200     AppListFolderItem* target_folder =
    201         static_cast<AppListFolderItem*>(target_item);
    202     target_folder->item_list()->AddItem(source_item_ptr.release());
    203   } else {
    204     scoped_ptr<AppListItemModel> target_item_ptr =
    205         item_list->RemoveItemAt(target_index);
    206     DCHECK_EQ(target_item, target_item_ptr.get());
    207     AppListFolderItem* new_folder =
    208         new AppListFolderItem(base::GenerateGUID());
    209     new_folder->item_list()->AddItem(target_item_ptr.release());
    210     new_folder->item_list()->AddItem(source_item_ptr.release());
    211     item_list->InsertItemAt(new_folder, target_index);
    212   }
    213 
    214   return target_index;
    215 }
    216 
    217 }  // namespace
    218 
    219 #if defined(OS_WIN)
    220 // Interprets drag events sent from Windows via the drag/drop API and forwards
    221 // them to AppsGridView.
    222 // On Windows, in order to have the OS perform the drag properly we need to
    223 // provide it with a shortcut file which may or may not exist at the time the
    224 // drag is started. Therefore while waiting for that shortcut to be located we
    225 // just do a regular "internal" drag and transition into the synchronous drag
    226 // when the shortcut is found/created. Hence a synchronous drag is an optional
    227 // phase of a regular drag and non-Windows platforms drags are equivalent to a
    228 // Windows drag that never enters the synchronous drag phase.
    229 class SynchronousDrag : public ui::DragSourceWin {
    230  public:
    231   SynchronousDrag(AppsGridView* grid_view,
    232                   AppListItemView* drag_view,
    233                   const gfx::Point& drag_view_offset)
    234       : grid_view_(grid_view),
    235         drag_view_(drag_view),
    236         drag_view_offset_(drag_view_offset),
    237         has_shortcut_path_(false),
    238         running_(false),
    239         canceled_(false) {}
    240 
    241   void set_shortcut_path(const base::FilePath& shortcut_path) {
    242     has_shortcut_path_ = true;
    243     shortcut_path_ = shortcut_path;
    244   }
    245 
    246   bool CanRun() {
    247     return has_shortcut_path_ && !running_;
    248   }
    249 
    250   void Run() {
    251     DCHECK(CanRun());
    252     running_ = true;
    253 
    254     ui::OSExchangeData data;
    255     SetupExchangeData(&data);
    256 
    257     // Hide the dragged view because the OS is going to create its own.
    258     const gfx::Size drag_view_size = drag_view_->size();
    259     drag_view_->SetSize(gfx::Size(0, 0));
    260 
    261     // Blocks until the drag is finished. Calls into the ui::DragSourceWin
    262     // methods.
    263     DWORD effects;
    264     DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data),
    265                this, DROPEFFECT_MOVE | DROPEFFECT_LINK, &effects);
    266 
    267     // Restore the dragged view to its original size.
    268     drag_view_->SetSize(drag_view_size);
    269     drag_view_->OnSyncDragEnd();
    270 
    271     grid_view_->EndDrag(canceled_ || !IsCursorWithinGridView());
    272   }
    273 
    274  private:
    275   // Overridden from ui::DragSourceWin.
    276   virtual void OnDragSourceCancel() OVERRIDE {
    277     canceled_ = true;
    278   }
    279 
    280   virtual void OnDragSourceDrop() OVERRIDE {
    281   }
    282 
    283   virtual void OnDragSourceMove() OVERRIDE {
    284     grid_view_->UpdateDrag(AppsGridView::MOUSE, GetCursorInGridViewCoords());
    285   }
    286 
    287   void SetupExchangeData(ui::OSExchangeData* data) {
    288     data->SetFilename(shortcut_path_);
    289     gfx::ImageSkia image(drag_view_->GetDragImage());
    290     gfx::Size image_size(image.size());
    291     drag_utils::SetDragImageOnDataObject(
    292         image,
    293         image.size(),
    294         drag_view_offset_ - drag_view_->GetDragImageOffset(),
    295         data);
    296   }
    297 
    298   HWND GetGridViewHWND() {
    299     return views::HWNDForView(grid_view_);
    300   }
    301 
    302   bool IsCursorWithinGridView() {
    303     POINT p;
    304     GetCursorPos(&p);
    305     return GetGridViewHWND() == WindowFromPoint(p);
    306   }
    307 
    308   gfx::Point GetCursorInGridViewCoords() {
    309     POINT p;
    310     GetCursorPos(&p);
    311     ScreenToClient(GetGridViewHWND(), &p);
    312     gfx::Point grid_view_pt(p.x, p.y);
    313     views::View::ConvertPointFromWidget(grid_view_, &grid_view_pt);
    314     return grid_view_pt;
    315   }
    316 
    317   AppsGridView* grid_view_;
    318   AppListItemView* drag_view_;
    319   gfx::Point drag_view_offset_;
    320   bool has_shortcut_path_;
    321   base::FilePath shortcut_path_;
    322   bool running_;
    323   bool canceled_;
    324 
    325   DISALLOW_COPY_AND_ASSIGN(SynchronousDrag);
    326 };
    327 #endif  // defined(OS_WIN)
    328 
    329 AppsGridView::AppsGridView(AppsGridViewDelegate* delegate,
    330                            PaginationModel* pagination_model,
    331                            content::WebContents* start_page_contents)
    332     : model_(NULL),
    333       item_list_(NULL),
    334       delegate_(delegate),
    335       pagination_model_(pagination_model),
    336       page_switcher_view_(new PageSwitcher(pagination_model)),
    337       start_page_view_(NULL),
    338       cols_(0),
    339       rows_per_page_(0),
    340       selected_view_(NULL),
    341       drag_view_(NULL),
    342       drag_start_page_(-1),
    343       drag_pointer_(NONE),
    344       drop_attempt_(DROP_FOR_NONE),
    345       drag_and_drop_host_(NULL),
    346       forward_events_to_drag_and_drop_host_(false),
    347       page_flip_target_(-1),
    348       page_flip_delay_in_ms_(kPageFlipDelayInMs),
    349       bounds_animator_(this),
    350       is_root_level_(true) {
    351   pagination_model_->AddObserver(this);
    352   AddChildView(page_switcher_view_);
    353 
    354   if (start_page_contents) {
    355     start_page_view_ =
    356         new views::WebView(start_page_contents->GetBrowserContext());
    357     start_page_view_->SetWebContents(start_page_contents);
    358     AddChildView(start_page_view_);
    359     start_page_contents->GetWebUI()->CallJavascriptFunction(
    360         "appList.startPage.onAppListShown");
    361   }
    362 }
    363 
    364 AppsGridView::~AppsGridView() {
    365   // Coming here |drag_view_| should already be canceled since otherwise the
    366   // drag would disappear after the app list got animated away and closed,
    367   // which would look odd.
    368   DCHECK(!drag_view_);
    369   if (drag_view_)
    370     EndDrag(true);
    371 
    372   if (model_)
    373     model_->RemoveObserver(this);
    374   pagination_model_->RemoveObserver(this);
    375 
    376   if (item_list_)
    377     item_list_->RemoveObserver(this);
    378 
    379   if (start_page_view_) {
    380     start_page_view_->GetWebContents()->GetWebUI()->CallJavascriptFunction(
    381         "appList.startPage.onAppListHidden");
    382   }
    383 }
    384 
    385 void AppsGridView::SetLayout(int icon_size, int cols, int rows_per_page) {
    386   icon_size_.SetSize(icon_size, icon_size);
    387   cols_ = cols;
    388   rows_per_page_ = rows_per_page;
    389 
    390   set_border(views::Border::CreateEmptyBorder(kTopPadding,
    391                                               kLeftRightPadding,
    392                                               0,
    393                                               kLeftRightPadding));
    394 }
    395 
    396 void AppsGridView::SetModel(AppListModel* model) {
    397   if (model_)
    398     model_->RemoveObserver(this);
    399 
    400   model_ = model;
    401   if (model_)
    402     model_->AddObserver(this);
    403 
    404   Update();
    405 }
    406 
    407 void AppsGridView::SetItemList(AppListItemList* item_list) {
    408   if (item_list_)
    409     item_list_->RemoveObserver(this);
    410 
    411   item_list_ = item_list;
    412   item_list_->AddObserver(this);
    413   Update();
    414 }
    415 
    416 void AppsGridView::SetSelectedView(views::View* view) {
    417   if (IsSelectedView(view) || IsDraggedView(view))
    418     return;
    419 
    420   Index index = GetIndexOfView(view);
    421   if (IsValidIndex(index))
    422     SetSelectedItemByIndex(index);
    423 }
    424 
    425 void AppsGridView::ClearSelectedView(views::View* view) {
    426   if (view && IsSelectedView(view)) {
    427     selected_view_->SchedulePaint();
    428     selected_view_ = NULL;
    429   }
    430 }
    431 
    432 void AppsGridView::ClearAnySelectedView() {
    433   if (selected_view_) {
    434     selected_view_->SchedulePaint();
    435     selected_view_ = NULL;
    436   }
    437 }
    438 
    439 bool AppsGridView::IsSelectedView(const views::View* view) const {
    440   return selected_view_ == view;
    441 }
    442 
    443 void AppsGridView::EnsureViewVisible(const views::View* view) {
    444   if (pagination_model_->has_transition())
    445     return;
    446 
    447   Index index = GetIndexOfView(view);
    448   if (IsValidIndex(index))
    449     pagination_model_->SelectPage(index.page, false);
    450 }
    451 
    452 void AppsGridView::InitiateDrag(AppListItemView* view,
    453                                 Pointer pointer,
    454                                 const ui::LocatedEvent& event) {
    455   DCHECK(view);
    456   if (drag_view_ || pulsing_blocks_model_.view_size())
    457     return;
    458 
    459   drag_view_ = view;
    460   drag_view_offset_ = event.location();
    461   drag_start_page_ = pagination_model_->selected_page();
    462   ExtractDragLocation(event, &drag_start_grid_view_);
    463   drag_view_start_ = gfx::Point(drag_view_->x(), drag_view_->y());
    464 }
    465 
    466 void AppsGridView::OnGotShortcutPath(const base::FilePath& path) {
    467 #if defined(OS_WIN)
    468   // Drag may have ended before we get the shortcut path.
    469   if (!synchronous_drag_)
    470     return;
    471   // Setting the shortcut path here means the next time we hit UpdateDrag()
    472   // we'll enter the synchronous drag.
    473   // NOTE we don't Run() the drag here because that causes animations not to
    474   // update for some reason.
    475   synchronous_drag_->set_shortcut_path(path);
    476   DCHECK(synchronous_drag_->CanRun());
    477 #endif
    478 }
    479 
    480 void AppsGridView::StartSettingUpSynchronousDrag() {
    481 #if defined(OS_WIN)
    482   if (!delegate_)
    483     return;
    484 
    485   // Favor the drag and drop host over native win32 drag. For the Win8/ash
    486   // launcher we want to have ashes drag and drop over win32's.
    487   if (drag_and_drop_host_)
    488     return;
    489 
    490   delegate_->GetShortcutPathForApp(
    491       drag_view_->model()->id(),
    492       base::Bind(&AppsGridView::OnGotShortcutPath, base::Unretained(this)));
    493   synchronous_drag_ = new SynchronousDrag(this, drag_view_, drag_view_offset_);
    494 #endif
    495 }
    496 
    497 bool AppsGridView::RunSynchronousDrag() {
    498 #if defined(OS_WIN)
    499   if (synchronous_drag_ && synchronous_drag_->CanRun()) {
    500     synchronous_drag_->Run();
    501     synchronous_drag_ = NULL;
    502     return true;
    503   }
    504 #endif
    505   return false;
    506 }
    507 
    508 void AppsGridView::CleanUpSynchronousDrag() {
    509 #if defined(OS_WIN)
    510   synchronous_drag_ = NULL;
    511 #endif
    512 }
    513 
    514 void AppsGridView::UpdateDragFromItem(Pointer pointer,
    515                                       const ui::LocatedEvent& event) {
    516   DCHECK(drag_view_);
    517 
    518   gfx::Point drag_point_in_grid_view;
    519   ExtractDragLocation(event, &drag_point_in_grid_view);
    520   UpdateDrag(pointer, drag_point_in_grid_view);
    521   if (!dragging())
    522     return;
    523 
    524   // If a drag and drop host is provided, see if the drag operation needs to be
    525   // forwarded.
    526   gfx::Point location_in_screen = drag_point_in_grid_view;
    527   views::View::ConvertPointToScreen(this, &location_in_screen);
    528   DispatchDragEventToDragAndDropHost(location_in_screen);
    529   if (drag_and_drop_host_)
    530     drag_and_drop_host_->UpdateDragIconProxy(location_in_screen);
    531 }
    532 
    533 void AppsGridView::UpdateDrag(Pointer pointer, const gfx::Point& point) {
    534   // EndDrag was called before if |drag_view_| is NULL.
    535   if (!drag_view_)
    536     return;
    537 
    538   if (RunSynchronousDrag())
    539     return;
    540 
    541   gfx::Vector2d drag_vector(point - drag_start_grid_view_);
    542   if (!dragging() && ExceededDragThreshold(drag_vector)) {
    543     drag_pointer_ = pointer;
    544     // Move the view to the front so that it appears on top of other views.
    545     ReorderChildView(drag_view_, -1);
    546     bounds_animator_.StopAnimatingView(drag_view_);
    547     StartSettingUpSynchronousDrag();
    548     StartDragAndDropHostDrag(point);
    549   }
    550 
    551   if (drag_pointer_ != pointer)
    552     return;
    553 
    554   last_drag_point_ = point;
    555   const Index last_drop_target = drop_target_;
    556   DropAttempt last_drop_attempt = drop_attempt_;
    557   CalculateDropTarget(last_drag_point_, false);
    558 
    559   if (IsPointWithinDragBuffer(last_drag_point_))
    560     MaybeStartPageFlipTimer(last_drag_point_);
    561   else
    562     StopPageFlipTimer();
    563 
    564   gfx::Point page_switcher_point(last_drag_point_);
    565   views::View::ConvertPointToTarget(this, page_switcher_view_,
    566                                     &page_switcher_point);
    567   page_switcher_view_->UpdateUIForDragPoint(page_switcher_point);
    568 
    569   if (!EnableFolderDragDropUI()) {
    570     if (last_drop_target != drop_target_)
    571       AnimateToIdealBounds();
    572     drag_view_->SetPosition(drag_view_start_ + drag_vector);
    573     return;
    574   }
    575 
    576   // Update drag with folder UI enabled.
    577   if (last_drop_target != drop_target_ ||
    578       last_drop_attempt != drop_attempt_) {
    579     if (drop_attempt_ == DROP_FOR_REORDER) {
    580       folder_dropping_timer_.Stop();
    581       reorder_timer_.Start(FROM_HERE,
    582           base::TimeDelta::FromMilliseconds(kReorderDelay),
    583           this, &AppsGridView::OnReorderTimer);
    584     } else if (drop_attempt_ == DROP_FOR_FOLDER) {
    585       reorder_timer_.Stop();
    586       folder_dropping_timer_.Start(FROM_HERE,
    587           base::TimeDelta::FromMilliseconds(kFolderDroppingDelay),
    588           this, &AppsGridView::OnFolderDroppingTimer);
    589     }
    590 
    591     // Reset the previous drop target.
    592     SetAsFolderDroppingTarget(last_drop_target, false);
    593   }
    594 
    595   drag_view_->SetPosition(drag_view_start_ + drag_vector);
    596 }
    597 
    598 void AppsGridView::EndDrag(bool cancel) {
    599   // EndDrag was called before if |drag_view_| is NULL.
    600   if (!drag_view_)
    601     return;
    602   // Coming here a drag and drop was in progress.
    603   bool landed_in_drag_and_drop_host = forward_events_to_drag_and_drop_host_;
    604   if (forward_events_to_drag_and_drop_host_) {
    605     forward_events_to_drag_and_drop_host_ = false;
    606     drag_and_drop_host_->EndDrag(cancel);
    607   } else if (!cancel && dragging()) {
    608     CalculateDropTarget(last_drag_point_, true);
    609     if (IsValidIndex(drop_target_)) {
    610       if (!EnableFolderDragDropUI()) {
    611           MoveItemInModel(drag_view_, drop_target_);
    612       } else {
    613         if (drop_attempt_ == DROP_FOR_REORDER)
    614           MoveItemInModel(drag_view_, drop_target_);
    615         else if (drop_attempt_ == DROP_FOR_FOLDER)
    616           MoveItemToFolder(drag_view_, drop_target_);
    617       }
    618     }
    619   }
    620 
    621   if (drag_and_drop_host_) {
    622     // If we had a drag and drop proxy icon, we delete it and make the real
    623     // item visible again.
    624     drag_and_drop_host_->DestroyDragIconProxy();
    625     if (landed_in_drag_and_drop_host) {
    626       // Move the item directly to the target location, avoiding the "zip back"
    627       // animation if the user was pinning it to the shelf.
    628       int i = drop_target_.slot;
    629       gfx::Rect bounds = view_model_.ideal_bounds(i);
    630       drag_view_->SetBoundsRect(bounds);
    631     }
    632     // Fade in slowly if it landed in the shelf.
    633     SetViewHidden(drag_view_,
    634              false /* hide */,
    635              !landed_in_drag_and_drop_host /* animate */);
    636   }
    637 
    638   // The drag can be ended after the synchronous drag is created but before it
    639   // is Run().
    640   CleanUpSynchronousDrag();
    641 
    642   SetAsFolderDroppingTarget(drop_target_, false);
    643   drop_attempt_ = DROP_FOR_NONE;
    644   drag_pointer_ = NONE;
    645   drop_target_ = Index();
    646   drag_view_->OnDragEnded();
    647   drag_view_ = NULL;
    648   drag_start_grid_view_ = gfx::Point();
    649   drag_start_page_ = -1;
    650   drag_view_offset_ = gfx::Point();
    651   AnimateToIdealBounds();
    652 
    653   StopPageFlipTimer();
    654 }
    655 
    656 void AppsGridView::StopPageFlipTimer() {
    657   page_flip_timer_.Stop();
    658   page_flip_target_ = -1;
    659 }
    660 
    661 bool AppsGridView::IsDraggedView(const views::View* view) const {
    662   return drag_view_ == view;
    663 }
    664 
    665 void AppsGridView::SetDragAndDropHostOfCurrentAppList(
    666     ApplicationDragAndDropHost* drag_and_drop_host) {
    667   drag_and_drop_host_ = drag_and_drop_host;
    668 }
    669 
    670 void AppsGridView::Prerender(int page_index) {
    671   Layout();
    672   int start = std::max(0, (page_index - kPrerenderPages) * tiles_per_page());
    673   int end = std::min(view_model_.view_size(),
    674                      (page_index + 1 + kPrerenderPages) * tiles_per_page());
    675   for (int i = start; i < end; i++) {
    676     AppListItemView* v = static_cast<AppListItemView*>(view_model_.view_at(i));
    677     v->Prerender();
    678   }
    679 }
    680 
    681 gfx::Size AppsGridView::GetPreferredSize() {
    682   const gfx::Insets insets(GetInsets());
    683   const gfx::Size tile_size = gfx::Size(kPreferredTileWidth,
    684                                         kPreferredTileHeight);
    685   const int page_switcher_height =
    686       page_switcher_view_->GetPreferredSize().height();
    687   return gfx::Size(
    688       tile_size.width() * cols_ + insets.width(),
    689       tile_size.height() * rows_per_page_ +
    690           page_switcher_height + insets.height());
    691 }
    692 
    693 bool AppsGridView::GetDropFormats(
    694     int* formats,
    695     std::set<OSExchangeData::CustomFormat>* custom_formats) {
    696   // TODO(koz): Only accept a specific drag type for app shortcuts.
    697   *formats = OSExchangeData::FILE_NAME;
    698   return true;
    699 }
    700 
    701 bool AppsGridView::CanDrop(const OSExchangeData& data) {
    702   return true;
    703 }
    704 
    705 int AppsGridView::OnDragUpdated(const ui::DropTargetEvent& event) {
    706   return ui::DragDropTypes::DRAG_MOVE;
    707 }
    708 
    709 void AppsGridView::Layout() {
    710   if (bounds_animator_.IsAnimating())
    711     bounds_animator_.Cancel();
    712 
    713   CalculateIdealBounds();
    714   for (int i = 0; i < view_model_.view_size(); ++i) {
    715     views::View* view = view_model_.view_at(i);
    716     if (view != drag_view_)
    717       view->SetBoundsRect(view_model_.ideal_bounds(i));
    718   }
    719   views::ViewModelUtils::SetViewBoundsToIdealBounds(pulsing_blocks_model_);
    720 
    721   const int page_switcher_height =
    722       page_switcher_view_->GetPreferredSize().height();
    723   gfx::Rect rect(GetContentsBounds());
    724   rect.set_y(rect.bottom() - page_switcher_height);
    725   rect.set_height(page_switcher_height);
    726   page_switcher_view_->SetBoundsRect(rect);
    727 
    728   LayoutStartPage();
    729 }
    730 
    731 bool AppsGridView::OnKeyPressed(const ui::KeyEvent& event) {
    732   bool handled = false;
    733   if (selected_view_)
    734     handled = selected_view_->OnKeyPressed(event);
    735 
    736   if (!handled) {
    737     const int forward_dir = base::i18n::IsRTL() ? -1 : 1;
    738     switch (event.key_code()) {
    739       case ui::VKEY_LEFT:
    740         MoveSelected(0, -forward_dir, 0);
    741         return true;
    742       case ui::VKEY_RIGHT:
    743         MoveSelected(0, forward_dir, 0);
    744         return true;
    745       case ui::VKEY_UP:
    746         MoveSelected(0, 0, -1);
    747         return true;
    748       case ui::VKEY_DOWN:
    749         MoveSelected(0, 0, 1);
    750         return true;
    751       case ui::VKEY_PRIOR: {
    752         MoveSelected(-1, 0, 0);
    753         return true;
    754       }
    755       case ui::VKEY_NEXT: {
    756         MoveSelected(1, 0, 0);
    757         return true;
    758       }
    759       default:
    760         break;
    761     }
    762   }
    763 
    764   return handled;
    765 }
    766 
    767 bool AppsGridView::OnKeyReleased(const ui::KeyEvent& event) {
    768   bool handled = false;
    769   if (selected_view_)
    770     handled = selected_view_->OnKeyReleased(event);
    771 
    772   return handled;
    773 }
    774 
    775 void AppsGridView::ViewHierarchyChanged(
    776     const ViewHierarchyChangedDetails& details) {
    777   if (!details.is_add && details.parent == this) {
    778     if (selected_view_ == details.child)
    779       selected_view_ = NULL;
    780 
    781     if (drag_view_ == details.child)
    782       EndDrag(true);
    783 
    784     bounds_animator_.StopAnimatingView(details.child);
    785   }
    786 }
    787 
    788 void AppsGridView::Update() {
    789   DCHECK(!selected_view_ && !drag_view_);
    790   if (!item_list_)
    791     return;
    792 
    793   view_model_.Clear();
    794   if (!item_list_->item_count())
    795     return;
    796   for (size_t i = 0; i < item_list_->item_count(); ++i) {
    797     views::View* view = CreateViewForItemAtIndex(i);
    798     view_model_.Add(view, i);
    799     AddChildView(view);
    800   }
    801   UpdatePaging();
    802   UpdatePulsingBlockViews();
    803   Layout();
    804   SchedulePaint();
    805 }
    806 
    807 void AppsGridView::UpdatePaging() {
    808   int total_page = start_page_view_ ? 1 : 0;
    809   if (view_model_.view_size() && tiles_per_page())
    810     total_page += (view_model_.view_size() - 1) / tiles_per_page() + 1;
    811 
    812   pagination_model_->SetTotalPages(total_page);
    813 }
    814 
    815 void AppsGridView::UpdatePulsingBlockViews() {
    816   const int existing_items = item_list_ ? item_list_->item_count() : 0;
    817   const int available_slots =
    818       tiles_per_page() - existing_items % tiles_per_page();
    819   const int desired = model_->status() == AppListModel::STATUS_SYNCING ?
    820       available_slots : 0;
    821 
    822   if (pulsing_blocks_model_.view_size() == desired)
    823     return;
    824 
    825   while (pulsing_blocks_model_.view_size() > desired) {
    826     views::View* view = pulsing_blocks_model_.view_at(0);
    827     pulsing_blocks_model_.Remove(0);
    828     delete view;
    829   }
    830 
    831   while (pulsing_blocks_model_.view_size() < desired) {
    832     views::View* view = new PulsingBlockView(
    833         gfx::Size(kPreferredTileWidth, kPreferredTileHeight), true);
    834     pulsing_blocks_model_.Add(view, 0);
    835     AddChildView(view);
    836   }
    837 }
    838 
    839 views::View* AppsGridView::CreateViewForItemAtIndex(size_t index) {
    840   // The drag_view_ might be pending for deletion, therefore view_model_
    841   // may have one more item than item_list_.
    842   DCHECK_LE(index, item_list_->item_count());
    843   AppListItemView* view = new AppListItemView(this,
    844                                               item_list_->item_at(index));
    845   view->SetIconSize(icon_size_);
    846 #if defined(USE_AURA)
    847   view->SetPaintToLayer(true);
    848   view->SetFillsBoundsOpaquely(false);
    849 #endif
    850   return view;
    851 }
    852 
    853 AppsGridView::Index AppsGridView::GetIndexFromModelIndex(
    854     int model_index) const {
    855   int page = model_index / tiles_per_page();
    856   if (start_page_view_)
    857     ++page;
    858 
    859   return Index(page, model_index % tiles_per_page());
    860 }
    861 
    862 int AppsGridView::GetModelIndexFromIndex(const Index& index) const {
    863   int model_index = index.page * tiles_per_page() + index.slot;
    864   if (start_page_view_)
    865     model_index -= tiles_per_page();
    866 
    867   return model_index;
    868 }
    869 
    870 void AppsGridView::SetSelectedItemByIndex(const Index& index) {
    871   if (GetIndexOfView(selected_view_) == index)
    872     return;
    873 
    874   views::View* new_selection = GetViewAtIndex(index);
    875   if (!new_selection)
    876     return;  // Keep current selection.
    877 
    878   if (selected_view_)
    879     selected_view_->SchedulePaint();
    880 
    881   EnsureViewVisible(new_selection);
    882   selected_view_ = new_selection;
    883   selected_view_->SchedulePaint();
    884   selected_view_->NotifyAccessibilityEvent(
    885       ui::AccessibilityTypes::EVENT_FOCUS, true);
    886 }
    887 
    888 bool AppsGridView::IsValidIndex(const Index& index) const {
    889   const int item_page_start = start_page_view_ ? 1 : 0;
    890   return index.page >= item_page_start &&
    891          index.page < pagination_model_->total_pages() &&
    892          index.slot >= 0 &&
    893          index.slot < tiles_per_page() &&
    894          GetModelIndexFromIndex(index) < view_model_.view_size();
    895 }
    896 
    897 AppsGridView::Index AppsGridView::GetIndexOfView(
    898     const views::View* view) const {
    899   const int model_index = view_model_.GetIndexOfView(view);
    900   if (model_index == -1)
    901     return Index();
    902 
    903   return GetIndexFromModelIndex(model_index);
    904 }
    905 
    906 views::View* AppsGridView::GetViewAtIndex(const Index& index) const {
    907   if (!IsValidIndex(index))
    908     return NULL;
    909 
    910   const int model_index = GetModelIndexFromIndex(index);
    911   return view_model_.view_at(model_index);
    912 }
    913 
    914 void AppsGridView::MoveSelected(int page_delta,
    915                                 int slot_x_delta,
    916                                 int slot_y_delta) {
    917   if (!selected_view_)
    918     return SetSelectedItemByIndex(Index(pagination_model_->selected_page(), 0));
    919 
    920   const Index& selected = GetIndexOfView(selected_view_);
    921   int target_slot = selected.slot + slot_x_delta + slot_y_delta * cols_;
    922 
    923   if (selected.slot % cols_ == 0 && slot_x_delta == -1) {
    924     if (selected.page > 0) {
    925       page_delta = -1;
    926       target_slot = selected.slot + cols_ - 1;
    927     } else {
    928       target_slot = selected.slot;
    929     }
    930   }
    931 
    932   if (selected.slot % cols_ == cols_ - 1 && slot_x_delta == 1) {
    933     if (selected.page < pagination_model_->total_pages() - 1) {
    934       page_delta = 1;
    935       target_slot = selected.slot - cols_ + 1;
    936     } else {
    937       target_slot = selected.slot;
    938     }
    939   }
    940 
    941   // Clamp the target slot to the last item if we are moving to the last page
    942   // but our target slot is past the end of the item list.
    943   if (page_delta &&
    944       selected.page + page_delta == pagination_model_->total_pages() - 1) {
    945     int last_item_slot = (view_model_.view_size() - 1) % tiles_per_page();
    946     if (last_item_slot < target_slot) {
    947       target_slot = last_item_slot;
    948     }
    949   }
    950 
    951   int target_page = std::min(pagination_model_->total_pages() - 1,
    952                              std::max(selected.page + page_delta, 0));
    953   SetSelectedItemByIndex(Index(target_page, target_slot));
    954 }
    955 
    956 void AppsGridView::CalculateIdealBounds() {
    957   gfx::Rect rect(GetContentsBounds());
    958   if (rect.IsEmpty())
    959     return;
    960 
    961   gfx::Size tile_size(kPreferredTileWidth, kPreferredTileHeight);
    962 
    963   gfx::Rect grid_rect(gfx::Size(tile_size.width() * cols_,
    964                                 tile_size.height() * rows_per_page_));
    965   grid_rect.Intersect(rect);
    966 
    967   // Page width including padding pixels. A tile.x + page_width means the same
    968   // tile slot in the next page.
    969   const int page_width = grid_rect.width() + kPagePadding;
    970 
    971   // If there is a transition, calculates offset for current and target page.
    972   const int current_page = pagination_model_->selected_page();
    973   const PaginationModel::Transition& transition =
    974       pagination_model_->transition();
    975   const bool is_valid =
    976       pagination_model_->is_valid_page(transition.target_page);
    977 
    978   // Transition to right means negative offset.
    979   const int dir = transition.target_page > current_page ? -1 : 1;
    980   const int transition_offset = is_valid ?
    981       transition.progress * page_width * dir : 0;
    982 
    983   const int total_views =
    984       view_model_.view_size() + pulsing_blocks_model_.view_size();
    985   int slot_index = 0;
    986   for (int i = 0; i < total_views; ++i) {
    987     if (i < view_model_.view_size() && view_model_.view_at(i) == drag_view_) {
    988       if (EnableFolderDragDropUI() && drop_attempt_ == DROP_FOR_FOLDER)
    989         ++slot_index;
    990       continue;
    991     }
    992 
    993     Index view_index = GetIndexFromModelIndex(slot_index);
    994 
    995     if (drop_target_ == view_index) {
    996       if (EnableFolderDragDropUI() && drop_attempt_ == DROP_FOR_FOLDER) {
    997         view_index = GetIndexFromModelIndex(slot_index);
    998       } else {
    999         ++slot_index;
   1000         view_index = GetIndexFromModelIndex(slot_index);
   1001       }
   1002     }
   1003 
   1004     // Decides an x_offset for current item.
   1005     int x_offset = 0;
   1006     if (view_index.page < current_page)
   1007       x_offset = -page_width;
   1008     else if (view_index.page > current_page)
   1009       x_offset = page_width;
   1010 
   1011     if (is_valid) {
   1012       if (view_index.page == current_page ||
   1013           view_index.page == transition.target_page) {
   1014         x_offset += transition_offset;
   1015       }
   1016     }
   1017 
   1018     const int row = view_index.slot / cols_;
   1019     const int col = view_index.slot % cols_;
   1020     gfx::Rect tile_slot(
   1021         gfx::Point(grid_rect.x() + col * tile_size.width() + x_offset,
   1022                    grid_rect.y() + row * tile_size.height()),
   1023         tile_size);
   1024     if (i < view_model_.view_size()) {
   1025       view_model_.set_ideal_bounds(i, tile_slot);
   1026     } else {
   1027       pulsing_blocks_model_.set_ideal_bounds(i - view_model_.view_size(),
   1028                                              tile_slot);
   1029     }
   1030 
   1031     ++slot_index;
   1032   }
   1033 }
   1034 
   1035 void AppsGridView::AnimateToIdealBounds() {
   1036   const gfx::Rect visible_bounds(GetVisibleBounds());
   1037 
   1038   CalculateIdealBounds();
   1039   for (int i = 0; i < view_model_.view_size(); ++i) {
   1040     views::View* view = view_model_.view_at(i);
   1041     if (view == drag_view_)
   1042       continue;
   1043 
   1044     const gfx::Rect& target = view_model_.ideal_bounds(i);
   1045     if (bounds_animator_.GetTargetBounds(view) == target)
   1046       continue;
   1047 
   1048     const gfx::Rect& current = view->bounds();
   1049     const bool current_visible = visible_bounds.Intersects(current);
   1050     const bool target_visible = visible_bounds.Intersects(target);
   1051     const bool visible = current_visible || target_visible;
   1052 
   1053     const int y_diff = target.y() - current.y();
   1054     if (visible && y_diff && y_diff % kPreferredTileHeight == 0) {
   1055       AnimationBetweenRows(view,
   1056                            current_visible,
   1057                            current,
   1058                            target_visible,
   1059                            target);
   1060     } else {
   1061       bounds_animator_.AnimateViewTo(view, target);
   1062     }
   1063   }
   1064 }
   1065 
   1066 void AppsGridView::AnimationBetweenRows(views::View* view,
   1067                                         bool animate_current,
   1068                                         const gfx::Rect& current,
   1069                                         bool animate_target,
   1070                                         const gfx::Rect& target) {
   1071   // Determine page of |current| and |target|. -1 means in the left invisible
   1072   // page, 0 is the center visible page and 1 means in the right invisible page.
   1073   const int current_page = current.x() < 0 ? -1 :
   1074       current.x() >= width() ? 1 : 0;
   1075   const int target_page = target.x() < 0 ? -1 :
   1076       target.x() >= width() ? 1 : 0;
   1077 
   1078   const int dir = current_page < target_page ||
   1079       (current_page == target_page && current.y() < target.y()) ? 1 : -1;
   1080 
   1081 #if defined(USE_AURA)
   1082   scoped_ptr<ui::Layer> layer;
   1083   if (animate_current) {
   1084     layer.reset(view->RecreateLayer());
   1085     layer->SuppressPaint();
   1086 
   1087     view->SetFillsBoundsOpaquely(false);
   1088     view->layer()->SetOpacity(0.f);
   1089   }
   1090 
   1091   gfx::Rect current_out(current);
   1092   current_out.Offset(dir * kPreferredTileWidth, 0);
   1093 #endif
   1094 
   1095   gfx::Rect target_in(target);
   1096   if (animate_target)
   1097     target_in.Offset(-dir * kPreferredTileWidth, 0);
   1098   view->SetBoundsRect(target_in);
   1099   bounds_animator_.AnimateViewTo(view, target);
   1100 
   1101 #if defined(USE_AURA)
   1102   bounds_animator_.SetAnimationDelegate(
   1103       view,
   1104       new RowMoveAnimationDelegate(view, layer.release(), current_out),
   1105       true);
   1106 #endif
   1107 }
   1108 
   1109 void AppsGridView::ExtractDragLocation(const ui::LocatedEvent& event,
   1110                                        gfx::Point* drag_point) {
   1111 #if defined(USE_AURA) && !defined(OS_WIN)
   1112   // Use root location of |event| instead of location in |drag_view_|'s
   1113   // coordinates because |drag_view_| has a scale transform and location
   1114   // could have integer round error and causes jitter.
   1115   *drag_point = event.root_location();
   1116 
   1117   // GetWidget() could be NULL for tests.
   1118   if (GetWidget()) {
   1119     aura::Window::ConvertPointToTarget(
   1120         GetWidget()->GetNativeWindow()->GetRootWindow(),
   1121         GetWidget()->GetNativeWindow(),
   1122         drag_point);
   1123   }
   1124 
   1125   views::View::ConvertPointFromWidget(this, drag_point);
   1126 #else
   1127   // For non-aura, root location is not clearly defined but |drag_view_| does
   1128   // not have the scale transform. So no round error would be introduced and
   1129   // it's okay to use View::ConvertPointToTarget.
   1130   *drag_point = event.location();
   1131   views::View::ConvertPointToTarget(drag_view_, this, drag_point);
   1132 #endif
   1133 }
   1134 
   1135 void AppsGridView::CalculateDropTarget(const gfx::Point& drag_point,
   1136                                        bool use_page_button_hovering) {
   1137   if (EnableFolderDragDropUI()) {
   1138     CalculateDropTargetWithFolderEnabled(drag_point, use_page_button_hovering);
   1139     return;
   1140   }
   1141 
   1142   int current_page = pagination_model_->selected_page();
   1143   gfx::Point point(drag_point);
   1144   if (!IsPointWithinDragBuffer(drag_point)) {
   1145     point = drag_start_grid_view_;
   1146     current_page = drag_start_page_;
   1147   }
   1148 
   1149   if (use_page_button_hovering &&
   1150       page_switcher_view_->bounds().Contains(point)) {
   1151     gfx::Point page_switcher_point(point);
   1152     views::View::ConvertPointToTarget(this, page_switcher_view_,
   1153                                       &page_switcher_point);
   1154     int page = page_switcher_view_->GetPageForPoint(page_switcher_point);
   1155     if (pagination_model_->is_valid_page(page)) {
   1156       drop_target_.page = page;
   1157       drop_target_.slot = tiles_per_page() - 1;
   1158     }
   1159   } else {
   1160     gfx::Rect bounds(GetContentsBounds());
   1161     const int drop_row = (point.y() - bounds.y()) / kPreferredTileHeight;
   1162     const int drop_col = std::min(cols_ - 1,
   1163         (point.x() - bounds.x()) / kPreferredTileWidth);
   1164 
   1165     drop_target_.page = current_page;
   1166     drop_target_.slot = std::max(0, std::min(
   1167         tiles_per_page() - 1,
   1168         drop_row * cols_ + drop_col));
   1169   }
   1170 
   1171   // Limits to the last possible slot on last page.
   1172   if (drop_target_.page == pagination_model_->total_pages() - 1) {
   1173     drop_target_.slot = std::min(
   1174         (view_model_.view_size() - 1) % tiles_per_page(),
   1175         drop_target_.slot);
   1176   }
   1177 }
   1178 
   1179 
   1180 void AppsGridView::CalculateDropTargetWithFolderEnabled(
   1181     const gfx::Point& drag_point,
   1182     bool use_page_button_hovering) {
   1183   gfx::Point point(drag_point);
   1184   if (!IsPointWithinDragBuffer(drag_point)) {
   1185     point = drag_start_grid_view_;
   1186   }
   1187 
   1188   if (use_page_button_hovering &&
   1189       page_switcher_view_->bounds().Contains(point)) {
   1190     gfx::Point page_switcher_point(point);
   1191     views::View::ConvertPointToTarget(this, page_switcher_view_,
   1192                                       &page_switcher_point);
   1193     int page = page_switcher_view_->GetPageForPoint(page_switcher_point);
   1194     if (pagination_model_->is_valid_page(page)) {
   1195       drop_target_.page = page;
   1196       drop_target_.slot = tiles_per_page() - 1;
   1197     }
   1198     if (drop_target_.page == pagination_model_->total_pages() - 1) {
   1199       drop_target_.slot = std::min(
   1200           (view_model_.view_size() - 1) % tiles_per_page(),
   1201           drop_target_.slot);
   1202     }
   1203     drop_attempt_ = DROP_FOR_REORDER;
   1204   } else {
   1205     DCHECK(drag_view_);
   1206     // Try to find the nearest target for folder dropping or re-ordering.
   1207     drop_target_ = GetNearestTileForDragView();
   1208   }
   1209 }
   1210 
   1211 void AppsGridView::OnReorderTimer() {
   1212   if (drop_attempt_ == DROP_FOR_REORDER)
   1213     AnimateToIdealBounds();
   1214 }
   1215 
   1216 void AppsGridView::OnFolderDroppingTimer() {
   1217   if (drop_attempt_ == DROP_FOR_FOLDER)
   1218     SetAsFolderDroppingTarget(drop_target_, true);
   1219 }
   1220 
   1221 void AppsGridView::StartDragAndDropHostDrag(const gfx::Point& grid_location) {
   1222   // When a drag and drop host is given, the item can be dragged out of the app
   1223   // list window. In that case a proxy widget needs to be used.
   1224   // Note: This code has very likely to be changed for Windows (non metro mode)
   1225   // when a |drag_and_drop_host_| gets implemented.
   1226   if (!drag_view_ || !drag_and_drop_host_)
   1227     return;
   1228 
   1229   gfx::Point screen_location = grid_location;
   1230   views::View::ConvertPointToScreen(this, &screen_location);
   1231 
   1232   // Determine the mouse offset to the center of the icon so that the drag and
   1233   // drop host follows this layer.
   1234   gfx::Vector2d delta = drag_view_offset_ -
   1235                         drag_view_->GetLocalBounds().CenterPoint();
   1236   delta.set_y(delta.y() + drag_view_->title()->size().height() / 2);
   1237 
   1238   // We have to hide the original item since the drag and drop host will do
   1239   // the OS dependent code to "lift off the dragged item".
   1240   drag_and_drop_host_->CreateDragIconProxy(screen_location,
   1241                                            drag_view_->model()->icon(),
   1242                                            drag_view_,
   1243                                            delta,
   1244                                            kDragAndDropProxyScale);
   1245   SetViewHidden(drag_view_,
   1246            true /* hide */,
   1247            true /* no animation */);
   1248 }
   1249 
   1250 void AppsGridView::DispatchDragEventToDragAndDropHost(
   1251     const gfx::Point& location_in_screen_coordinates) {
   1252   if (!drag_view_ || !drag_and_drop_host_)
   1253     return;
   1254   if (bounds().Contains(last_drag_point_)) {
   1255     // The event was issued inside the app menu and we should get all events.
   1256     if (forward_events_to_drag_and_drop_host_) {
   1257       // The DnD host was previously called and needs to be informed that the
   1258       // session returns to the owner.
   1259       forward_events_to_drag_and_drop_host_ = false;
   1260       drag_and_drop_host_->EndDrag(true);
   1261     }
   1262   } else {
   1263     // The event happened outside our app menu and we might need to dispatch.
   1264     if (forward_events_to_drag_and_drop_host_) {
   1265       // Dispatch since we have already started.
   1266       if (!drag_and_drop_host_->Drag(location_in_screen_coordinates)) {
   1267         // The host is not active any longer and we cancel the operation.
   1268         forward_events_to_drag_and_drop_host_ = false;
   1269         drag_and_drop_host_->EndDrag(true);
   1270       }
   1271     } else {
   1272       if (drag_and_drop_host_->StartDrag(drag_view_->model()->id(),
   1273                                          location_in_screen_coordinates)) {
   1274         // From now on we forward the drag events.
   1275         forward_events_to_drag_and_drop_host_ = true;
   1276         // Any flip operations are stopped.
   1277         StopPageFlipTimer();
   1278       }
   1279     }
   1280   }
   1281 }
   1282 
   1283 void AppsGridView::MaybeStartPageFlipTimer(const gfx::Point& drag_point) {
   1284   if (!IsPointWithinDragBuffer(drag_point))
   1285     StopPageFlipTimer();
   1286   int new_page_flip_target = -1;
   1287 
   1288   if (page_switcher_view_->bounds().Contains(drag_point)) {
   1289     gfx::Point page_switcher_point(drag_point);
   1290     views::View::ConvertPointToTarget(this, page_switcher_view_,
   1291                                       &page_switcher_point);
   1292     new_page_flip_target =
   1293         page_switcher_view_->GetPageForPoint(page_switcher_point);
   1294   }
   1295 
   1296   // TODO(xiyuan): Fix this for RTL.
   1297   if (new_page_flip_target == -1 && drag_point.x() < kPageFlipZoneSize)
   1298     new_page_flip_target = pagination_model_->selected_page() - 1;
   1299 
   1300   if (new_page_flip_target == -1 &&
   1301       drag_point.x() > width() - kPageFlipZoneSize) {
   1302     new_page_flip_target = pagination_model_->selected_page() + 1;
   1303   }
   1304 
   1305   if (new_page_flip_target == page_flip_target_)
   1306     return;
   1307 
   1308   StopPageFlipTimer();
   1309   if (pagination_model_->is_valid_page(new_page_flip_target)) {
   1310     page_flip_target_ = new_page_flip_target;
   1311 
   1312     if (page_flip_target_ != pagination_model_->selected_page()) {
   1313       page_flip_timer_.Start(FROM_HERE,
   1314           base::TimeDelta::FromMilliseconds(page_flip_delay_in_ms_),
   1315           this, &AppsGridView::OnPageFlipTimer);
   1316     }
   1317   }
   1318 }
   1319 
   1320 void AppsGridView::OnPageFlipTimer() {
   1321   DCHECK(pagination_model_->is_valid_page(page_flip_target_));
   1322   pagination_model_->SelectPage(page_flip_target_, true);
   1323 }
   1324 
   1325 void AppsGridView::MoveItemInModel(views::View* item_view,
   1326                                    const Index& target) {
   1327   int current_model_index = view_model_.GetIndexOfView(item_view);
   1328   DCHECK_GE(current_model_index, 0);
   1329 
   1330   int target_model_index = GetModelIndexFromIndex(target);
   1331   if (target_model_index == current_model_index)
   1332     return;
   1333 
   1334   item_list_->RemoveObserver(this);
   1335   item_list_->MoveItem(current_model_index, target_model_index);
   1336   view_model_.Move(current_model_index, target_model_index);
   1337   item_list_->AddObserver(this);
   1338 
   1339   if (pagination_model_->selected_page() != target.page)
   1340     pagination_model_->SelectPage(target.page, false);
   1341 }
   1342 
   1343 void AppsGridView::MoveItemToFolder(views::View* item_view,
   1344                                     const Index& target) {
   1345   AppListItemModel* source_item =
   1346       static_cast<AppListItemView*>(item_view)->model();
   1347   AppListItemView* target_view =
   1348       static_cast<AppListItemView*>(GetViewAtSlotOnCurrentPage(target.slot));
   1349   AppListItemModel* target_item = target_view->model();
   1350   bool target_is_folder = IsFolderItem(target_item);
   1351 
   1352   // Make change to data model.
   1353   item_list_->RemoveObserver(this);
   1354   int folder_index = MergeItems(item_list_, target_item->id(), source_item);
   1355   item_list_->AddObserver(this);
   1356 
   1357   if (!target_is_folder) {
   1358     // Change view_model_ to replace the old target view with new folder
   1359     // item view.
   1360     int target_index = view_model_.GetIndexOfView(target_view);
   1361     view_model_.Remove(target_index);
   1362     delete target_view;
   1363 
   1364     views::View* target_folder_view = CreateViewForItemAtIndex(folder_index);
   1365     view_model_.Add(target_folder_view, target_index);
   1366     AddChildView(target_folder_view);
   1367   }
   1368 
   1369   // Fade out the drag_view_ and delete it when animation ends.
   1370   int drag_view_index = view_model_.GetIndexOfView(drag_view_);
   1371   view_model_.Remove(drag_view_index);
   1372   bounds_animator_.AnimateViewTo(drag_view_, drag_view_->bounds());
   1373   bounds_animator_.SetAnimationDelegate(
   1374       drag_view_, new ItemRemoveAnimationDelegate(drag_view_), true);
   1375 
   1376   UpdatePaging();
   1377 }
   1378 
   1379 void AppsGridView::CancelContextMenusOnCurrentPage() {
   1380   int start = pagination_model_->selected_page() * tiles_per_page();
   1381   int end = std::min(view_model_.view_size(), start + tiles_per_page());
   1382   for (int i = start; i < end; ++i) {
   1383     AppListItemView* view =
   1384         static_cast<AppListItemView*>(view_model_.view_at(i));
   1385     view->CancelContextMenu();
   1386   }
   1387 }
   1388 
   1389 bool AppsGridView::IsPointWithinDragBuffer(const gfx::Point& point) const {
   1390   gfx::Rect rect(GetLocalBounds());
   1391   rect.Inset(-kDragBufferPx, -kDragBufferPx, -kDragBufferPx, -kDragBufferPx);
   1392   return rect.Contains(point);
   1393 }
   1394 
   1395 void AppsGridView::ButtonPressed(views::Button* sender,
   1396                                  const ui::Event& event) {
   1397   if (dragging())
   1398     return;
   1399 
   1400   if (strcmp(sender->GetClassName(), AppListItemView::kViewClassName))
   1401     return;
   1402 
   1403   if (delegate_) {
   1404     delegate_->ActivateApp(static_cast<AppListItemView*>(sender)->model(),
   1405                            event.flags());
   1406   }
   1407 }
   1408 
   1409 void AppsGridView::LayoutStartPage() {
   1410   if (!start_page_view_)
   1411     return;
   1412 
   1413   gfx::Rect start_page_bounds(GetLocalBounds());
   1414   start_page_bounds.set_height(start_page_bounds.height() -
   1415                                page_switcher_view_->height());
   1416 
   1417   const int page_width = width() + kPagePadding;
   1418   const int current_page = pagination_model_->selected_page();
   1419   if (current_page > 0)
   1420     start_page_bounds.Offset(-page_width, 0);
   1421 
   1422   const PaginationModel::Transition& transition =
   1423       pagination_model_->transition();
   1424   if (current_page == 0 || transition.target_page == 0) {
   1425     const int dir = transition.target_page > current_page ? -1 : 1;
   1426     start_page_bounds.Offset(transition.progress * page_width * dir, 0);
   1427   }
   1428 
   1429   start_page_view_->SetBoundsRect(start_page_bounds);
   1430 }
   1431 
   1432 void AppsGridView::OnListItemAdded(size_t index, AppListItemModel* item) {
   1433   EndDrag(true);
   1434 
   1435   views::View* view = CreateViewForItemAtIndex(index);
   1436   view_model_.Add(view, index);
   1437   AddChildView(view);
   1438 
   1439   UpdatePaging();
   1440   UpdatePulsingBlockViews();
   1441   Layout();
   1442   SchedulePaint();
   1443 }
   1444 
   1445 void AppsGridView::OnListItemRemoved(size_t index, AppListItemModel* item) {
   1446   EndDrag(true);
   1447 
   1448   views::View* view = view_model_.view_at(index);
   1449   view_model_.Remove(index);
   1450   delete view;
   1451 
   1452   UpdatePaging();
   1453   UpdatePulsingBlockViews();
   1454   Layout();
   1455   SchedulePaint();
   1456 }
   1457 
   1458 void AppsGridView::OnListItemMoved(size_t from_index,
   1459                                    size_t to_index,
   1460                                    AppListItemModel* item) {
   1461   EndDrag(true);
   1462   view_model_.Move(from_index, to_index);
   1463 
   1464   UpdatePaging();
   1465   AnimateToIdealBounds();
   1466 }
   1467 
   1468 void AppsGridView::TotalPagesChanged() {
   1469 }
   1470 
   1471 void AppsGridView::SelectedPageChanged(int old_selected, int new_selected) {
   1472   if (dragging()) {
   1473     CalculateDropTarget(last_drag_point_, true);
   1474     Layout();
   1475     MaybeStartPageFlipTimer(last_drag_point_);
   1476   } else {
   1477     ClearSelectedView(selected_view_);
   1478     Layout();
   1479   }
   1480 }
   1481 
   1482 void AppsGridView::TransitionStarted() {
   1483   CancelContextMenusOnCurrentPage();
   1484 }
   1485 
   1486 void AppsGridView::TransitionChanged() {
   1487   // Update layout for valid page transition only since over-scroll no longer
   1488   // animates app icons.
   1489   const PaginationModel::Transition& transition =
   1490       pagination_model_->transition();
   1491   if (pagination_model_->is_valid_page(transition.target_page))
   1492     Layout();
   1493 }
   1494 
   1495 void AppsGridView::OnAppListModelStatusChanged() {
   1496   UpdatePulsingBlockViews();
   1497   Layout();
   1498   SchedulePaint();
   1499 }
   1500 
   1501 void AppsGridView::SetViewHidden(views::View* view, bool hide, bool immediate) {
   1502 #if defined(USE_AURA)
   1503   ui::ScopedLayerAnimationSettings animator(view->layer()->GetAnimator());
   1504   animator.SetPreemptionStrategy(
   1505       immediate ? ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET :
   1506                   ui::LayerAnimator::BLEND_WITH_CURRENT_ANIMATION);
   1507   view->layer()->SetOpacity(hide ? 0 : 1);
   1508 #endif
   1509 }
   1510 
   1511 bool AppsGridView::EnableFolderDragDropUI() {
   1512   // Enable drag and drop folder UI only if it is at the app list root level
   1513   // and the switch is on and the target folder can still accept new items.
   1514   return switches::IsFolderUIEnabled() && is_root_level_ &&
   1515       CanDropIntoTarget(drop_target_);
   1516 }
   1517 
   1518 bool AppsGridView::CanDropIntoTarget(const Index& drop_target) {
   1519   views::View* target_view = GetViewAtSlotOnCurrentPage(drop_target.slot);
   1520   if (!target_view)
   1521     return true;
   1522 
   1523   AppListItemModel* target_item =
   1524       static_cast<AppListItemView*>(target_view)->model();
   1525   if (!IsFolderItem(target_item))
   1526     return true;
   1527 
   1528   return static_cast<AppListFolderItem*>(target_item)->item_list()->
   1529       item_count() < kMaxFolderItems;
   1530 }
   1531 
   1532 // TODO(jennyz): Optimize the calculation for finding nearest tile.
   1533 AppsGridView::Index AppsGridView::GetNearestTileForDragView() {
   1534   Index nearest_tile;
   1535   nearest_tile.page = -1;
   1536   nearest_tile.slot = -1;
   1537   int d_min = -1;
   1538 
   1539   // Calculate the top left tile |drag_view| intersects.
   1540   gfx::Point pt = drag_view_->bounds().origin();
   1541   CalculateNearestTileForVertex(pt, &nearest_tile, &d_min);
   1542 
   1543   // Calculate the top right tile |drag_view| intersects.
   1544   pt = drag_view_->bounds().top_right();
   1545   CalculateNearestTileForVertex(pt, &nearest_tile, &d_min);
   1546 
   1547   // Calculate the bottom left tile |drag_view| intersects.
   1548   pt = drag_view_->bounds().bottom_left();
   1549   CalculateNearestTileForVertex(pt, &nearest_tile, &d_min);
   1550 
   1551   // Calculate the bottom right tile |drag_view| intersects.
   1552   pt = drag_view_->bounds().bottom_right();
   1553   CalculateNearestTileForVertex(pt, &nearest_tile, &d_min);
   1554 
   1555   const int d_folder_dropping =
   1556       kFolderDroppingCircleRadius + kPreferredIconDimension / 2;
   1557   const int d_reorder =
   1558       kReorderDroppingCircleRadius + kPreferredIconDimension / 2;
   1559 
   1560   if (IsValidIndex(nearest_tile)) {
   1561     if (d_min < d_folder_dropping) {
   1562       views::View* target_view = GetViewAtSlotOnCurrentPage(nearest_tile.slot);
   1563       if (target_view &&
   1564           !IsFolderItem(static_cast<AppListItemView*>(drag_view_)->model())) {
   1565         // If a non-folder item is dragged to the target slot with an item
   1566         // sitting on it, attempt to drop the dragged item into the folder
   1567         // containing the item on nearest_tile.
   1568         drop_attempt_ = DROP_FOR_FOLDER;
   1569         return nearest_tile;
   1570       } else {
   1571         // If the target slot is blank, or the dragged item is a folder, attempt
   1572         // to re-order.
   1573         drop_attempt_ = DROP_FOR_REORDER;
   1574         return nearest_tile;
   1575       }
   1576     } else if (d_min < d_reorder) {
   1577       // Entering the re-order circle of the slot.
   1578       drop_attempt_ = DROP_FOR_REORDER;
   1579       return nearest_tile;
   1580     }
   1581   }
   1582 
   1583   // If |drag_view| is not entering the re-order or fold dropping region of
   1584   // any items, cancel any previous re-order or folder dropping timer, and
   1585   // return itself.
   1586   drop_attempt_ = DROP_FOR_NONE;
   1587   reorder_timer_.Stop();
   1588   folder_dropping_timer_.Stop();
   1589   return GetIndexOfView(drag_view_);
   1590 }
   1591 
   1592 void AppsGridView::CalculateNearestTileForVertex(const gfx::Point& vertex,
   1593                                                 Index* nearest_tile,
   1594                                                 int* d_min) {
   1595   Index target_index;
   1596   gfx::Rect target_bounds = GetTileBoundsForPoint(vertex, &target_index);
   1597 
   1598   if (target_bounds.IsEmpty() || target_index == *nearest_tile)
   1599     return;
   1600 
   1601   int d_center = GetDistanceBetweenRects(drag_view_->bounds(), target_bounds);
   1602   if (*d_min < 0 || d_center < *d_min) {
   1603     *d_min = d_center;
   1604     *nearest_tile = target_index;
   1605   }
   1606 }
   1607 
   1608 gfx::Rect AppsGridView::GetTileBoundsForPoint(const gfx::Point& point,
   1609                                               Index *tile_index) {
   1610   // Check if |point| is outside of contents bounds.
   1611   gfx::Rect bounds(GetContentsBounds());
   1612   if (!bounds.Contains(point))
   1613     return gfx::Rect();
   1614 
   1615   // Calculate which tile |point| is enclosed in.
   1616   int x = point.x();
   1617   int y = point.y();
   1618   int col = (x - bounds.x()) / kPreferredTileWidth;
   1619   int row = (y - bounds.y()) / kPreferredTileHeight;
   1620   gfx::Rect tile_rect = GetTileBounds(row, col);
   1621 
   1622   // Check if |point| is outside a valid item's tile.
   1623   Index index(pagination_model_->selected_page(), row * cols_ + col);
   1624   if (!IsValidIndex(index))
   1625     return gfx::Rect();
   1626 
   1627   // |point| is inside of the valid item's tile.
   1628   *tile_index = index;
   1629   return tile_rect;
   1630 }
   1631 
   1632 gfx::Rect AppsGridView::GetTileBounds(int row, int col) const {
   1633   gfx::Rect bounds(GetContentsBounds());
   1634   gfx::Size tile_size(kPreferredTileWidth, kPreferredTileHeight);
   1635   gfx::Rect grid_rect(gfx::Size(tile_size.width() * cols_,
   1636                                 tile_size.height() * rows_per_page_));
   1637   grid_rect.Intersect(bounds);
   1638   gfx::Rect tile_rect(
   1639       gfx::Point(grid_rect.x() + col * tile_size.width(),
   1640                  grid_rect.y() + row * tile_size.height()),
   1641       tile_size);
   1642   return tile_rect;
   1643 }
   1644 
   1645 views::View* AppsGridView::GetViewAtSlotOnCurrentPage(int slot) {
   1646   if (slot < 0)
   1647     return NULL;
   1648 
   1649   // Calculate the original bound of the tile at |index|.
   1650   int row = slot / cols_;
   1651   int col = slot % cols_;
   1652   gfx::Rect tile_rect = GetTileBounds(row, col);
   1653 
   1654   for (int i = 0; i < view_model_.view_size(); ++i) {
   1655     views::View* view = view_model_.view_at(i);
   1656     if (view->bounds() == tile_rect)
   1657       return view;
   1658   }
   1659   return NULL;
   1660 }
   1661 
   1662 void AppsGridView::SetAsFolderDroppingTarget(const Index& target_index,
   1663                                              bool is_target_folder) {
   1664   AppListItemView* target_view =
   1665       static_cast<AppListItemView*>(
   1666           GetViewAtSlotOnCurrentPage(target_index.slot));
   1667   if (target_view)
   1668     target_view->SetAsAttemptedFolderTarget(is_target_folder);
   1669 }
   1670 
   1671 }  // namespace app_list
   1672