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/contents_view.h"
6
7#include <algorithm>
8#include <vector>
9
10#include "base/logging.h"
11#include "ui/app_list/app_list_constants.h"
12#include "ui/app_list/app_list_switches.h"
13#include "ui/app_list/app_list_view_delegate.h"
14#include "ui/app_list/views/app_list_folder_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_switcher_view.h"
19#include "ui/app_list/views/search_result_list_view.h"
20#include "ui/app_list/views/start_page_view.h"
21#include "ui/events/event.h"
22#include "ui/gfx/animation/tween.h"
23#include "ui/resources/grit/ui_resources.h"
24#include "ui/views/view_model.h"
25#include "ui/views/view_model_utils.h"
26
27namespace app_list {
28
29ContentsView::ContentsView(AppListMainView* app_list_main_view)
30    : search_results_view_(NULL),
31      start_page_view_(NULL),
32      app_list_main_view_(app_list_main_view),
33      contents_switcher_view_(NULL),
34      view_model_(new views::ViewModel),
35      page_before_search_(0) {
36  pagination_model_.SetTransitionDurations(kPageTransitionDurationInMs,
37                                           kOverscrollPageTransitionDurationMs);
38  pagination_model_.AddObserver(this);
39}
40
41ContentsView::~ContentsView() {
42  pagination_model_.RemoveObserver(this);
43  if (contents_switcher_view_)
44    pagination_model_.RemoveObserver(contents_switcher_view_);
45}
46
47void ContentsView::InitNamedPages(AppListModel* model,
48                                  AppListViewDelegate* view_delegate) {
49  DCHECK(model);
50
51  if (app_list::switches::IsExperimentalAppListEnabled()) {
52    std::vector<views::View*> custom_page_views =
53        view_delegate->CreateCustomPageWebViews(GetLocalBounds().size());
54    for (std::vector<views::View*>::const_iterator it =
55             custom_page_views.begin();
56         it != custom_page_views.end();
57         ++it) {
58      AddLauncherPage(*it, IDR_APP_LIST_NOTIFICATIONS_ICON);
59    }
60
61    start_page_view_ = new StartPageView(app_list_main_view_, view_delegate);
62    AddLauncherPage(
63        start_page_view_, IDR_APP_LIST_SEARCH_ICON, NAMED_PAGE_START);
64  } else {
65    search_results_view_ =
66        new SearchResultListView(app_list_main_view_, view_delegate);
67    AddLauncherPage(search_results_view_, 0, NAMED_PAGE_SEARCH_RESULTS);
68    search_results_view_->SetResults(model->results());
69  }
70
71  apps_container_view_ = new AppsContainerView(app_list_main_view_, model);
72
73  AddLauncherPage(
74      apps_container_view_, IDR_APP_LIST_APPS_ICON, NAMED_PAGE_APPS);
75
76  int initial_page_index = app_list::switches::IsExperimentalAppListEnabled()
77                               ? GetPageIndexForNamedPage(NAMED_PAGE_START)
78                               : GetPageIndexForNamedPage(NAMED_PAGE_APPS);
79  DCHECK_GE(initial_page_index, 0);
80
81  page_before_search_ = initial_page_index;
82  pagination_model_.SelectPage(initial_page_index, false);
83
84  // Needed to update the main search box visibility.
85  ActivePageChanged(false);
86}
87
88void ContentsView::CancelDrag() {
89  if (apps_container_view_->apps_grid_view()->has_dragged_view())
90    apps_container_view_->apps_grid_view()->EndDrag(true);
91  if (apps_container_view_->app_list_folder_view()
92          ->items_grid_view()
93          ->has_dragged_view()) {
94    apps_container_view_->app_list_folder_view()->items_grid_view()->EndDrag(
95        true);
96  }
97}
98
99void ContentsView::SetDragAndDropHostOfCurrentAppList(
100    ApplicationDragAndDropHost* drag_and_drop_host) {
101  apps_container_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
102}
103
104void ContentsView::SetContentsSwitcherView(
105    ContentsSwitcherView* contents_switcher_view) {
106  DCHECK(!contents_switcher_view_);
107  contents_switcher_view_ = contents_switcher_view;
108  if (contents_switcher_view_)
109    pagination_model_.AddObserver(contents_switcher_view_);
110}
111
112void ContentsView::SetActivePage(int page_index) {
113  if (GetActivePageIndex() == page_index)
114    return;
115
116  SetActivePageInternal(page_index, false);
117}
118
119int ContentsView::GetActivePageIndex() const {
120  // The active page is changed at the beginning of an animation, not the end.
121  return pagination_model_.SelectedTargetPage();
122}
123
124bool ContentsView::IsNamedPageActive(NamedPage named_page) const {
125  int active_page_index = GetActivePageIndex();
126  return active_page_index >= 0 &&
127         GetPageIndexForNamedPage(named_page) == active_page_index;
128}
129
130int ContentsView::GetPageIndexForNamedPage(NamedPage named_page) const {
131  // Find the index of the view corresponding to the given named_page.
132  std::map<NamedPage, int>::const_iterator it =
133      named_page_to_view_.find(named_page);
134  if (it == named_page_to_view_.end())
135    return -1;
136
137  return it->second;
138}
139
140int ContentsView::NumLauncherPages() const {
141  return pagination_model_.total_pages();
142}
143
144void ContentsView::SetActivePageInternal(int page_index,
145                                         bool show_search_results) {
146  if (!show_search_results)
147    page_before_search_ = page_index;
148  // Start animating to the new page.
149  pagination_model_.SelectPage(page_index, true);
150  ActivePageChanged(show_search_results);
151}
152
153void ContentsView::ActivePageChanged(bool show_search_results) {
154  // TODO(xiyuan): Highlight default match instead of the first.
155  if (IsNamedPageActive(NAMED_PAGE_SEARCH_RESULTS) &&
156      search_results_view_->visible()) {
157    search_results_view_->SetSelectedIndex(0);
158  }
159  if (search_results_view_)
160    search_results_view_->UpdateAutoLaunchState();
161
162  if (IsNamedPageActive(NAMED_PAGE_START)) {
163    if (show_search_results)
164      start_page_view_->ShowSearchResults();
165    else
166      start_page_view_->Reset();
167  }
168
169  // Notify parent AppListMainView of the page change.
170  app_list_main_view_->UpdateSearchBoxVisibility();
171}
172
173void ContentsView::ShowSearchResults(bool show) {
174  int search_page = GetPageIndexForNamedPage(
175      app_list::switches::IsExperimentalAppListEnabled()
176          ? NAMED_PAGE_START
177          : NAMED_PAGE_SEARCH_RESULTS);
178  DCHECK_GE(search_page, 0);
179
180  SetActivePageInternal(show ? search_page : page_before_search_, show);
181}
182
183bool ContentsView::IsShowingSearchResults() const {
184  return app_list::switches::IsExperimentalAppListEnabled()
185             ? IsNamedPageActive(NAMED_PAGE_START) &&
186                   start_page_view_->IsShowingSearchResults()
187             : IsNamedPageActive(NAMED_PAGE_SEARCH_RESULTS);
188}
189
190gfx::Rect ContentsView::GetOffscreenPageBounds(int page_index) const {
191  gfx::Rect bounds(GetContentsBounds());
192  // The start page and search page origins are above; all other pages' origins
193  // are below.
194  int page_height = bounds.height();
195  bool origin_above =
196      GetPageIndexForNamedPage(NAMED_PAGE_START) == page_index ||
197      GetPageIndexForNamedPage(NAMED_PAGE_SEARCH_RESULTS) == page_index;
198  bounds.set_y(origin_above ? -page_height : page_height);
199  return bounds;
200}
201
202void ContentsView::UpdatePageBounds() {
203  // The bounds calculations will potentially be mid-transition (depending on
204  // the state of the PaginationModel).
205  int current_page = std::max(0, pagination_model_.selected_page());
206  int target_page = current_page;
207  double progress = 1;
208  if (pagination_model_.has_transition()) {
209    const PaginationModel::Transition& transition =
210        pagination_model_.transition();
211    if (pagination_model_.is_valid_page(transition.target_page)) {
212      target_page = transition.target_page;
213      progress = transition.progress;
214    }
215  }
216
217  // Move |current_page| from 0 to its origin. Move |target_page| from its
218  // origin to 0.
219  gfx::Rect on_screen(GetContentsBounds());
220  gfx::Rect current_page_origin(GetOffscreenPageBounds(current_page));
221  gfx::Rect target_page_origin(GetOffscreenPageBounds(target_page));
222  gfx::Rect current_page_rect(
223      gfx::Tween::RectValueBetween(progress, on_screen, current_page_origin));
224  gfx::Rect target_page_rect(
225      gfx::Tween::RectValueBetween(progress, target_page_origin, on_screen));
226
227  view_model_->view_at(current_page)->SetBoundsRect(current_page_rect);
228  view_model_->view_at(target_page)->SetBoundsRect(target_page_rect);
229}
230
231PaginationModel* ContentsView::GetAppsPaginationModel() {
232  return apps_container_view_->apps_grid_view()->pagination_model();
233}
234
235void ContentsView::ShowFolderContent(AppListFolderItem* item) {
236  apps_container_view_->ShowActiveFolder(item);
237}
238
239void ContentsView::Prerender() {
240  apps_container_view_->apps_grid_view()->Prerender();
241}
242
243views::View* ContentsView::GetPageView(int index) {
244  return view_model_->view_at(index);
245}
246
247void ContentsView::AddBlankPageForTesting() {
248  AddLauncherPage(new views::View, 0);
249}
250
251int ContentsView::AddLauncherPage(views::View* view, int resource_id) {
252  int page_index = view_model_->view_size();
253  AddChildView(view);
254  view_model_->Add(view, page_index);
255  if (contents_switcher_view_ && resource_id)
256    contents_switcher_view_->AddSwitcherButton(resource_id, page_index);
257  pagination_model_.SetTotalPages(view_model_->view_size());
258  return page_index;
259}
260
261int ContentsView::AddLauncherPage(views::View* view,
262                                  int resource_id,
263                                  NamedPage named_page) {
264  int page_index = AddLauncherPage(view, resource_id);
265  named_page_to_view_.insert(std::pair<NamedPage, int>(named_page, page_index));
266  return page_index;
267}
268
269gfx::Size ContentsView::GetPreferredSize() const {
270  const gfx::Size container_size =
271      apps_container_view_->apps_grid_view()->GetPreferredSize();
272  const gfx::Size results_size = search_results_view_
273                                     ? search_results_view_->GetPreferredSize()
274                                     : gfx::Size();
275
276  int width = std::max(container_size.width(), results_size.width());
277  int height = std::max(container_size.height(), results_size.height());
278  return gfx::Size(width, height);
279}
280
281void ContentsView::Layout() {
282  // Immediately finish all current animations.
283  pagination_model_.FinishAnimation();
284
285  // Move the current view onto the screen, and all other views off screen to
286  // the left. (Since we are not animating, we don't need to be careful about
287  // which side we place the off-screen views onto.)
288  gfx::Rect rect(GetContentsBounds());
289  if (rect.IsEmpty())
290    return;
291
292  gfx::Rect offscreen_target(rect);
293  offscreen_target.set_x(-rect.width());
294
295  for (int i = 0; i < view_model_->view_size(); ++i) {
296    view_model_->view_at(i)->SetBoundsRect(
297        i == pagination_model_.SelectedTargetPage() ? rect : offscreen_target);
298  }
299}
300
301bool ContentsView::OnKeyPressed(const ui::KeyEvent& event) {
302  return view_model_->view_at(GetActivePageIndex())->OnKeyPressed(event);
303}
304
305void ContentsView::TotalPagesChanged() {
306}
307
308void ContentsView::SelectedPageChanged(int old_selected, int new_selected) {
309}
310
311void ContentsView::TransitionStarted() {
312}
313
314void ContentsView::TransitionChanged() {
315  UpdatePageBounds();
316}
317
318}  // namespace app_list
319