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#ifndef UI_APP_LIST_VIEWS_APPS_GRID_VIEW_H_
6#define UI_APP_LIST_VIEWS_APPS_GRID_VIEW_H_
7
8#include <set>
9#include <string>
10
11#include "base/basictypes.h"
12#include "base/compiler_specific.h"
13#include "base/memory/ref_counted.h"
14#include "base/timer/timer.h"
15#include "ui/app_list/app_list_export.h"
16#include "ui/app_list/app_list_model.h"
17#include "ui/app_list/app_list_model_observer.h"
18#include "ui/app_list/pagination_model.h"
19#include "ui/app_list/pagination_model_observer.h"
20#include "ui/base/models/list_model_observer.h"
21#include "ui/compositor/layer_animation_observer.h"
22#include "ui/gfx/image/image_skia_operations.h"
23#include "ui/views/animation/bounds_animator.h"
24#include "ui/views/controls/button/button.h"
25#include "ui/views/controls/image_view.h"
26#include "ui/views/view.h"
27#include "ui/views/view_model.h"
28
29#if defined(OS_WIN)
30#include "ui/base/dragdrop/drag_source_win.h"
31#endif
32
33namespace views {
34class ButtonListener;
35class DragImageView;
36}
37
38namespace app_list {
39
40#if defined(OS_WIN)
41class SynchronousDrag;
42#endif
43
44namespace test {
45class AppsGridViewTestApi;
46}
47
48class ApplicationDragAndDropHost;
49class AppListItemView;
50class AppsGridViewDelegate;
51class AppsGridViewFolderDelegate;
52class PageSwitcher;
53class PaginationModel;
54
55// AppsGridView displays a grid for AppListItemList sub model.
56class APP_LIST_EXPORT AppsGridView : public views::View,
57                                     public views::ButtonListener,
58                                     public AppListItemListObserver,
59                                     public PaginationModelObserver,
60                                     public AppListModelObserver,
61                                     public ui::ImplicitAnimationObserver {
62 public:
63  enum Pointer {
64    NONE,
65    MOUSE,
66    TOUCH,
67  };
68
69  // Constructs the app icon grid view. |delegate| is the delegate of this
70  // view, which usually is the hosting AppListView.
71  explicit AppsGridView(AppsGridViewDelegate* delegate);
72  virtual ~AppsGridView();
73
74  // Sets fixed layout parameters. After setting this, CalculateLayout below
75  // is no longer called to dynamically choosing those layout params.
76  void SetLayout(int icon_size, int cols, int rows_per_page);
77
78  int cols() const { return cols_; }
79  int rows_per_page() const { return rows_per_page_; }
80
81  // This resets the grid view to a fresh state for showing the app list.
82  void ResetForShowApps();
83
84  // Sets |model| to use. Note this does not take ownership of |model|.
85  void SetModel(AppListModel* model);
86
87  // Sets the |item_list| to render. Note this does not take ownership of
88  // |item_list|.
89  void SetItemList(AppListItemList* item_list);
90
91  void SetSelectedView(views::View* view);
92  void ClearSelectedView(views::View* view);
93  void ClearAnySelectedView();
94  bool IsSelectedView(const views::View* view) const;
95
96  // Ensures the view is visible. Note that if there is a running page
97  // transition, this does nothing.
98  void EnsureViewVisible(const views::View* view);
99
100  void InitiateDrag(AppListItemView* view,
101                    Pointer pointer,
102                    const ui::LocatedEvent& event);
103
104  // Called from AppListItemView when it receives a drag event. Returns true
105  // if the drag is still happening.
106  bool UpdateDragFromItem(Pointer pointer, const ui::LocatedEvent& event);
107
108  // Called when the user is dragging an app. |point| is in grid view
109  // coordinates.
110  void UpdateDrag(Pointer pointer, const gfx::Point& point);
111  void EndDrag(bool cancel);
112  bool IsDraggedView(const views::View* view) const;
113  void ClearDragState();
114  void SetDragViewVisible(bool visible);
115
116  // Set the drag and drop host for application links.
117  void SetDragAndDropHostOfCurrentAppList(
118      ApplicationDragAndDropHost* drag_and_drop_host);
119
120  // Prerenders the icons on and around |page_index|.
121  void Prerender(int page_index);
122
123  // Return true if the |bounds_animator_| is animating |view|.
124  bool IsAnimatingView(views::View* view);
125
126  bool has_dragged_view() const { return drag_view_ != NULL; }
127  bool dragging() const { return drag_pointer_ != NONE; }
128
129  // Gets the PaginationModel used for the grid view.
130  PaginationModel* pagination_model() { return &pagination_model_; }
131
132  // Overridden from views::View:
133  virtual gfx::Size GetPreferredSize() const OVERRIDE;
134  virtual void Layout() OVERRIDE;
135  virtual bool OnKeyPressed(const ui::KeyEvent& event) OVERRIDE;
136  virtual bool OnKeyReleased(const ui::KeyEvent& event) OVERRIDE;
137  virtual void ViewHierarchyChanged(
138      const ViewHierarchyChangedDetails& details) OVERRIDE;
139  virtual bool GetDropFormats(
140      int* formats,
141      std::set<OSExchangeData::CustomFormat>* custom_formats) OVERRIDE;
142  virtual bool CanDrop(const OSExchangeData& data) OVERRIDE;
143  virtual int OnDragUpdated(const ui::DropTargetEvent& event) OVERRIDE;
144
145  // Stops the timer that triggers a page flip during a drag.
146  void StopPageFlipTimer();
147
148  // Returns the item view of the item at |index|.
149  AppListItemView* GetItemViewAt(int index) const;
150
151  // Show or hide the top item views.
152  void SetTopItemViewsVisible(bool visible);
153
154  // Schedules an animation to show or hide the view.
155  void ScheduleShowHideAnimation(bool show);
156
157  // Called to initiate drag for reparenting a folder item in root level grid
158  // view.
159  // Both |drag_view_rect| and |drag_pint| is in the coordinates of root level
160  // grid view.
161  void InitiateDragFromReparentItemInRootLevelGridView(
162      AppListItemView* original_drag_view,
163      const gfx::Rect& drag_view_rect,
164      const gfx::Point& drag_point);
165
166  // Updates drag in the root level grid view when receiving the drag event
167  // dispatched from the hidden grid view for reparenting a folder item.
168  void UpdateDragFromReparentItem(Pointer pointer,
169                                  const gfx::Point& drag_point);
170
171  // Dispatches the drag event from hidden grid view to the top level grid view.
172  void DispatchDragEventForReparent(Pointer pointer,
173                                    const gfx::Point& drag_point);
174
175  // Handles EndDrag event dispatched from the hidden folder grid view in the
176  // root level grid view to end reparenting a folder item.
177  // |events_forwarded_to_drag_drop_host|: True if the dragged item is dropped
178  // to the drag_drop_host, eg. dropped on shelf.
179  // |cancel_drag|: True if the drag is ending because it has been canceled.
180  void EndDragFromReparentItemInRootLevel(
181      bool events_forwarded_to_drag_drop_host,
182      bool cancel_drag);
183
184  // Handles EndDrag event in the hidden folder grid view to end reparenting
185  // a folder item.
186  void EndDragForReparentInHiddenFolderGridView();
187
188  // Called when the folder item associated with the grid view is removed.
189  // The grid view must be inside a folder view.
190  void OnFolderItemRemoved();
191
192  // Return the view model for test purposes.
193  const views::ViewModel* view_model_for_test() const { return &view_model_; }
194
195  // For test: Return if the drag and drop handler was set.
196  bool has_drag_and_drop_host_for_test() { return NULL != drag_and_drop_host_; }
197
198  // For test: Return if the drag and drop operation gets dispatched.
199  bool forward_events_to_drag_and_drop_host_for_test() {
200    return forward_events_to_drag_and_drop_host_;
201  }
202
203  void set_folder_delegate(AppsGridViewFolderDelegate* folder_delegate) {
204    folder_delegate_ = folder_delegate;
205  }
206
207  AppListItemView* activated_folder_item_view() const {
208    return activated_folder_item_view_;
209  }
210
211  const AppListModel* model() const { return model_; }
212
213 private:
214  friend class test::AppsGridViewTestApi;
215
216  enum DropAttempt {
217    DROP_FOR_NONE,
218    DROP_FOR_REORDER,
219    DROP_FOR_FOLDER,
220  };
221
222  // Represents the index to an item view in the grid.
223  struct Index {
224    Index() : page(-1), slot(-1) {}
225    Index(int page, int slot) : page(page), slot(slot) {}
226
227    bool operator==(const Index& other) const {
228      return page == other.page && slot == other.slot;
229    }
230    bool operator!=(const Index& other) const {
231      return page != other.page || slot != other.slot;
232    }
233
234    int page;  // Which page an item view is on.
235    int slot;  // Which slot in the page an item view is in.
236  };
237
238  int tiles_per_page() const { return cols_ * rows_per_page_; }
239
240  // Updates from model.
241  void Update();
242
243  // Updates page splits for item views.
244  void UpdatePaging();
245
246  // Updates the number of pulsing block views based on AppListModel status and
247  // number of apps.
248  void UpdatePulsingBlockViews();
249
250  views::View* CreateViewForItemAtIndex(size_t index);
251
252  // Convert between the model index and the visual index. The model index
253  // is the index of the item in AppListModel. The visual index is the Index
254  // struct above with page/slot info of where to display the item.
255  Index GetIndexFromModelIndex(int model_index) const;
256  int GetModelIndexFromIndex(const Index& index) const;
257
258  void SetSelectedItemByIndex(const Index& index);
259  bool IsValidIndex(const Index& index) const;
260
261  Index GetIndexOfView(const views::View* view) const;
262  views::View* GetViewAtIndex(const Index& index) const;
263
264  void MoveSelected(int page_delta, int slot_x_delta, int slot_y_delta);
265
266  void CalculateIdealBounds();
267  void AnimateToIdealBounds();
268
269  // Invoked when the given |view|'s current bounds and target bounds are on
270  // different rows. To avoid moving diagonally, |view| would be put into a
271  // slot prior |target| and fade in while moving to |target|. In the meanwhile,
272  // a layer copy of |view| would start at |current| and fade out while moving
273  // to succeeding slot of |current|. |animate_current| controls whether to run
274  // fading out animation from |current|. |animate_target| controls whether to
275  // run fading in animation to |target|.
276  void AnimationBetweenRows(views::View* view,
277                            bool animate_current,
278                            const gfx::Rect& current,
279                            bool animate_target,
280                            const gfx::Rect& target);
281
282  // Extracts drag location info from |event| into |drag_point|.
283  void ExtractDragLocation(const ui::LocatedEvent& event,
284                           gfx::Point* drag_point);
285
286  // Calculates |drop_target_| based on |drag_point|. |drag_point| is in the
287  // grid view's coordinates. When |use_page_button_hovering| is true and
288  // |drag_point| is hovering on a page button, use the last slot on that page
289  // as drop target.
290  void CalculateDropTarget(const gfx::Point& drag_point,
291                           bool use_page_button_hovering);
292
293  // Same as CalculateDropTarget, but with folder UI enabled. The |drop_target_|
294  // can be either a target for re-ordering, or a target folder to move the
295  // dragged item into if |drag_view_| enters its re-ordering or folder
296  // dropping circle.
297  void CalculateDropTargetWithFolderEnabled(const gfx::Point& drag_point,
298                                            bool use_page_button_hovering);
299
300  // Prepares |drag_and_drop_host_| for dragging. |grid_location| contains
301  // the drag point in this grid view's coordinates.
302  void StartDragAndDropHostDrag(const gfx::Point& grid_location);
303
304  // Dispatch the drag and drop update event to the dnd host (if needed).
305  void DispatchDragEventToDragAndDropHost(
306      const gfx::Point& location_in_screen_coordinates);
307
308  // Starts the page flip timer if |drag_point| is in left/right side page flip
309  // zone or is over page switcher.
310  void MaybeStartPageFlipTimer(const gfx::Point& drag_point);
311
312  // Invoked when |page_flip_timer_| fires.
313  void OnPageFlipTimer();
314
315  // Updates |model_| to move item represented by |item_view| to |target| slot.
316  void MoveItemInModel(views::View* item_view, const Index& target);
317
318  // Updates |model_| to move item represented by |item_view| into a folder
319  // containing item located at |target| slot, also update |view_model_| for
320  // the related view changes.
321  void MoveItemToFolder(views::View* item_view, const Index& target);
322
323  // Updates both data model and view_model_ for re-parenting a folder item to a
324  // new position in top level item list.
325  void ReparentItemForReorder(views::View* item_view, const Index& target);
326
327  // Updates both data model and view_model_ for re-parenting a folder item
328  // to anther folder target.
329  void ReparentItemToAnotherFolder(views::View* item_view, const Index& target);
330
331  // If there is only 1 item left in the source folder after reparenting an item
332  // from it, updates both data model and view_model_ for removing last item
333  // from the source folder and removes the source folder.
334  void RemoveLastItemFromReparentItemFolderIfNecessary(
335      const std::string& source_folder_id);
336
337  // If user does not drop the re-parenting folder item to any valid target,
338  // cancel the re-parenting action, let the item go back to its original
339  // parent folder with UI animation.
340  void CancelFolderItemReparent(AppListItemView* drag_item_view);
341
342  // Cancels any context menus showing for app items on the current page.
343  void CancelContextMenusOnCurrentPage();
344
345  // Removes the AppListItemView at |index| in |view_model_| and deletes it.
346  void DeleteItemViewAtIndex(int index);
347
348  // Returns true if |point| lies within the bounds of this grid view plus a
349  // buffer area surrounding it.
350  bool IsPointWithinDragBuffer(const gfx::Point& point) const;
351
352  // Overridden from views::ButtonListener:
353  virtual void ButtonPressed(views::Button* sender,
354                             const ui::Event& event) OVERRIDE;
355
356  // Overridden from AppListItemListObserver:
357  virtual void OnListItemAdded(size_t index, AppListItem* item) OVERRIDE;
358  virtual void OnListItemRemoved(size_t index, AppListItem* item) OVERRIDE;
359  virtual void OnListItemMoved(size_t from_index,
360                               size_t to_index,
361                               AppListItem* item) OVERRIDE;
362
363  // Overridden from PaginationModelObserver:
364  virtual void TotalPagesChanged() OVERRIDE;
365  virtual void SelectedPageChanged(int old_selected, int new_selected) OVERRIDE;
366  virtual void TransitionStarted() OVERRIDE;
367  virtual void TransitionChanged() OVERRIDE;
368
369  // Overridden from AppListModelObserver:
370  virtual void OnAppListModelStatusChanged() OVERRIDE;
371
372  // ui::ImplicitAnimationObserver overrides:
373  virtual void OnImplicitAnimationsCompleted() OVERRIDE;
374
375  // Hide a given view temporarily without losing (mouse) events and / or
376  // changing the size of it. If |immediate| is set the change will be
377  // immediately applied - otherwise it will change gradually.
378  // If |hide| is set the view will get hidden, otherwise it gets shown.
379  void SetViewHidden(views::View* view, bool hide, bool immediate);
380
381  // Whether the folder drag-and-drop UI should be enabled.
382  bool EnableFolderDragDropUI();
383
384  // Whether target specified by |drap_target| can accept more items to be
385  // dropped into it.
386  bool CanDropIntoTarget(const Index& drop_target);
387
388  // Returns the visual index of the nearest tile in which |drag_view_| enters
389  // either its re-ordering or folder dropping circle.
390  Index GetNearestTileForDragView();
391
392  // Calculates |nearest_tile| in which |vertex| of the |drag_view| is
393  // enclosed.
394  // *|nearest_tile| and *|d_min| will be updated based on the calculation.
395  // *|d_min| is the distance between |nearest_tile| and |drag_view_|.
396  void CalculateNearestTileForVertex(
397      const gfx::Point& vertex, Index* nearest_tile, int* d_min);
398
399  // Returns the bounds of the tile in which |point| is enclosed if there
400  // is a valid item sits on the tile.
401  gfx::Rect GetTileBoundsForPoint(const gfx::Point& point, Index* tile_index);
402
403  // Gets the bounds of the tile located at |row| and |col| on current page.
404  gfx::Rect GetTileBounds(int row, int col) const;
405
406  // Returns true if the slot of |index| is the last possible slot to drop
407  // an item, i.e. first empty slot next to the last item on the last page.
408  bool IsLastPossibleDropTarget(const Index& index) const;
409
410  // Gets the item view located at |slot| on the current page. If there is
411  // no item located at |slot|, returns NULL.
412  views::View* GetViewAtSlotOnCurrentPage(int slot);
413
414  // Sets state of the view with |target_index| to |is_target_folder| for
415  // dropping |drag_view_|.
416  void SetAsFolderDroppingTarget(const Index& target_index,
417                                 bool is_target_folder);
418
419  // Invoked when |reorder_timer_| fires to show re-order preview UI.
420  void OnReorderTimer();
421
422  // Invoked when |folder_item_reparent_timer_| fires.
423  void OnFolderItemReparentTimer();
424
425  // Invoked when |folder_dropping_timer_| fires to show folder dropping
426  // preview UI.
427  void OnFolderDroppingTimer();
428
429  // Updates drag state for dragging inside a folder's grid view.
430  void UpdateDragStateInsideFolder(Pointer pointer,
431                                   const gfx::Point& drag_point);
432
433  // Returns true if drag event is happening in the root level AppsGridView
434  // for reparenting a folder item.
435  bool IsDraggingForReparentInRootLevelGridView() const;
436
437  // Returns true if drag event is happening in the hidden AppsGridView of the
438  // folder during reparenting a folder item.
439  bool IsDraggingForReparentInHiddenGridView() const;
440
441  // Returns the target icon bounds for |drag_item_view| to fly back
442  // to its parent |folder_item_view| in animation.
443  gfx::Rect GetTargetIconRectInFolder(AppListItemView* drag_item_view,
444      AppListItemView* folder_item_view);
445
446  // Returns true if the grid view is under an OEM folder.
447  bool IsUnderOEMFolder();
448
449  void StartSettingUpSynchronousDrag();
450  bool RunSynchronousDrag();
451  void CleanUpSynchronousDrag();
452#if defined(OS_WIN)
453  void OnGotShortcutPath(scoped_refptr<SynchronousDrag> drag,
454                         const base::FilePath& path);
455#endif
456
457  AppListModel* model_;  // Owned by AppListView.
458  AppListItemList* item_list_;  // Not owned.
459  AppsGridViewDelegate* delegate_;
460
461  // This can be NULL. Only grid views inside folders have a folder delegate.
462  AppsGridViewFolderDelegate* folder_delegate_;
463
464  PaginationModel pagination_model_;
465  PageSwitcher* page_switcher_view_;  // Owned by views hierarchy.
466
467  gfx::Size icon_size_;
468  int cols_;
469  int rows_per_page_;
470
471  // Tracks app item views. There is a view per item in |model_|.
472  views::ViewModel view_model_;
473
474  // Tracks pulsing block views.
475  views::ViewModel pulsing_blocks_model_;
476
477  views::View* selected_view_;
478
479  AppListItemView* drag_view_;
480
481  // The index of the drag_view_ when the drag starts.
482  Index drag_view_init_index_;
483
484  // The point where the drag started in AppListItemView coordinates.
485  gfx::Point drag_view_offset_;
486
487  // The point where the drag started in GridView coordinates.
488  gfx::Point drag_start_grid_view_;
489
490  // The location of |drag_view_| when the drag started.
491  gfx::Point drag_view_start_;
492
493  // Page the drag started on.
494  int drag_start_page_;
495
496#if defined(OS_WIN)
497  // Created when a drag is started (ie: drag exceeds the drag threshold), but
498  // not Run() until supplied with a shortcut path.
499  scoped_refptr<SynchronousDrag> synchronous_drag_;
500
501  // Whether to use SynchronousDrag to support dropping to task bar etc.
502  bool use_synchronous_drag_;
503#endif
504
505  Pointer drag_pointer_;
506  Index drop_target_;
507  DropAttempt drop_attempt_;
508
509  // Timer for re-ordering the |drop_target_| and |drag_view_|.
510  base::OneShotTimer<AppsGridView> reorder_timer_;
511
512  // Timer for dropping |drag_view_| into the folder containing
513  // the |drop_target_|.
514  base::OneShotTimer<AppsGridView> folder_dropping_timer_;
515
516  // Timer for dragging a folder item out of folder container ink bubble.
517  base::OneShotTimer<AppsGridView> folder_item_reparent_timer_;
518
519  // An application target drag and drop host which accepts dnd operations.
520  ApplicationDragAndDropHost* drag_and_drop_host_;
521
522  // The drag operation is currently inside the dnd host and events get
523  // forwarded.
524  bool forward_events_to_drag_and_drop_host_;
525
526  // Last mouse drag location in this view's coordinates.
527  gfx::Point last_drag_point_;
528
529  // Timer to auto flip page when dragging an item near the left/right edges.
530  base::OneShotTimer<AppsGridView> page_flip_timer_;
531
532  // Target page to switch to when |page_flip_timer_| fires.
533  int page_flip_target_;
534
535  // Delay in milliseconds of when |page_flip_timer_| should fire after user
536  // drags an item near the edges.
537  int page_flip_delay_in_ms_;
538
539  views::BoundsAnimator bounds_animator_;
540
541  // The most recent activated folder item view.
542  AppListItemView* activated_folder_item_view_;
543
544  // Tracks if drag_view_ is dragged out of the folder container bubble
545  // when dragging a item inside a folder.
546  bool drag_out_of_folder_container_;
547
548  // True if the drag_view_ item is a folder item being dragged for reparenting.
549  bool dragging_for_reparent_item_;
550
551  DISALLOW_COPY_AND_ASSIGN(AppsGridView);
552};
553
554}  // namespace app_list
555
556#endif  // UI_APP_LIST_VIEWS_APPS_GRID_VIEW_H_
557