Correct blacklist entry message
[chromium-blink-merge.git] / ui / app_list / views / apps_grid_view.cc
blobf300de7fdd22400d750c8f6a4fe52b28ce640eae
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.
5 #include "ui/app_list/views/apps_grid_view.h"
7 #include <algorithm>
9 #include "content/public/browser/web_contents.h"
10 #include "ui/app_list/app_list_item_model.h"
11 #include "ui/app_list/pagination_model.h"
12 #include "ui/app_list/views/app_list_drag_and_drop_host.h"
13 #include "ui/app_list/views/app_list_item_view.h"
14 #include "ui/app_list/views/apps_grid_view_delegate.h"
15 #include "ui/app_list/views/page_switcher.h"
16 #include "ui/app_list/views/pulsing_block_view.h"
17 #include "ui/compositor/scoped_layer_animation_settings.h"
18 #include "ui/events/event.h"
19 #include "ui/gfx/animation/animation.h"
20 #include "ui/views/border.h"
21 #include "ui/views/controls/webview/webview.h"
22 #include "ui/views/view_model_utils.h"
23 #include "ui/views/widget/widget.h"
25 #if defined(USE_AURA)
26 #include "ui/aura/root_window.h"
27 #endif
29 #if defined(OS_WIN) && !defined(USE_AURA)
30 #include "base/command_line.h"
31 #include "base/files/file_path.h"
32 #include "base/win/shortcut.h"
33 #include "ui/base/dragdrop/drag_utils.h"
34 #include "ui/base/dragdrop/drop_target_win.h"
35 #include "ui/base/dragdrop/os_exchange_data.h"
36 #include "ui/base/dragdrop/os_exchange_data_provider_win.h"
37 #endif
39 namespace app_list {
41 namespace {
43 // Distance a drag needs to be from the app grid to be considered 'outside', at
44 // which point we rearrange the apps to their pre-drag configuration, as a drop
45 // then would be canceled. We have a buffer to make it easier to drag apps to
46 // other pages.
47 const int kDragBufferPx = 20;
49 // Padding space in pixels for fixed layout.
50 const int kLeftRightPadding = 20;
51 const int kTopPadding = 1;
53 // Padding space in pixels between pages.
54 const int kPagePadding = 40;
56 // Preferred tile size when showing in fixed layout.
57 const int kPreferredTileWidth = 88;
58 const int kPreferredTileHeight = 98;
60 // Width in pixels of the area on the sides that triggers a page flip.
61 const int kPageFlipZoneSize = 40;
63 // Delay in milliseconds to do the page flip.
64 const int kPageFlipDelayInMs = 1000;
66 // How many pages on either side of the selected one we prerender.
67 const int kPrerenderPages = 1;
69 // The drag and drop proxy should get scaled by this factor.
70 const float kDragAndDropProxyScale = 1.5f;
72 // RowMoveAnimationDelegate is used when moving an item into a different row.
73 // Before running the animation, the item's layer is re-created and kept in
74 // the original position, then the item is moved to just before its target
75 // position and opacity set to 0. When the animation runs, this delegate moves
76 // the layer and fades it out while fading in the item at the same time.
77 class RowMoveAnimationDelegate
78 : public views::BoundsAnimator::OwnedAnimationDelegate {
79 public:
80 RowMoveAnimationDelegate(views::View* view,
81 ui::Layer* layer,
82 const gfx::Rect& layer_target)
83 : view_(view),
84 layer_(layer),
85 layer_start_(layer ? layer->bounds() : gfx::Rect()),
86 layer_target_(layer_target) {
88 virtual ~RowMoveAnimationDelegate() {}
90 // gfx::AnimationDelegate overrides:
91 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
92 view_->layer()->SetOpacity(animation->GetCurrentValue());
93 view_->layer()->ScheduleDraw();
95 if (layer_) {
96 layer_->SetOpacity(1 - animation->GetCurrentValue());
97 layer_->SetBounds(animation->CurrentValueBetween(layer_start_,
98 layer_target_));
99 layer_->ScheduleDraw();
102 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE {
103 view_->layer()->SetOpacity(1.0f);
104 view_->layer()->ScheduleDraw();
106 virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE {
107 view_->layer()->SetOpacity(1.0f);
108 view_->layer()->ScheduleDraw();
111 private:
112 // The view that needs to be wrapped. Owned by views hierarchy.
113 views::View* view_;
115 scoped_ptr<ui::Layer> layer_;
116 const gfx::Rect layer_start_;
117 const gfx::Rect layer_target_;
119 DISALLOW_COPY_AND_ASSIGN(RowMoveAnimationDelegate);
122 } // namespace
124 #if defined(OS_WIN) && !defined(USE_AURA)
125 // Interprets drag events sent from Windows via the drag/drop API and forwards
126 // them to AppsGridView.
127 // On Windows, in order to have the OS perform the drag properly we need to
128 // provide it with a shortcut file which may or may not exist at the time the
129 // drag is started. Therefore while waiting for that shortcut to be located we
130 // just do a regular "internal" drag and transition into the synchronous drag
131 // when the shortcut is found/created. Hence a synchronous drag is an optional
132 // phase of a regular drag and non-Windows platforms drags are equivalent to a
133 // Windows drag that never enters the synchronous drag phase.
134 class SynchronousDrag : public ui::DragSourceWin {
135 public:
136 SynchronousDrag(AppsGridView* grid_view,
137 AppListItemView* drag_view,
138 const gfx::Point& drag_view_offset)
139 : grid_view_(grid_view),
140 drag_view_(drag_view),
141 drag_view_offset_(drag_view_offset),
142 has_shortcut_path_(false),
143 running_(false),
144 canceled_(false) {}
146 void set_shortcut_path(const base::FilePath& shortcut_path) {
147 has_shortcut_path_ = true;
148 shortcut_path_ = shortcut_path;
151 bool CanRun() {
152 return has_shortcut_path_ && !running_;
155 void Run() {
156 DCHECK(CanRun());
157 running_ = true;
159 ui::OSExchangeData data;
160 SetupExchangeData(&data);
162 // Hide the dragged view because the OS is going to create its own.
163 const gfx::Size drag_view_size = drag_view_->size();
164 drag_view_->SetSize(gfx::Size(0, 0));
166 // Blocks until the drag is finished. Calls into the ui::DragSourceWin
167 // methods.
168 DWORD effects;
169 DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data),
170 this, DROPEFFECT_MOVE | DROPEFFECT_LINK, &effects);
172 // Restore the dragged view to its original size.
173 drag_view_->SetSize(drag_view_size);
175 grid_view_->EndDrag(canceled_ || !IsCursorWithinGridView());
178 private:
179 // Overridden from ui::DragSourceWin.
180 virtual void OnDragSourceCancel() OVERRIDE {
181 canceled_ = true;
184 virtual void OnDragSourceDrop() OVERRIDE {
187 virtual void OnDragSourceMove() OVERRIDE {
188 grid_view_->UpdateDrag(AppsGridView::MOUSE, GetCursorInGridViewCoords());
191 void SetupExchangeData(ui::OSExchangeData* data) {
192 data->SetFilename(shortcut_path_);
193 gfx::ImageSkia image(drag_view_->GetDragImage());
194 gfx::Size image_size(image.size());
195 drag_utils::SetDragImageOnDataObject(
196 image,
197 image.size(),
198 gfx::Vector2d(drag_view_offset_.x(), drag_view_offset_.y()),
199 data);
202 HWND GetGridViewHWND() {
203 return grid_view_->GetWidget()->GetTopLevelWidget()->GetNativeView();
206 bool IsCursorWithinGridView() {
207 POINT p;
208 GetCursorPos(&p);
209 return GetGridViewHWND() == WindowFromPoint(p);
212 gfx::Point GetCursorInGridViewCoords() {
213 POINT p;
214 GetCursorPos(&p);
215 ScreenToClient(GetGridViewHWND(), &p);
216 gfx::Point grid_view_pt(p.x, p.y);
217 views::View::ConvertPointFromWidget(grid_view_, &grid_view_pt);
218 return grid_view_pt;
221 AppsGridView* grid_view_;
222 AppListItemView* drag_view_;
223 gfx::Point drag_view_offset_;
224 bool has_shortcut_path_;
225 base::FilePath shortcut_path_;
226 bool running_;
227 bool canceled_;
229 DISALLOW_COPY_AND_ASSIGN(SynchronousDrag);
231 #endif // defined(OS_WIN) && !defined(USE_AURA)
233 AppsGridView::AppsGridView(AppsGridViewDelegate* delegate,
234 PaginationModel* pagination_model,
235 content::WebContents* start_page_contents)
236 : model_(NULL),
237 item_list_(NULL),
238 delegate_(delegate),
239 pagination_model_(pagination_model),
240 page_switcher_view_(new PageSwitcher(pagination_model)),
241 start_page_view_(NULL),
242 cols_(0),
243 rows_per_page_(0),
244 selected_view_(NULL),
245 drag_view_(NULL),
246 drag_start_page_(-1),
247 drag_pointer_(NONE),
248 drag_and_drop_host_(NULL),
249 forward_events_to_drag_and_drop_host_(false),
250 page_flip_target_(-1),
251 page_flip_delay_in_ms_(kPageFlipDelayInMs),
252 bounds_animator_(this) {
253 pagination_model_->AddObserver(this);
254 AddChildView(page_switcher_view_);
256 if (start_page_contents) {
257 start_page_view_ =
258 new views::WebView(start_page_contents->GetBrowserContext());
259 start_page_view_->SetWebContents(start_page_contents);
260 AddChildView(start_page_view_);
264 AppsGridView::~AppsGridView() {
265 // Coming here |drag_view_| should already be canceled since otherwise the
266 // drag would disappear after the app list got animated away and closed,
267 // which would look odd.
268 DCHECK(!drag_view_);
269 if (drag_view_)
270 EndDrag(true);
272 if (model_)
273 model_->RemoveObserver(this);
274 pagination_model_->RemoveObserver(this);
276 if (item_list_)
277 item_list_->RemoveObserver(this);
280 void AppsGridView::SetLayout(int icon_size, int cols, int rows_per_page) {
281 icon_size_.SetSize(icon_size, icon_size);
282 cols_ = cols;
283 rows_per_page_ = rows_per_page;
285 set_border(views::Border::CreateEmptyBorder(kTopPadding,
286 kLeftRightPadding,
288 kLeftRightPadding));
291 void AppsGridView::SetModel(AppListModel* model) {
292 if (model_)
293 model_->RemoveObserver(this);
295 model_ = model;
296 if (model_)
297 model_->AddObserver(this);
299 Update();
302 void AppsGridView::SetItemList(AppListItemList* item_list) {
303 if (item_list_)
304 item_list_->RemoveObserver(this);
306 item_list_ = item_list;
307 item_list_->AddObserver(this);
308 Update();
311 void AppsGridView::SetSelectedView(views::View* view) {
312 if (IsSelectedView(view) || IsDraggedView(view))
313 return;
315 Index index = GetIndexOfView(view);
316 if (IsValidIndex(index))
317 SetSelectedItemByIndex(index);
320 void AppsGridView::ClearSelectedView(views::View* view) {
321 if (view && IsSelectedView(view)) {
322 selected_view_->SchedulePaint();
323 selected_view_ = NULL;
327 bool AppsGridView::IsSelectedView(const views::View* view) const {
328 return selected_view_ == view;
331 void AppsGridView::EnsureViewVisible(const views::View* view) {
332 if (pagination_model_->has_transition())
333 return;
335 Index index = GetIndexOfView(view);
336 if (IsValidIndex(index))
337 pagination_model_->SelectPage(index.page, false);
340 void AppsGridView::InitiateDrag(AppListItemView* view,
341 Pointer pointer,
342 const ui::LocatedEvent& event) {
343 DCHECK(view);
344 if (drag_view_ || pulsing_blocks_model_.view_size())
345 return;
347 drag_view_ = view;
348 drag_view_offset_ = event.location();
349 drag_start_page_ = pagination_model_->selected_page();
350 ExtractDragLocation(event, &drag_start_grid_view_);
351 drag_view_start_ = gfx::Point(drag_view_->x(), drag_view_->y());
354 void AppsGridView::OnGotShortcutPath(const base::FilePath& path) {
355 #if defined(OS_WIN) && !defined(USE_AURA)
356 // Drag may have ended before we get the shortcut path.
357 if (!synchronous_drag_)
358 return;
359 // Setting the shortcut path here means the next time we hit UpdateDrag()
360 // we'll enter the synchronous drag.
361 // NOTE we don't Run() the drag here because that causes animations not to
362 // update for some reason.
363 synchronous_drag_->set_shortcut_path(path);
364 DCHECK(synchronous_drag_->CanRun());
365 #endif
368 void AppsGridView::StartSettingUpSynchronousDrag() {
369 #if defined(OS_WIN) && !defined(USE_AURA)
370 if (!delegate_)
371 return;
373 delegate_->GetShortcutPathForApp(
374 drag_view_->model()->id(),
375 base::Bind(&AppsGridView::OnGotShortcutPath, base::Unretained(this)));
376 synchronous_drag_ = new SynchronousDrag(this, drag_view_, drag_view_offset_);
377 #endif
380 bool AppsGridView::RunSynchronousDrag() {
381 #if defined(OS_WIN) && !defined(USE_AURA)
382 if (synchronous_drag_ && synchronous_drag_->CanRun()) {
383 synchronous_drag_->Run();
384 synchronous_drag_ = NULL;
385 return true;
387 #endif
388 return false;
391 void AppsGridView::CleanUpSynchronousDrag() {
392 #if defined(OS_WIN) && !defined(USE_AURA)
393 synchronous_drag_ = NULL;
394 #endif
397 void AppsGridView::UpdateDragFromItem(Pointer pointer,
398 const ui::LocatedEvent& event) {
399 gfx::Point drag_point_in_grid_view;
400 ExtractDragLocation(event, &drag_point_in_grid_view);
401 UpdateDrag(pointer, drag_point_in_grid_view);
402 if (!dragging())
403 return;
405 // If a drag and drop host is provided, see if the drag operation needs to be
406 // forwarded.
407 DispatchDragEventToDragAndDropHost(event.root_location());
408 if (drag_and_drop_host_)
409 drag_and_drop_host_->UpdateDragIconProxy(event.root_location());
412 void AppsGridView::UpdateDrag(Pointer pointer, const gfx::Point& point) {
413 // EndDrag was called before if |drag_view_| is NULL.
414 if (!drag_view_)
415 return;
417 if (RunSynchronousDrag())
418 return;
420 gfx::Vector2d drag_vector(point - drag_start_grid_view_);
421 if (!dragging() && ExceededDragThreshold(drag_vector)) {
422 drag_pointer_ = pointer;
423 // Move the view to the front so that it appears on top of other views.
424 ReorderChildView(drag_view_, -1);
425 bounds_animator_.StopAnimatingView(drag_view_);
426 StartSettingUpSynchronousDrag();
427 StartDragAndDropHostDrag(point);
430 if (drag_pointer_ != pointer)
431 return;
433 last_drag_point_ = point;
434 const Index last_drop_target = drop_target_;
435 CalculateDropTarget(last_drag_point_, false);
437 if (IsPointWithinDragBuffer(last_drag_point_))
438 MaybeStartPageFlipTimer(last_drag_point_);
439 else
440 StopPageFlipTimer();
442 gfx::Point page_switcher_point(last_drag_point_);
443 views::View::ConvertPointToTarget(this, page_switcher_view_,
444 &page_switcher_point);
445 page_switcher_view_->UpdateUIForDragPoint(page_switcher_point);
447 if (last_drop_target != drop_target_)
448 AnimateToIdealBounds();
450 drag_view_->SetPosition(drag_view_start_ + drag_vector);
453 void AppsGridView::EndDrag(bool cancel) {
454 // EndDrag was called before if |drag_view_| is NULL.
455 if (!drag_view_)
456 return;
457 // Coming here a drag and drop was in progress.
458 bool landed_in_drag_and_drop_host = forward_events_to_drag_and_drop_host_;
459 if (forward_events_to_drag_and_drop_host_) {
460 forward_events_to_drag_and_drop_host_ = false;
461 drag_and_drop_host_->EndDrag(cancel);
462 } else if (!cancel && dragging()) {
463 CalculateDropTarget(last_drag_point_, true);
464 if (IsValidIndex(drop_target_))
465 MoveItemInModel(drag_view_, drop_target_);
468 if (drag_and_drop_host_) {
469 // If we had a drag and drop proxy icon, we delete it and make the real
470 // item visible again.
471 drag_and_drop_host_->DestroyDragIconProxy();
472 if (landed_in_drag_and_drop_host) {
473 // Move the item directly to the target location, avoiding the "zip back"
474 // animation if the user was pinning it to the shelf.
475 int i = drop_target_.slot;
476 gfx::Rect bounds = view_model_.ideal_bounds(i);
477 drag_view_->SetBoundsRect(bounds);
479 // Fade in slowly if it landed in the shelf.
480 SetViewHidden(drag_view_,
481 false /* hide */,
482 !landed_in_drag_and_drop_host /* animate */);
485 // The drag can be ended after the synchronous drag is created but before it
486 // is Run().
487 CleanUpSynchronousDrag();
489 drag_pointer_ = NONE;
490 drop_target_ = Index();
491 drag_view_ = NULL;
492 drag_start_grid_view_ = gfx::Point();
493 drag_start_page_ = -1;
494 drag_view_offset_ = gfx::Point();
495 AnimateToIdealBounds();
497 StopPageFlipTimer();
500 void AppsGridView::StopPageFlipTimer() {
501 page_flip_timer_.Stop();
502 page_flip_target_ = -1;
505 bool AppsGridView::IsDraggedView(const views::View* view) const {
506 return drag_view_ == view;
509 void AppsGridView::SetDragAndDropHostOfCurrentAppList(
510 ApplicationDragAndDropHost* drag_and_drop_host) {
511 drag_and_drop_host_ = drag_and_drop_host;
514 void AppsGridView::Prerender(int page_index) {
515 Layout();
516 int start = std::max(0, (page_index - kPrerenderPages) * tiles_per_page());
517 int end = std::min(view_model_.view_size(),
518 (page_index + 1 + kPrerenderPages) * tiles_per_page());
519 for (int i = start; i < end; i++) {
520 AppListItemView* v = static_cast<AppListItemView*>(view_model_.view_at(i));
521 v->Prerender();
525 gfx::Size AppsGridView::GetPreferredSize() {
526 const gfx::Insets insets(GetInsets());
527 const gfx::Size tile_size = gfx::Size(kPreferredTileWidth,
528 kPreferredTileHeight);
529 const int page_switcher_height =
530 page_switcher_view_->GetPreferredSize().height();
531 return gfx::Size(
532 tile_size.width() * cols_ + insets.width(),
533 tile_size.height() * rows_per_page_ +
534 page_switcher_height + insets.height());
537 bool AppsGridView::GetDropFormats(
538 int* formats,
539 std::set<OSExchangeData::CustomFormat>* custom_formats) {
540 // TODO(koz): Only accept a specific drag type for app shortcuts.
541 *formats = OSExchangeData::FILE_NAME;
542 return true;
545 bool AppsGridView::CanDrop(const OSExchangeData& data) {
546 return true;
549 int AppsGridView::OnDragUpdated(const ui::DropTargetEvent& event) {
550 return ui::DragDropTypes::DRAG_MOVE;
553 void AppsGridView::Layout() {
554 if (bounds_animator_.IsAnimating())
555 bounds_animator_.Cancel();
557 CalculateIdealBounds();
558 for (int i = 0; i < view_model_.view_size(); ++i) {
559 views::View* view = view_model_.view_at(i);
560 if (view != drag_view_)
561 view->SetBoundsRect(view_model_.ideal_bounds(i));
563 views::ViewModelUtils::SetViewBoundsToIdealBounds(pulsing_blocks_model_);
565 const int page_switcher_height =
566 page_switcher_view_->GetPreferredSize().height();
567 gfx::Rect rect(GetContentsBounds());
568 rect.set_y(rect.bottom() - page_switcher_height);
569 rect.set_height(page_switcher_height);
570 page_switcher_view_->SetBoundsRect(rect);
572 LayoutStartPage();
575 bool AppsGridView::OnKeyPressed(const ui::KeyEvent& event) {
576 bool handled = false;
577 if (selected_view_)
578 handled = selected_view_->OnKeyPressed(event);
580 if (!handled) {
581 const int forward_dir = base::i18n::IsRTL() ? -1 : 1;
582 switch (event.key_code()) {
583 case ui::VKEY_LEFT:
584 MoveSelected(0, -forward_dir, 0);
585 return true;
586 case ui::VKEY_RIGHT:
587 MoveSelected(0, forward_dir, 0);
588 return true;
589 case ui::VKEY_UP:
590 MoveSelected(0, 0, -1);
591 return true;
592 case ui::VKEY_DOWN:
593 MoveSelected(0, 0, 1);
594 return true;
595 case ui::VKEY_PRIOR: {
596 MoveSelected(-1, 0, 0);
597 return true;
599 case ui::VKEY_NEXT: {
600 MoveSelected(1, 0, 0);
601 return true;
603 default:
604 break;
608 return handled;
611 bool AppsGridView::OnKeyReleased(const ui::KeyEvent& event) {
612 bool handled = false;
613 if (selected_view_)
614 handled = selected_view_->OnKeyReleased(event);
616 return handled;
619 void AppsGridView::ViewHierarchyChanged(
620 const ViewHierarchyChangedDetails& details) {
621 if (!details.is_add && details.parent == this) {
622 if (selected_view_ == details.child)
623 selected_view_ = NULL;
625 if (drag_view_ == details.child)
626 EndDrag(true);
628 bounds_animator_.StopAnimatingView(details.child);
632 void AppsGridView::Update() {
633 DCHECK(!selected_view_ && !drag_view_);
634 if (!item_list_)
635 return;
637 view_model_.Clear();
638 if (!item_list_->item_count())
639 return;
640 for (size_t i = 0; i < item_list_->item_count(); ++i) {
641 views::View* view = CreateViewForItemAtIndex(i);
642 view_model_.Add(view, i);
643 AddChildView(view);
645 UpdatePaging();
646 UpdatePulsingBlockViews();
647 Layout();
648 SchedulePaint();
651 void AppsGridView::UpdatePaging() {
652 int total_page = start_page_view_ ? 1 : 0;
653 if (view_model_.view_size() && tiles_per_page())
654 total_page += (view_model_.view_size() - 1) / tiles_per_page() + 1;
656 pagination_model_->SetTotalPages(total_page);
659 void AppsGridView::UpdatePulsingBlockViews() {
660 const int existing_items = item_list_ ? item_list_->item_count() : 0;
661 const int available_slots =
662 tiles_per_page() - existing_items % tiles_per_page();
663 const int desired = model_->status() == AppListModel::STATUS_SYNCING ?
664 available_slots : 0;
666 if (pulsing_blocks_model_.view_size() == desired)
667 return;
669 while (pulsing_blocks_model_.view_size() > desired) {
670 views::View* view = pulsing_blocks_model_.view_at(0);
671 pulsing_blocks_model_.Remove(0);
672 delete view;
675 while (pulsing_blocks_model_.view_size() < desired) {
676 views::View* view = new PulsingBlockView(
677 gfx::Size(kPreferredTileWidth, kPreferredTileHeight), true);
678 pulsing_blocks_model_.Add(view, 0);
679 AddChildView(view);
683 views::View* AppsGridView::CreateViewForItemAtIndex(size_t index) {
684 DCHECK_LT(index, item_list_->item_count());
685 AppListItemView* view = new AppListItemView(this,
686 item_list_->item_at(index));
687 view->SetIconSize(icon_size_);
688 #if defined(USE_AURA)
689 view->SetPaintToLayer(true);
690 view->SetFillsBoundsOpaquely(false);
691 #endif
692 return view;
695 AppsGridView::Index AppsGridView::GetIndexFromModelIndex(
696 int model_index) const {
697 int page = model_index / tiles_per_page();
698 if (start_page_view_)
699 ++page;
701 return Index(page, model_index % tiles_per_page());
704 int AppsGridView::GetModelIndexFromIndex(const Index& index) const {
705 int model_index = index.page * tiles_per_page() + index.slot;
706 if (start_page_view_)
707 model_index -= tiles_per_page();
709 return model_index;
712 void AppsGridView::SetSelectedItemByIndex(const Index& index) {
713 if (GetIndexOfView(selected_view_) == index)
714 return;
716 views::View* new_selection = GetViewAtIndex(index);
717 if (!new_selection)
718 return; // Keep current selection.
720 if (selected_view_)
721 selected_view_->SchedulePaint();
723 EnsureViewVisible(new_selection);
724 selected_view_ = new_selection;
725 selected_view_->SchedulePaint();
726 selected_view_->NotifyAccessibilityEvent(
727 ui::AccessibilityTypes::EVENT_FOCUS, true);
730 bool AppsGridView::IsValidIndex(const Index& index) const {
731 const int item_page_start = start_page_view_ ? 1 : 0;
732 return index.page >= item_page_start &&
733 index.page < pagination_model_->total_pages() &&
734 index.slot >= 0 &&
735 index.slot < tiles_per_page() &&
736 GetModelIndexFromIndex(index) < view_model_.view_size();
739 AppsGridView::Index AppsGridView::GetIndexOfView(
740 const views::View* view) const {
741 const int model_index = view_model_.GetIndexOfView(view);
742 if (model_index == -1)
743 return Index();
745 return GetIndexFromModelIndex(model_index);
748 views::View* AppsGridView::GetViewAtIndex(const Index& index) const {
749 if (!IsValidIndex(index))
750 return NULL;
752 const int model_index = GetModelIndexFromIndex(index);
753 return view_model_.view_at(model_index);
756 void AppsGridView::MoveSelected(int page_delta,
757 int slot_x_delta,
758 int slot_y_delta) {
759 if (!selected_view_)
760 return SetSelectedItemByIndex(Index(pagination_model_->selected_page(), 0));
762 const Index& selected = GetIndexOfView(selected_view_);
763 int target_slot = selected.slot + slot_x_delta + slot_y_delta * cols_;
765 if (selected.slot % cols_ == 0 && slot_x_delta == -1) {
766 if (selected.page > 0) {
767 page_delta = -1;
768 target_slot = selected.slot + cols_ - 1;
769 } else {
770 target_slot = selected.slot;
774 if (selected.slot % cols_ == cols_ - 1 && slot_x_delta == 1) {
775 if (selected.page < pagination_model_->total_pages() - 1) {
776 page_delta = 1;
777 target_slot = selected.slot - cols_ + 1;
778 } else {
779 target_slot = selected.slot;
783 // Clamp the target slot to the last item if we are moving to the last page
784 // but our target slot is past the end of the item list.
785 if (page_delta &&
786 selected.page + page_delta == pagination_model_->total_pages() - 1) {
787 int last_item_slot = (view_model_.view_size() - 1) % tiles_per_page();
788 if (last_item_slot < target_slot) {
789 target_slot = last_item_slot;
793 int target_page = std::min(pagination_model_->total_pages() - 1,
794 std::max(selected.page + page_delta, 0));
795 SetSelectedItemByIndex(Index(target_page, target_slot));
798 void AppsGridView::CalculateIdealBounds() {
799 gfx::Rect rect(GetContentsBounds());
800 if (rect.IsEmpty())
801 return;
803 gfx::Size tile_size(kPreferredTileWidth, kPreferredTileHeight);
805 gfx::Rect grid_rect(gfx::Size(tile_size.width() * cols_,
806 tile_size.height() * rows_per_page_));
807 grid_rect.Intersect(rect);
809 // Page width including padding pixels. A tile.x + page_width means the same
810 // tile slot in the next page.
811 const int page_width = grid_rect.width() + kPagePadding;
813 // If there is a transition, calculates offset for current and target page.
814 const int current_page = pagination_model_->selected_page();
815 const PaginationModel::Transition& transition =
816 pagination_model_->transition();
817 const bool is_valid =
818 pagination_model_->is_valid_page(transition.target_page);
820 // Transition to right means negative offset.
821 const int dir = transition.target_page > current_page ? -1 : 1;
822 const int transition_offset = is_valid ?
823 transition.progress * page_width * dir : 0;
825 const int total_views =
826 view_model_.view_size() + pulsing_blocks_model_.view_size();
827 int slot_index = 0;
828 for (int i = 0; i < total_views; ++i) {
829 if (i < view_model_.view_size() && view_model_.view_at(i) == drag_view_)
830 continue;
832 Index view_index = GetIndexFromModelIndex(slot_index);
834 if (drop_target_ == view_index) {
835 ++slot_index;
836 view_index = GetIndexFromModelIndex(slot_index);
839 // Decides an x_offset for current item.
840 int x_offset = 0;
841 if (view_index.page < current_page)
842 x_offset = -page_width;
843 else if (view_index.page > current_page)
844 x_offset = page_width;
846 if (is_valid) {
847 if (view_index.page == current_page ||
848 view_index.page == transition.target_page) {
849 x_offset += transition_offset;
853 const int row = view_index.slot / cols_;
854 const int col = view_index.slot % cols_;
855 gfx::Rect tile_slot(
856 gfx::Point(grid_rect.x() + col * tile_size.width() + x_offset,
857 grid_rect.y() + row * tile_size.height()),
858 tile_size);
859 if (i < view_model_.view_size()) {
860 view_model_.set_ideal_bounds(i, tile_slot);
861 } else {
862 pulsing_blocks_model_.set_ideal_bounds(i - view_model_.view_size(),
863 tile_slot);
866 ++slot_index;
870 void AppsGridView::AnimateToIdealBounds() {
871 const gfx::Rect visible_bounds(GetVisibleBounds());
873 CalculateIdealBounds();
874 for (int i = 0; i < view_model_.view_size(); ++i) {
875 views::View* view = view_model_.view_at(i);
876 if (view == drag_view_)
877 continue;
879 const gfx::Rect& target = view_model_.ideal_bounds(i);
880 if (bounds_animator_.GetTargetBounds(view) == target)
881 continue;
883 const gfx::Rect& current = view->bounds();
884 const bool current_visible = visible_bounds.Intersects(current);
885 const bool target_visible = visible_bounds.Intersects(target);
886 const bool visible = current_visible || target_visible;
888 const int y_diff = target.y() - current.y();
889 if (visible && y_diff && y_diff % kPreferredTileHeight == 0) {
890 AnimationBetweenRows(view,
891 current_visible,
892 current,
893 target_visible,
894 target);
895 } else {
896 bounds_animator_.AnimateViewTo(view, target);
901 void AppsGridView::AnimationBetweenRows(views::View* view,
902 bool animate_current,
903 const gfx::Rect& current,
904 bool animate_target,
905 const gfx::Rect& target) {
906 // Determine page of |current| and |target|. -1 means in the left invisible
907 // page, 0 is the center visible page and 1 means in the right invisible page.
908 const int current_page = current.x() < 0 ? -1 :
909 current.x() >= width() ? 1 : 0;
910 const int target_page = target.x() < 0 ? -1 :
911 target.x() >= width() ? 1 : 0;
913 const int dir = current_page < target_page ||
914 (current_page == target_page && current.y() < target.y()) ? 1 : -1;
916 #if defined(USE_AURA)
917 scoped_ptr<ui::Layer> layer;
918 if (animate_current) {
919 layer.reset(view->RecreateLayer());
920 layer->SuppressPaint();
922 view->SetFillsBoundsOpaquely(false);
923 view->layer()->SetOpacity(0.f);
926 gfx::Rect current_out(current);
927 current_out.Offset(dir * kPreferredTileWidth, 0);
928 #endif
930 gfx::Rect target_in(target);
931 if (animate_target)
932 target_in.Offset(-dir * kPreferredTileWidth, 0);
933 view->SetBoundsRect(target_in);
934 bounds_animator_.AnimateViewTo(view, target);
936 #if defined(USE_AURA)
937 bounds_animator_.SetAnimationDelegate(
938 view,
939 new RowMoveAnimationDelegate(view, layer.release(), current_out),
940 true);
941 #endif
944 void AppsGridView::ExtractDragLocation(const ui::LocatedEvent& event,
945 gfx::Point* drag_point) {
946 #if defined(USE_AURA)
947 // Use root location of |event| instead of location in |drag_view_|'s
948 // coordinates because |drag_view_| has a scale transform and location
949 // could have integer round error and causes jitter.
950 *drag_point = event.root_location();
952 // GetWidget() could be NULL for tests.
953 if (GetWidget()) {
954 aura::Window::ConvertPointToTarget(
955 GetWidget()->GetNativeWindow()->GetRootWindow(),
956 GetWidget()->GetNativeWindow(),
957 drag_point);
960 views::View::ConvertPointFromWidget(this, drag_point);
961 #else
962 // For non-aura, root location is not clearly defined but |drag_view_| does
963 // not have the scale transform. So no round error would be introduced and
964 // it's okay to use View::ConvertPointToTarget.
965 *drag_point = event.location();
966 views::View::ConvertPointToTarget(drag_view_, this, drag_point);
967 #endif
970 void AppsGridView::CalculateDropTarget(const gfx::Point& drag_point,
971 bool use_page_button_hovering) {
972 int current_page = pagination_model_->selected_page();
973 gfx::Point point(drag_point);
974 if (!IsPointWithinDragBuffer(drag_point)) {
975 point = drag_start_grid_view_;
976 current_page = drag_start_page_;
979 if (use_page_button_hovering &&
980 page_switcher_view_->bounds().Contains(point)) {
981 gfx::Point page_switcher_point(point);
982 views::View::ConvertPointToTarget(this, page_switcher_view_,
983 &page_switcher_point);
984 int page = page_switcher_view_->GetPageForPoint(page_switcher_point);
985 if (pagination_model_->is_valid_page(page)) {
986 drop_target_.page = page;
987 drop_target_.slot = tiles_per_page() - 1;
989 } else {
990 gfx::Rect bounds(GetContentsBounds());
991 const int drop_row = (point.y() - bounds.y()) / kPreferredTileHeight;
992 const int drop_col = std::min(cols_ - 1,
993 (point.x() - bounds.x()) / kPreferredTileWidth);
995 drop_target_.page = current_page;
996 drop_target_.slot = std::max(0, std::min(
997 tiles_per_page() - 1,
998 drop_row * cols_ + drop_col));
1001 // Limits to the last possible slot on last page.
1002 if (drop_target_.page == pagination_model_->total_pages() - 1) {
1003 drop_target_.slot = std::min(
1004 (view_model_.view_size() - 1) % tiles_per_page(),
1005 drop_target_.slot);
1009 void AppsGridView::StartDragAndDropHostDrag(const gfx::Point& grid_location) {
1010 // When a drag and drop host is given, the item can be dragged out of the app
1011 // list window. In that case a proxy widget needs to be used.
1012 // Note: This code has very likely to be changed for Windows (non metro mode)
1013 // when a |drag_and_drop_host_| gets implemented.
1014 if (!drag_view_ || !drag_and_drop_host_)
1015 return;
1017 gfx::Point screen_location = grid_location;
1018 views::View::ConvertPointToScreen(this, &screen_location);
1020 // Determine the mouse offset to the center of the icon so that the drag and
1021 // drop host follows this layer.
1022 gfx::Vector2d delta = drag_view_offset_ -
1023 drag_view_->GetLocalBounds().CenterPoint();
1024 delta.set_y(delta.y() + drag_view_->title()->size().height() / 2);
1026 // We have to hide the original item since the drag and drop host will do
1027 // the OS dependent code to "lift off the dragged item".
1028 drag_and_drop_host_->CreateDragIconProxy(screen_location,
1029 drag_view_->model()->icon(),
1030 drag_view_,
1031 delta,
1032 kDragAndDropProxyScale);
1033 SetViewHidden(drag_view_,
1034 true /* hide */,
1035 true /* no animation */);
1038 void AppsGridView::DispatchDragEventToDragAndDropHost(
1039 const gfx::Point& point) {
1040 if (!drag_view_ || !drag_and_drop_host_)
1041 return;
1042 if (bounds().Contains(last_drag_point_)) {
1043 // The event was issued inside the app menu and we should get all events.
1044 if (forward_events_to_drag_and_drop_host_) {
1045 // The DnD host was previously called and needs to be informed that the
1046 // session returns to the owner.
1047 forward_events_to_drag_and_drop_host_ = false;
1048 drag_and_drop_host_->EndDrag(true);
1050 } else {
1051 // The event happened outside our app menu and we might need to dispatch.
1052 if (forward_events_to_drag_and_drop_host_) {
1053 // Dispatch since we have already started.
1054 if (!drag_and_drop_host_->Drag(point)) {
1055 // The host is not active any longer and we cancel the operation.
1056 forward_events_to_drag_and_drop_host_ = false;
1057 drag_and_drop_host_->EndDrag(true);
1059 } else {
1060 if (drag_and_drop_host_->StartDrag(drag_view_->model()->id(), point)) {
1061 // From now on we forward the drag events.
1062 forward_events_to_drag_and_drop_host_ = true;
1063 // Any flip operations are stopped.
1064 StopPageFlipTimer();
1070 void AppsGridView::MaybeStartPageFlipTimer(const gfx::Point& drag_point) {
1071 if (!IsPointWithinDragBuffer(drag_point))
1072 StopPageFlipTimer();
1073 int new_page_flip_target = -1;
1075 if (page_switcher_view_->bounds().Contains(drag_point)) {
1076 gfx::Point page_switcher_point(drag_point);
1077 views::View::ConvertPointToTarget(this, page_switcher_view_,
1078 &page_switcher_point);
1079 new_page_flip_target =
1080 page_switcher_view_->GetPageForPoint(page_switcher_point);
1083 // TODO(xiyuan): Fix this for RTL.
1084 if (new_page_flip_target == -1 && drag_point.x() < kPageFlipZoneSize)
1085 new_page_flip_target = pagination_model_->selected_page() - 1;
1087 if (new_page_flip_target == -1 &&
1088 drag_point.x() > width() - kPageFlipZoneSize) {
1089 new_page_flip_target = pagination_model_->selected_page() + 1;
1092 if (new_page_flip_target == page_flip_target_)
1093 return;
1095 StopPageFlipTimer();
1096 if (pagination_model_->is_valid_page(new_page_flip_target)) {
1097 page_flip_target_ = new_page_flip_target;
1099 if (page_flip_target_ != pagination_model_->selected_page()) {
1100 page_flip_timer_.Start(FROM_HERE,
1101 base::TimeDelta::FromMilliseconds(page_flip_delay_in_ms_),
1102 this, &AppsGridView::OnPageFlipTimer);
1107 void AppsGridView::OnPageFlipTimer() {
1108 DCHECK(pagination_model_->is_valid_page(page_flip_target_));
1109 pagination_model_->SelectPage(page_flip_target_, true);
1112 void AppsGridView::MoveItemInModel(views::View* item_view,
1113 const Index& target) {
1114 int current_model_index = view_model_.GetIndexOfView(item_view);
1115 DCHECK_GE(current_model_index, 0);
1117 int target_model_index = GetModelIndexFromIndex(target);
1118 if (target_model_index == current_model_index)
1119 return;
1121 item_list_->RemoveObserver(this);
1122 item_list_->MoveItem(current_model_index, target_model_index);
1123 view_model_.Move(current_model_index, target_model_index);
1124 item_list_->AddObserver(this);
1126 if (pagination_model_->selected_page() != target.page)
1127 pagination_model_->SelectPage(target.page, false);
1130 void AppsGridView::CancelContextMenusOnCurrentPage() {
1131 int start = pagination_model_->selected_page() * tiles_per_page();
1132 int end = std::min(view_model_.view_size(), start + tiles_per_page());
1133 for (int i = start; i < end; ++i) {
1134 AppListItemView* view =
1135 static_cast<AppListItemView*>(view_model_.view_at(i));
1136 view->CancelContextMenu();
1140 bool AppsGridView::IsPointWithinDragBuffer(const gfx::Point& point) const {
1141 gfx::Rect rect(GetLocalBounds());
1142 rect.Inset(-kDragBufferPx, -kDragBufferPx, -kDragBufferPx, -kDragBufferPx);
1143 return rect.Contains(point);
1146 void AppsGridView::ButtonPressed(views::Button* sender,
1147 const ui::Event& event) {
1148 if (dragging())
1149 return;
1151 if (strcmp(sender->GetClassName(), AppListItemView::kViewClassName))
1152 return;
1154 if (delegate_) {
1155 delegate_->ActivateApp(static_cast<AppListItemView*>(sender)->model(),
1156 event.flags());
1160 void AppsGridView::LayoutStartPage() {
1161 if (!start_page_view_)
1162 return;
1164 gfx::Rect start_page_bounds(GetLocalBounds());
1165 start_page_bounds.set_height(start_page_bounds.height() -
1166 page_switcher_view_->height());
1168 const int page_width = width() + kPagePadding;
1169 const int current_page = pagination_model_->selected_page();
1170 if (current_page > 0)
1171 start_page_bounds.Offset(-page_width, 0);
1173 const PaginationModel::Transition& transition =
1174 pagination_model_->transition();
1175 if (current_page == 0 || transition.target_page == 0) {
1176 const int dir = transition.target_page > current_page ? -1 : 1;
1177 start_page_bounds.Offset(transition.progress * page_width * dir, 0);
1180 start_page_view_->SetBoundsRect(start_page_bounds);
1183 void AppsGridView::OnListItemAdded(size_t index, AppListItemModel* item) {
1184 EndDrag(true);
1186 views::View* view = CreateViewForItemAtIndex(index);
1187 view_model_.Add(view, index);
1188 AddChildView(view);
1190 UpdatePaging();
1191 UpdatePulsingBlockViews();
1192 Layout();
1193 SchedulePaint();
1196 void AppsGridView::OnListItemRemoved(size_t index, AppListItemModel* item) {
1197 EndDrag(true);
1199 views::View* view = view_model_.view_at(index);
1200 view_model_.Remove(index);
1201 delete view;
1203 UpdatePaging();
1204 UpdatePulsingBlockViews();
1205 Layout();
1206 SchedulePaint();
1209 void AppsGridView::OnListItemMoved(size_t from_index,
1210 size_t to_index,
1211 AppListItemModel* item) {
1212 EndDrag(true);
1213 view_model_.Move(from_index, to_index);
1215 UpdatePaging();
1216 AnimateToIdealBounds();
1219 void AppsGridView::TotalPagesChanged() {
1222 void AppsGridView::SelectedPageChanged(int old_selected, int new_selected) {
1223 if (dragging()) {
1224 CalculateDropTarget(last_drag_point_, true);
1225 Layout();
1226 MaybeStartPageFlipTimer(last_drag_point_);
1227 } else {
1228 ClearSelectedView(selected_view_);
1229 Layout();
1233 void AppsGridView::TransitionStarted() {
1234 CancelContextMenusOnCurrentPage();
1237 void AppsGridView::TransitionChanged() {
1238 // Update layout for valid page transition only since over-scroll no longer
1239 // animates app icons.
1240 const PaginationModel::Transition& transition =
1241 pagination_model_->transition();
1242 if (pagination_model_->is_valid_page(transition.target_page))
1243 Layout();
1246 void AppsGridView::OnAppListModelStatusChanged() {
1247 UpdatePulsingBlockViews();
1248 Layout();
1249 SchedulePaint();
1252 void AppsGridView::SetViewHidden(views::View* view, bool hide, bool immediate) {
1253 #if defined(USE_AURA)
1254 ui::ScopedLayerAnimationSettings animator(view->layer()->GetAnimator());
1255 animator.SetPreemptionStrategy(
1256 immediate ? ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET :
1257 ui::LayerAnimator::BLEND_WITH_CURRENT_ANIMATION);
1258 view->layer()->SetOpacity(hide ? 0 : 1);
1259 #endif
1262 } // namespace app_list