1// Copyright 2013 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/app_list_folder_view.h"
6
7#include <algorithm>
8
9#include "grit/ui_strings.h"
10#include "ui/accessibility/ax_view_state.h"
11#include "ui/app_list/app_list_constants.h"
12#include "ui/app_list/app_list_folder_item.h"
13#include "ui/app_list/app_list_model.h"
14#include "ui/app_list/views/app_list_item_view.h"
15#include "ui/app_list/views/app_list_main_view.h"
16#include "ui/app_list/views/apps_container_view.h"
17#include "ui/app_list/views/apps_grid_view.h"
18#include "ui/app_list/views/contents_view.h"
19#include "ui/app_list/views/folder_background_view.h"
20#include "ui/app_list/views/folder_header_view.h"
21#include "ui/app_list/views/search_box_view.h"
22#include "ui/compositor/scoped_layer_animation_settings.h"
23#include "ui/events/event.h"
24#include "ui/gfx/rect_conversions.h"
25#include "ui/views/controls/textfield/textfield.h"
26#include "ui/views/view_model.h"
27#include "ui/views/view_model_utils.h"
28
29namespace app_list {
30
31namespace {
32
33// Indexes of interesting views in ViewModel of AppListFolderView.
34const int kIndexFolderHeader = 0;
35const int kIndexChildItems = 1;
36
37// Threshold for the distance from the center of the item to the circle of the
38// folder container ink bubble, beyond which, the item is considered dragged
39// out of the folder boundary.
40const int kOutOfFolderContainerBubbleDelta = 30;
41
42}  // namespace
43
44AppListFolderView::AppListFolderView(AppsContainerView* container_view,
45                                     AppListModel* model,
46                                     AppListMainView* app_list_main_view)
47    : container_view_(container_view),
48      app_list_main_view_(app_list_main_view),
49      folder_header_view_(new FolderHeaderView(this)),
50      view_model_(new views::ViewModel),
51      model_(model),
52      folder_item_(NULL),
53      hide_for_reparent_(false) {
54  AddChildView(folder_header_view_);
55  view_model_->Add(folder_header_view_, kIndexFolderHeader);
56
57  items_grid_view_ = new AppsGridView(app_list_main_view_);
58  items_grid_view_->set_folder_delegate(this);
59  items_grid_view_->SetLayout(
60      kPreferredIconDimension,
61      container_view->apps_grid_view()->cols(),
62      container_view->apps_grid_view()->rows_per_page());
63  items_grid_view_->SetModel(model);
64  AddChildView(items_grid_view_);
65  view_model_->Add(items_grid_view_, kIndexChildItems);
66
67  SetPaintToLayer(true);
68  SetFillsBoundsOpaquely(false);
69
70  model_->AddObserver(this);
71}
72
73AppListFolderView::~AppListFolderView() {
74  model_->RemoveObserver(this);
75}
76
77void AppListFolderView::SetAppListFolderItem(AppListFolderItem* folder) {
78  accessible_name_ = ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
79      IDS_APP_LIST_FOLDER_OPEN_FOLDER_ACCESSIBILE_NAME);
80  NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
81
82  folder_item_ = folder;
83  items_grid_view_->SetItemList(folder_item_->item_list());
84  folder_header_view_->SetFolderItem(folder_item_);
85}
86
87void AppListFolderView::ScheduleShowHideAnimation(bool show,
88                                                  bool hide_for_reparent) {
89  hide_for_reparent_ = hide_for_reparent;
90
91  // Stop any previous animation.
92  layer()->GetAnimator()->StopAnimating();
93
94  // Hide the top items temporarily if showing the view for opening the folder.
95  if (show)
96    items_grid_view_->SetTopItemViewsVisible(false);
97
98  // Set initial state.
99  layer()->SetOpacity(show ? 0.0f : 1.0f);
100  SetVisible(true);
101  UpdateFolderNameVisibility(true);
102
103  ui::ScopedLayerAnimationSettings animation(layer()->GetAnimator());
104  animation.SetTweenType(
105      show ? kFolderFadeInTweenType : kFolderFadeOutTweenType);
106  animation.AddObserver(this);
107  animation.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
108      show ? kFolderTransitionInDurationMs : kFolderTransitionOutDurationMs));
109
110  layer()->SetOpacity(show ? 1.0f : 0.0f);
111}
112
113gfx::Size AppListFolderView::GetPreferredSize() const {
114  const gfx::Size header_size = folder_header_view_->GetPreferredSize();
115  const gfx::Size grid_size = items_grid_view_->GetPreferredSize();
116  int width = std::max(header_size.width(), grid_size.width());
117  int height = header_size.height() + grid_size.height();
118  return gfx::Size(width, height);
119}
120
121void AppListFolderView::Layout() {
122  CalculateIdealBounds();
123  views::ViewModelUtils::SetViewBoundsToIdealBounds(*view_model_);
124}
125
126bool AppListFolderView::OnKeyPressed(const ui::KeyEvent& event) {
127  return items_grid_view_->OnKeyPressed(event);
128}
129
130void AppListFolderView::OnAppListItemWillBeDeleted(AppListItem* item) {
131  if (item == folder_item_) {
132    items_grid_view_->OnFolderItemRemoved();
133    folder_header_view_->OnFolderItemRemoved();
134    folder_item_ = NULL;
135
136    // Do not change state if it is hidden.
137    if (hide_for_reparent_ || layer()->opacity() == 0.0f)
138      return;
139
140    // If the folder item associated with this view is removed from the model,
141    // (e.g. the last item in the folder was deleted), reset the view and signal
142    // the container view to show the app list instead.
143    // Pass NULL to ShowApps() to avoid triggering animation from the deleted
144    // folder.
145    container_view_->ShowApps(NULL);
146  }
147}
148
149void AppListFolderView::OnImplicitAnimationsCompleted() {
150  // Show the top items when the opening folder animation is done.
151  if (layer()->opacity() == 1.0f)
152    items_grid_view_->SetTopItemViewsVisible(true);
153
154  // If the view is hidden for reparenting a folder item, it has to be visible,
155  // so that drag_view_ can keep receiving mouse events.
156  if (layer()->opacity() == 0.0f && !hide_for_reparent_)
157    SetVisible(false);
158
159  // Set the view bounds to a small rect, so that it won't overlap the root
160  // level apps grid view during folder item reprenting transitional period.
161  if (hide_for_reparent_)
162    SetBoundsRect(gfx::Rect(bounds().x(), bounds().y(), 1, 1));
163}
164
165void AppListFolderView::CalculateIdealBounds() {
166  gfx::Rect rect(GetContentsBounds());
167  if (rect.IsEmpty())
168    return;
169
170  gfx::Rect header_frame(rect);
171  gfx::Size size = folder_header_view_->GetPreferredSize();
172  header_frame.set_height(size.height());
173  view_model_->set_ideal_bounds(kIndexFolderHeader, header_frame);
174
175  gfx::Rect grid_frame(rect);
176  grid_frame.Subtract(header_frame);
177  view_model_->set_ideal_bounds(kIndexChildItems, grid_frame);
178}
179
180void AppListFolderView::StartSetupDragInRootLevelAppsGridView(
181    AppListItemView* original_drag_view,
182    const gfx::Point& drag_point_in_root_grid) {
183  // Converts the original_drag_view's bounds to the coordinate system of
184  // root level grid view.
185  gfx::RectF rect_f(original_drag_view->bounds());
186  views::View::ConvertRectToTarget(items_grid_view_,
187                                   container_view_->apps_grid_view(),
188                                   &rect_f);
189  gfx::Rect rect_in_root_grid_view = gfx::ToEnclosingRect(rect_f);
190
191  container_view_->apps_grid_view()->
192      InitiateDragFromReparentItemInRootLevelGridView(
193          original_drag_view, rect_in_root_grid_view, drag_point_in_root_grid);
194}
195
196gfx::Rect AppListFolderView::GetItemIconBoundsAt(int index) {
197  AppListItemView* item_view = items_grid_view_->GetItemViewAt(index);
198  // Icon bounds relative to AppListItemView.
199  const gfx::Rect icon_bounds = item_view->GetIconBounds();
200  gfx::Rect to_apps_grid_view = item_view->ConvertRectToParent(icon_bounds);
201  gfx::Rect to_folder =
202      items_grid_view_->ConvertRectToParent(to_apps_grid_view);
203
204  // Get the icon image's bound.
205  to_folder.ClampToCenteredSize(
206      gfx::Size(kPreferredIconDimension, kPreferredIconDimension));
207
208  return to_folder;
209}
210
211void AppListFolderView::UpdateFolderViewBackground(bool show_bubble) {
212  if (hide_for_reparent_)
213    return;
214
215  // Before showing the folder container inking bubble, hide the folder name.
216  if (show_bubble)
217    UpdateFolderNameVisibility(false);
218
219  container_view_->folder_background_view()->UpdateFolderContainerBubble(
220      show_bubble ? FolderBackgroundView::SHOW_BUBBLE :
221                    FolderBackgroundView::HIDE_BUBBLE);
222}
223
224void AppListFolderView::UpdateFolderNameVisibility(bool visible) {
225  folder_header_view_->UpdateFolderNameVisibility(visible);
226}
227
228bool AppListFolderView::IsPointOutsideOfFolderBoundary(
229    const gfx::Point& point) {
230  if (!GetLocalBounds().Contains(point))
231    return true;
232
233  gfx::Point center = GetLocalBounds().CenterPoint();
234  float delta = (point - center).Length();
235  return delta > container_view_->folder_background_view()->
236      GetFolderContainerBubbleRadius() + kOutOfFolderContainerBubbleDelta;
237}
238
239// When user drags a folder item out of the folder boundary ink bubble, the
240// folder view UI will be hidden, and switch back to top level AppsGridView.
241// The dragged item will seamlessly move on the top level AppsGridView.
242// In order to achieve the above, we keep the folder view and its child grid
243// view visible with opacity 0, so that the drag_view_ on the hidden grid view
244// will keep receiving mouse event. At the same time, we initiated a new
245// drag_view_ in the top level grid view, and keep it moving with the hidden
246// grid view's drag_view_, so that the dragged item can be engaged in drag and
247// drop flow in the top level grid view. During the reparenting process, the
248// drag_view_ in hidden grid view will dispatch the drag and drop event to
249// the top level grid view, until the drag ends.
250void AppListFolderView::ReparentItem(
251    AppListItemView* original_drag_view,
252    const gfx::Point& drag_point_in_folder_grid) {
253  // Convert the drag point relative to the root level AppsGridView.
254  gfx::Point to_root_level_grid = drag_point_in_folder_grid;
255  ConvertPointToTarget(items_grid_view_,
256                       container_view_->apps_grid_view(),
257                       &to_root_level_grid);
258  StartSetupDragInRootLevelAppsGridView(original_drag_view, to_root_level_grid);
259  container_view_->ReparentFolderItemTransit(folder_item_);
260}
261
262void AppListFolderView::DispatchDragEventForReparent(
263    AppsGridView::Pointer pointer,
264    const gfx::Point& drag_point_in_folder_grid) {
265  AppsGridView* root_grid = container_view_->apps_grid_view();
266  gfx::Point drag_point_in_root_grid = drag_point_in_folder_grid;
267  ConvertPointToTarget(items_grid_view_, root_grid, &drag_point_in_root_grid);
268  root_grid->UpdateDragFromReparentItem(pointer, drag_point_in_folder_grid);
269}
270
271void AppListFolderView::DispatchEndDragEventForReparent(
272    bool events_forwarded_to_drag_drop_host,
273    bool cancel_drag) {
274  container_view_->apps_grid_view()->EndDragFromReparentItemInRootLevel(
275      events_forwarded_to_drag_drop_host, cancel_drag);
276}
277
278void AppListFolderView::HideViewImmediately() {
279  SetVisible(false);
280  hide_for_reparent_ = false;
281}
282
283void AppListFolderView::CloseFolderPage() {
284  accessible_name_ = ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
285      IDS_APP_LIST_FOLDER_CLOSE_FOLDER_ACCESSIBILE_NAME);
286  NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
287
288  GiveBackFocusToSearchBox();
289  if (items_grid_view()->dragging())
290    items_grid_view()->EndDrag(true);
291  items_grid_view()->ClearAnySelectedView();
292  container_view_->ShowApps(folder_item_);
293}
294
295bool AppListFolderView::IsOEMFolder() const {
296  return folder_item_->folder_type() == AppListFolderItem::FOLDER_TYPE_OEM;
297}
298
299void AppListFolderView::SetRootLevelDragViewVisible(bool visible) {
300  container_view_->apps_grid_view()->SetDragViewVisible(visible);
301}
302
303void AppListFolderView::GetAccessibleState(ui::AXViewState* state) {
304  state->role = ui::AX_ROLE_BUTTON;
305  state->name = accessible_name_;
306}
307
308void AppListFolderView::NavigateBack(AppListFolderItem* item,
309                                     const ui::Event& event_flags) {
310  CloseFolderPage();
311}
312
313void AppListFolderView::GiveBackFocusToSearchBox() {
314  app_list_main_view_->search_box_view()->search_box()->RequestFocus();
315}
316
317void AppListFolderView::SetItemName(AppListFolderItem* item,
318                                    const std::string& name) {
319  model_->SetItemName(item, name);
320}
321
322}  // namespace app_list
323