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 "ash/shelf/shelf_view.h"
6
7#include <algorithm>
8
9#include "ash/ash_constants.h"
10#include "ash/ash_switches.h"
11#include "ash/drag_drop/drag_image_view.h"
12#include "ash/metrics/user_metrics_recorder.h"
13#include "ash/root_window_controller.h"
14#include "ash/scoped_target_root_window.h"
15#include "ash/shelf/alternate_app_list_button.h"
16#include "ash/shelf/app_list_button.h"
17#include "ash/shelf/overflow_bubble.h"
18#include "ash/shelf/overflow_bubble_view.h"
19#include "ash/shelf/overflow_button.h"
20#include "ash/shelf/shelf_button.h"
21#include "ash/shelf/shelf_delegate.h"
22#include "ash/shelf/shelf_icon_observer.h"
23#include "ash/shelf/shelf_item_delegate.h"
24#include "ash/shelf/shelf_item_delegate_manager.h"
25#include "ash/shelf/shelf_layout_manager.h"
26#include "ash/shelf/shelf_menu_model.h"
27#include "ash/shelf/shelf_model.h"
28#include "ash/shelf/shelf_tooltip_manager.h"
29#include "ash/shelf/shelf_widget.h"
30#include "ash/shell.h"
31#include "ash/wm/coordinate_conversion.h"
32#include "base/auto_reset.h"
33#include "base/memory/scoped_ptr.h"
34#include "base/metrics/histogram.h"
35#include "grit/ash_resources.h"
36#include "grit/ash_strings.h"
37#include "ui/aura/client/screen_position_client.h"
38#include "ui/aura/root_window.h"
39#include "ui/aura/window.h"
40#include "ui/base/accessibility/accessible_view_state.h"
41#include "ui/base/l10n/l10n_util.h"
42#include "ui/base/models/simple_menu_model.h"
43#include "ui/base/resource/resource_bundle.h"
44#include "ui/compositor/layer.h"
45#include "ui/compositor/layer_animator.h"
46#include "ui/compositor/scoped_animation_duration_scale_mode.h"
47#include "ui/gfx/canvas.h"
48#include "ui/gfx/point.h"
49#include "ui/views/animation/bounds_animator.h"
50#include "ui/views/border.h"
51#include "ui/views/controls/button/image_button.h"
52#include "ui/views/controls/menu/menu_model_adapter.h"
53#include "ui/views/controls/menu/menu_runner.h"
54#include "ui/views/focus/focus_search.h"
55#include "ui/views/view_model.h"
56#include "ui/views/view_model_utils.h"
57#include "ui/views/widget/widget.h"
58
59using gfx::Animation;
60using views::View;
61
62namespace ash {
63namespace internal {
64
65const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_BOTTOM = 0;
66const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_LEFT = 1;
67const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_RIGHT = 2;
68const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_COUNT = 3;
69
70// Default amount content is inset on the left edge.
71const int kDefaultLeadingInset = 8;
72
73// Minimum distance before drag starts.
74const int kMinimumDragDistance = 8;
75
76// Size between the buttons.
77const int kButtonSpacing = 4;
78const int kAlternateButtonSpacing = 10;
79
80// Size allocated to for each button.
81const int kButtonSize = 44;
82
83// Additional spacing for the left and right side of icons.
84const int kHorizontalIconSpacing = 2;
85
86// Inset for items which do not have an icon.
87const int kHorizontalNoIconInsetSpacing =
88    kHorizontalIconSpacing + kDefaultLeadingInset;
89
90// The proportion of the launcher space reserved for non-panel icons. Panels
91// may flow into this space but will be put into the overflow bubble if there
92// is contention for the space.
93const float kReservedNonPanelIconProportion = 0.67f;
94
95// This is the command id of the menu item which contains the name of the menu.
96const int kCommandIdOfMenuName = 0;
97
98// The background color of the active item in the list.
99const SkColor kActiveListItemBackgroundColor = SkColorSetRGB(203 , 219, 241);
100
101// The background color of the active & hovered item in the list.
102const SkColor kFocusedActiveListItemBackgroundColor =
103    SkColorSetRGB(193, 211, 236);
104
105// The text color of the caption item in a list.
106const SkColor kCaptionItemForegroundColor = SK_ColorBLACK;
107
108// The maximum allowable length of a menu line of an application menu in pixels.
109const int kMaximumAppMenuItemLength = 350;
110
111// The distance of the cursor from the outer rim of the shelf before it
112// separates.
113const int kRipOffDistance = 48;
114
115// The rip off drag and drop proxy image should get scaled by this factor.
116const float kDragAndDropProxyScale = 1.5f;
117
118// The opacity represents that this partially disappeared item will get removed.
119const float kDraggedImageOpacity = 0.5f;
120
121namespace {
122
123// The MenuModelAdapter gets slightly changed to adapt the menu appearance to
124// our requirements.
125class ShelfMenuModelAdapter : public views::MenuModelAdapter {
126 public:
127  explicit ShelfMenuModelAdapter(ShelfMenuModel* menu_model);
128
129  // views::MenuModelAdapter:
130  virtual const gfx::Font* GetLabelFont(int command_id) const OVERRIDE;
131  virtual bool IsCommandEnabled(int id) const OVERRIDE;
132  virtual void GetHorizontalIconMargins(int id,
133                                        int icon_size,
134                                        int* left_margin,
135                                        int* right_margin) const OVERRIDE;
136  virtual bool GetForegroundColor(int command_id,
137                                  bool is_hovered,
138                                  SkColor* override_color) const OVERRIDE;
139  virtual bool GetBackgroundColor(int command_id,
140                                  bool is_hovered,
141                                  SkColor* override_color) const OVERRIDE;
142  virtual int GetMaxWidthForMenu(views::MenuItemView* menu) OVERRIDE;
143  virtual bool ShouldReserveSpaceForSubmenuIndicator() const OVERRIDE;
144
145 private:
146  ShelfMenuModel* menu_model_;
147
148  DISALLOW_COPY_AND_ASSIGN(ShelfMenuModelAdapter);
149};
150
151ShelfMenuModelAdapter::ShelfMenuModelAdapter(ShelfMenuModel* menu_model)
152    : MenuModelAdapter(menu_model),
153      menu_model_(menu_model) {
154}
155
156const gfx::Font* ShelfMenuModelAdapter::GetLabelFont(int command_id) const {
157  if (command_id != kCommandIdOfMenuName)
158    return MenuModelAdapter::GetLabelFont(command_id);
159
160  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
161  return &rb.GetFont(ui::ResourceBundle::BoldFont);
162}
163
164bool ShelfMenuModelAdapter::IsCommandEnabled(int id) const {
165  return id != kCommandIdOfMenuName;
166}
167
168bool ShelfMenuModelAdapter::GetForegroundColor(int command_id,
169                                               bool is_hovered,
170                                               SkColor* override_color) const {
171  if (command_id != kCommandIdOfMenuName)
172    return false;
173
174  *override_color = kCaptionItemForegroundColor;
175  return true;
176}
177
178bool ShelfMenuModelAdapter::GetBackgroundColor(int command_id,
179                                               bool is_hovered,
180                                               SkColor* override_color) const {
181  if (!menu_model_->IsCommandActive(command_id))
182    return false;
183
184  *override_color = is_hovered ? kFocusedActiveListItemBackgroundColor :
185                                 kActiveListItemBackgroundColor;
186  return true;
187}
188
189void ShelfMenuModelAdapter::GetHorizontalIconMargins(int command_id,
190                                                     int icon_size,
191                                                     int* left_margin,
192                                                     int* right_margin) const {
193  *left_margin = kHorizontalIconSpacing;
194  *right_margin = (command_id != kCommandIdOfMenuName) ?
195      kHorizontalIconSpacing : -(icon_size + kHorizontalNoIconInsetSpacing);
196}
197
198int ShelfMenuModelAdapter::GetMaxWidthForMenu(views::MenuItemView* menu) {
199  return kMaximumAppMenuItemLength;
200}
201
202bool ShelfMenuModelAdapter::ShouldReserveSpaceForSubmenuIndicator() const {
203  return false;
204}
205
206// Custom FocusSearch used to navigate the launcher in the order items are in
207// the ViewModel.
208class LauncherFocusSearch : public views::FocusSearch {
209 public:
210  explicit LauncherFocusSearch(views::ViewModel* view_model)
211      : FocusSearch(NULL, true, true),
212        view_model_(view_model) {}
213  virtual ~LauncherFocusSearch() {}
214
215  // views::FocusSearch overrides:
216  virtual View* FindNextFocusableView(
217      View* starting_view,
218      bool reverse,
219      Direction direction,
220      bool check_starting_view,
221      views::FocusTraversable** focus_traversable,
222      View** focus_traversable_view) OVERRIDE {
223    int index = view_model_->GetIndexOfView(starting_view);
224    if (index == -1)
225      return view_model_->view_at(0);
226
227    if (reverse) {
228      --index;
229      if (index < 0)
230        index = view_model_->view_size() - 1;
231    } else {
232      ++index;
233      if (index >= view_model_->view_size())
234        index = 0;
235    }
236    return view_model_->view_at(index);
237  }
238
239 private:
240  views::ViewModel* view_model_;
241
242  DISALLOW_COPY_AND_ASSIGN(LauncherFocusSearch);
243};
244
245// AnimationDelegate used when inserting a new item. This steadily increases the
246// opacity of the layer as the animation progress.
247class FadeInAnimationDelegate
248    : public views::BoundsAnimator::OwnedAnimationDelegate {
249 public:
250  explicit FadeInAnimationDelegate(views::View* view) : view_(view) {}
251  virtual ~FadeInAnimationDelegate() {}
252
253  // AnimationDelegate overrides:
254  virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
255    view_->layer()->SetOpacity(animation->GetCurrentValue());
256    view_->layer()->ScheduleDraw();
257  }
258  virtual void AnimationEnded(const Animation* animation) OVERRIDE {
259    view_->layer()->SetOpacity(1.0f);
260    view_->layer()->ScheduleDraw();
261  }
262  virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
263    view_->layer()->SetOpacity(1.0f);
264    view_->layer()->ScheduleDraw();
265  }
266
267 private:
268  views::View* view_;
269
270  DISALLOW_COPY_AND_ASSIGN(FadeInAnimationDelegate);
271};
272
273void ReflectItemStatus(const ash::LauncherItem& item, ShelfButton* button) {
274  switch (item.status) {
275    case STATUS_CLOSED:
276      button->ClearState(ShelfButton::STATE_ACTIVE);
277      button->ClearState(ShelfButton::STATE_RUNNING);
278      button->ClearState(ShelfButton::STATE_ATTENTION);
279      break;
280    case STATUS_RUNNING:
281      button->ClearState(ShelfButton::STATE_ACTIVE);
282      button->AddState(ShelfButton::STATE_RUNNING);
283      button->ClearState(ShelfButton::STATE_ATTENTION);
284      break;
285    case STATUS_ACTIVE:
286      button->AddState(ShelfButton::STATE_ACTIVE);
287      button->ClearState(ShelfButton::STATE_RUNNING);
288      button->ClearState(ShelfButton::STATE_ATTENTION);
289      break;
290    case STATUS_ATTENTION:
291      button->ClearState(ShelfButton::STATE_ACTIVE);
292      button->ClearState(ShelfButton::STATE_RUNNING);
293      button->AddState(ShelfButton::STATE_ATTENTION);
294      break;
295  }
296}
297
298}  // namespace
299
300// AnimationDelegate used when deleting an item. This steadily decreased the
301// opacity of the layer as the animation progress.
302class ShelfView::FadeOutAnimationDelegate
303    : public views::BoundsAnimator::OwnedAnimationDelegate {
304 public:
305  FadeOutAnimationDelegate(ShelfView* host, views::View* view)
306      : shelf_view_(host),
307        view_(view) {}
308  virtual ~FadeOutAnimationDelegate() {}
309
310  // AnimationDelegate overrides:
311  virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
312    view_->layer()->SetOpacity(1 - animation->GetCurrentValue());
313    view_->layer()->ScheduleDraw();
314  }
315  virtual void AnimationEnded(const Animation* animation) OVERRIDE {
316    shelf_view_->OnFadeOutAnimationEnded();
317  }
318  virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
319  }
320
321 private:
322  ShelfView* shelf_view_;
323  scoped_ptr<views::View> view_;
324
325  DISALLOW_COPY_AND_ASSIGN(FadeOutAnimationDelegate);
326};
327
328// AnimationDelegate used to trigger fading an element in. When an item is
329// inserted this delegate is attached to the animation that expands the size of
330// the item.  When done it kicks off another animation to fade the item in.
331class ShelfView::StartFadeAnimationDelegate
332    : public views::BoundsAnimator::OwnedAnimationDelegate {
333 public:
334  StartFadeAnimationDelegate(ShelfView* host,
335                             views::View* view)
336      : shelf_view_(host),
337        view_(view) {}
338  virtual ~StartFadeAnimationDelegate() {}
339
340  // AnimationDelegate overrides:
341  virtual void AnimationEnded(const Animation* animation) OVERRIDE {
342    shelf_view_->FadeIn(view_);
343  }
344  virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
345    view_->layer()->SetOpacity(1.0f);
346  }
347
348 private:
349  ShelfView* shelf_view_;
350  views::View* view_;
351
352  DISALLOW_COPY_AND_ASSIGN(StartFadeAnimationDelegate);
353};
354
355ShelfView::ShelfView(ShelfModel* model,
356                     ShelfDelegate* delegate,
357                     ShelfLayoutManager* manager)
358    : model_(model),
359      delegate_(delegate),
360      view_model_(new views::ViewModel),
361      first_visible_index_(0),
362      last_visible_index_(-1),
363      overflow_button_(NULL),
364      owner_overflow_bubble_(NULL),
365      drag_pointer_(NONE),
366      drag_view_(NULL),
367      drag_offset_(0),
368      start_drag_index_(-1),
369      context_menu_id_(0),
370      leading_inset_(kDefaultLeadingInset),
371      cancelling_drag_model_changed_(false),
372      last_hidden_index_(0),
373      closing_event_time_(base::TimeDelta()),
374      got_deleted_(NULL),
375      drag_and_drop_item_pinned_(false),
376      drag_and_drop_launcher_id_(0),
377      dragged_off_shelf_(false),
378      snap_back_from_rip_off_view_(NULL),
379      item_manager_(Shell::GetInstance()->shelf_item_delegate_manager()),
380      layout_manager_(manager),
381      overflow_mode_(false),
382      main_shelf_(NULL),
383      dragged_off_from_overflow_to_shelf_(false) {
384  DCHECK(model_);
385  bounds_animator_.reset(new views::BoundsAnimator(this));
386  bounds_animator_->AddObserver(this);
387  set_context_menu_controller(this);
388  focus_search_.reset(new LauncherFocusSearch(view_model_.get()));
389  tooltip_.reset(new ShelfTooltipManager(manager, this));
390}
391
392ShelfView::~ShelfView() {
393  bounds_animator_->RemoveObserver(this);
394  model_->RemoveObserver(this);
395  // If we are inside the MenuRunner, we need to know if we were getting
396  // deleted while it was running.
397  if (got_deleted_)
398    *got_deleted_ = true;
399}
400
401void ShelfView::Init() {
402  model_->AddObserver(this);
403
404  const LauncherItems& items(model_->items());
405  for (LauncherItems::const_iterator i = items.begin(); i != items.end(); ++i) {
406    views::View* child = CreateViewForItem(*i);
407    child->SetPaintToLayer(true);
408    view_model_->Add(child, static_cast<int>(i - items.begin()));
409    AddChildView(child);
410  }
411  ShelfStatusChanged();
412  overflow_button_ = new OverflowButton(this);
413  overflow_button_->set_context_menu_controller(this);
414  ConfigureChildView(overflow_button_);
415  AddChildView(overflow_button_);
416  UpdateFirstButtonPadding();
417
418  // We'll layout when our bounds change.
419}
420
421void ShelfView::OnShelfAlignmentChanged() {
422  UpdateFirstButtonPadding();
423  overflow_button_->OnShelfAlignmentChanged();
424  LayoutToIdealBounds();
425  for (int i=0; i < view_model_->view_size(); ++i) {
426    // TODO: remove when AppIcon is a Shelf Button.
427    if (TYPE_APP_LIST == model_->items()[i].type &&
428        !ash::switches::UseAlternateShelfLayout()) {
429      static_cast<AppListButton*>(view_model_->view_at(i))->SetImageAlignment(
430          layout_manager_->SelectValueForShelfAlignment(
431              views::ImageButton::ALIGN_CENTER,
432              views::ImageButton::ALIGN_LEFT,
433              views::ImageButton::ALIGN_RIGHT,
434              views::ImageButton::ALIGN_CENTER),
435          layout_manager_->SelectValueForShelfAlignment(
436              views::ImageButton::ALIGN_TOP,
437              views::ImageButton::ALIGN_MIDDLE,
438              views::ImageButton::ALIGN_MIDDLE,
439              views::ImageButton::ALIGN_BOTTOM));
440    }
441    if (i >= first_visible_index_ && i <= last_visible_index_)
442      view_model_->view_at(i)->Layout();
443  }
444  tooltip_->Close();
445  if (overflow_bubble_)
446    overflow_bubble_->Hide();
447}
448
449void ShelfView::SchedulePaintForAllButtons() {
450  for (int i = 0; i < view_model_->view_size(); ++i) {
451    if (i >= first_visible_index_ && i <= last_visible_index_)
452      view_model_->view_at(i)->SchedulePaint();
453  }
454  if (overflow_button_ && overflow_button_->visible())
455    overflow_button_->SchedulePaint();
456}
457
458gfx::Rect ShelfView::GetIdealBoundsOfItemIcon(LauncherID id) {
459  int index = model_->ItemIndexByID(id);
460  if (index == -1 || (index > last_visible_index_ &&
461                      index < model_->FirstPanelIndex()))
462    return gfx::Rect();
463  const gfx::Rect& ideal_bounds(view_model_->ideal_bounds(index));
464  DCHECK_NE(TYPE_APP_LIST, model_->items()[index].type);
465  ShelfButton* button = static_cast<ShelfButton*>(view_model_->view_at(index));
466  gfx::Rect icon_bounds = button->GetIconBounds();
467  return gfx::Rect(GetMirroredXWithWidthInView(
468                       ideal_bounds.x() + icon_bounds.x(), icon_bounds.width()),
469                   ideal_bounds.y() + icon_bounds.y(),
470                   icon_bounds.width(),
471                   icon_bounds.height());
472}
473
474void ShelfView::UpdatePanelIconPosition(LauncherID id,
475                                        const gfx::Point& midpoint) {
476  int current_index = model_->ItemIndexByID(id);
477  int first_panel_index = model_->FirstPanelIndex();
478  if (current_index < first_panel_index)
479    return;
480
481  gfx::Point midpoint_in_view(GetMirroredXInView(midpoint.x()),
482                              midpoint.y());
483  int target_index = current_index;
484  while (target_index > first_panel_index &&
485         layout_manager_->PrimaryAxisValue(
486             view_model_->ideal_bounds(target_index).x(),
487             view_model_->ideal_bounds(target_index).y()) >
488         layout_manager_->PrimaryAxisValue(midpoint_in_view.x(),
489                                           midpoint_in_view.y())) {
490    --target_index;
491  }
492  while (target_index < view_model_->view_size() - 1 &&
493         layout_manager_->PrimaryAxisValue(
494             view_model_->ideal_bounds(target_index).right(),
495             view_model_->ideal_bounds(target_index).bottom()) <
496         layout_manager_->PrimaryAxisValue(midpoint_in_view.x(),
497                                           midpoint_in_view.y())) {
498    ++target_index;
499  }
500  if (current_index != target_index)
501    model_->Move(current_index, target_index);
502}
503
504bool ShelfView::IsShowingMenu() const {
505  return (launcher_menu_runner_.get() &&
506       launcher_menu_runner_->IsRunning());
507}
508
509bool ShelfView::IsShowingOverflowBubble() const {
510  return overflow_bubble_.get() && overflow_bubble_->IsShowing();
511}
512
513views::View* ShelfView::GetAppListButtonView() const {
514  for (int i = 0; i < model_->item_count(); ++i) {
515    if (model_->items()[i].type == TYPE_APP_LIST)
516      return view_model_->view_at(i);
517  }
518
519  NOTREACHED() << "Applist button not found";
520  return NULL;
521}
522
523////////////////////////////////////////////////////////////////////////////////
524// ShelfView, FocusTraversable implementation:
525
526views::FocusSearch* ShelfView::GetFocusSearch() {
527  return focus_search_.get();
528}
529
530views::FocusTraversable* ShelfView::GetFocusTraversableParent() {
531  return parent()->GetFocusTraversable();
532}
533
534View* ShelfView::GetFocusTraversableParentView() {
535  return this;
536}
537
538void ShelfView::CreateDragIconProxy(
539    const gfx::Point& location_in_screen_coordinates,
540    const gfx::ImageSkia& icon,
541    views::View* replaced_view,
542    const gfx::Vector2d& cursor_offset_from_center,
543    float scale_factor) {
544  drag_replaced_view_ = replaced_view;
545  drag_image_.reset(new ash::internal::DragImageView(
546      drag_replaced_view_->GetWidget()->GetNativeWindow()->GetRootWindow(),
547      ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE));
548  drag_image_->SetImage(icon);
549  gfx::Size size = drag_image_->GetPreferredSize();
550  size.set_width(size.width() * scale_factor);
551  size.set_height(size.height() * scale_factor);
552  drag_image_offset_ = gfx::Vector2d(size.width() / 2, size.height() / 2) +
553                       cursor_offset_from_center;
554  gfx::Rect drag_image_bounds(
555      location_in_screen_coordinates - drag_image_offset_,
556      size);
557  drag_image_->SetBoundsInScreen(drag_image_bounds);
558  drag_image_->SetWidgetVisible(true);
559}
560
561void ShelfView::UpdateDragIconProxy(
562    const gfx::Point& location_in_screen_coordinates) {
563  drag_image_->SetScreenPosition(
564      location_in_screen_coordinates - drag_image_offset_);
565}
566
567void ShelfView::DestroyDragIconProxy() {
568  drag_image_.reset();
569  drag_image_offset_ = gfx::Vector2d(0, 0);
570}
571
572bool ShelfView::StartDrag(const std::string& app_id,
573                          const gfx::Point& location_in_screen_coordinates) {
574  // Bail if an operation is already going on - or the cursor is not inside.
575  // This could happen if mouse / touch operations overlap.
576  if (drag_and_drop_launcher_id_ ||
577      !GetBoundsInScreen().Contains(location_in_screen_coordinates))
578    return false;
579
580  // If the AppsGridView (which was dispatching this event) was opened by our
581  // button, ShelfView dragging operations are locked and we have to unlock.
582  CancelDrag(-1);
583  drag_and_drop_item_pinned_ = false;
584  drag_and_drop_app_id_ = app_id;
585  drag_and_drop_launcher_id_ =
586      delegate_->GetLauncherIDForAppID(drag_and_drop_app_id_);
587  // Check if the application is known and pinned - if not, we have to pin it so
588  // that we can re-arrange the launcher order accordingly. Note that items have
589  // to be pinned to give them the same (order) possibilities as a shortcut.
590  // When an item is dragged from overflow to shelf, IsShowingOverflowBubble()
591  // returns true. At this time, we don't need to pin the item.
592  if (!IsShowingOverflowBubble() &&
593      (!drag_and_drop_launcher_id_ ||
594       !delegate_->IsAppPinned(app_id))) {
595    delegate_->PinAppWithID(app_id);
596    drag_and_drop_launcher_id_ =
597        delegate_->GetLauncherIDForAppID(drag_and_drop_app_id_);
598    if (!drag_and_drop_launcher_id_)
599      return false;
600    drag_and_drop_item_pinned_ = true;
601  }
602  views::View* drag_and_drop_view = view_model_->view_at(
603      model_->ItemIndexByID(drag_and_drop_launcher_id_));
604  DCHECK(drag_and_drop_view);
605
606  // Since there is already an icon presented by the caller, we hide this item
607  // for now. That has to be done by reducing the size since the visibility will
608  // change once a regrouping animation is performed.
609  pre_drag_and_drop_size_ = drag_and_drop_view->size();
610  drag_and_drop_view->SetSize(gfx::Size());
611
612  // First we have to center the mouse cursor over the item.
613  gfx::Point pt = drag_and_drop_view->GetBoundsInScreen().CenterPoint();
614  views::View::ConvertPointFromScreen(drag_and_drop_view, &pt);
615  gfx::Point point_in_root = location_in_screen_coordinates;
616  ash::wm::ConvertPointFromScreen(
617      ash::wm::GetRootWindowAt(location_in_screen_coordinates),
618      &point_in_root);
619  ui::MouseEvent event(ui::ET_MOUSE_PRESSED, pt, point_in_root, 0);
620  PointerPressedOnButton(drag_and_drop_view,
621                         ShelfButtonHost::DRAG_AND_DROP,
622                         event);
623
624  // Drag the item where it really belongs.
625  Drag(location_in_screen_coordinates);
626  return true;
627}
628
629bool ShelfView::Drag(const gfx::Point& location_in_screen_coordinates) {
630  if (!drag_and_drop_launcher_id_ ||
631      !GetBoundsInScreen().Contains(location_in_screen_coordinates))
632    return false;
633
634  gfx::Point pt = location_in_screen_coordinates;
635  views::View* drag_and_drop_view = view_model_->view_at(
636      model_->ItemIndexByID(drag_and_drop_launcher_id_));
637  ConvertPointFromScreen(drag_and_drop_view, &pt);
638  gfx::Point point_in_root = location_in_screen_coordinates;
639  ash::wm::ConvertPointFromScreen(
640      ash::wm::GetRootWindowAt(location_in_screen_coordinates),
641      &point_in_root);
642  ui::MouseEvent event(ui::ET_MOUSE_DRAGGED, pt, point_in_root, 0);
643  PointerDraggedOnButton(drag_and_drop_view,
644                         ShelfButtonHost::DRAG_AND_DROP,
645                         event);
646  return true;
647}
648
649void ShelfView::EndDrag(bool cancel) {
650  if (!drag_and_drop_launcher_id_)
651    return;
652
653  views::View* drag_and_drop_view = view_model_->view_at(
654      model_->ItemIndexByID(drag_and_drop_launcher_id_));
655  PointerReleasedOnButton(
656      drag_and_drop_view, ShelfButtonHost::DRAG_AND_DROP, cancel);
657
658  // Either destroy the temporarily created item - or - make the item visible.
659  if (drag_and_drop_item_pinned_ && cancel)
660    delegate_->UnpinAppWithID(drag_and_drop_app_id_);
661  else if (drag_and_drop_view) {
662    if (cancel) {
663      // When a hosted drag gets canceled, the item can remain in the same slot
664      // and it might have moved within the bounds. In that case the item need
665      // to animate back to its correct location.
666      AnimateToIdealBounds();
667    } else {
668      drag_and_drop_view->SetSize(pre_drag_and_drop_size_);
669    }
670  }
671
672  drag_and_drop_launcher_id_ = 0;
673}
674
675void ShelfView::LayoutToIdealBounds() {
676  if (bounds_animator_->IsAnimating()) {
677    AnimateToIdealBounds();
678    return;
679  }
680
681  IdealBounds ideal_bounds;
682  CalculateIdealBounds(&ideal_bounds);
683  views::ViewModelUtils::SetViewBoundsToIdealBounds(*view_model_);
684  overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
685}
686
687void ShelfView::UpdateAllButtonsVisibilityInOverflowMode() {
688  // The overflow button is not shown in overflow mode.
689  overflow_button_->SetVisible(false);
690  int last_button_index = model_->FirstPanelIndex() - 1;
691  DCHECK_LT(last_visible_index_, view_model_->view_size());
692  for (int i = 0; i < view_model_->view_size(); ++i) {
693    bool visible = i >= first_visible_index_ &&
694        i <= last_visible_index_;
695    if (!ash::switches::UseAlternateShelfLayout())
696      visible &= i != last_button_index;
697
698    // To track the dragging of |drag_view_| continuously, its visibility
699    // should be always true regardless of its position.
700    if (dragged_off_from_overflow_to_shelf_ &&
701        view_model_->view_at(i) == drag_view_)
702      view_model_->view_at(i)->SetVisible(true);
703    else
704      view_model_->view_at(i)->SetVisible(visible);
705  }
706}
707
708void ShelfView::CalculateIdealBounds(IdealBounds* bounds) {
709  int available_size = layout_manager_->PrimaryAxisValue(width(), height());
710  DCHECK(model_->item_count() == view_model_->view_size());
711  if (!available_size)
712    return;
713
714  int first_panel_index = model_->FirstPanelIndex();
715  int last_button_index = first_panel_index - 1;
716
717  // Initial x,y values account both leading_inset in primary
718  // coordinate and secondary coordinate based on the dynamic edge of the
719  // launcher (eg top edge on bottom-aligned launcher).
720  int inset = ash::switches::UseAlternateShelfLayout() ? 0 : leading_inset_;
721  int x = layout_manager_->SelectValueForShelfAlignment(inset, 0, 0, inset);
722  int y = layout_manager_->SelectValueForShelfAlignment(0, inset, inset, 0);
723
724  int button_size = GetButtonSize();
725  int button_spacing = GetButtonSpacing();
726
727  int w = layout_manager_->PrimaryAxisValue(button_size, width());
728  int h = layout_manager_->PrimaryAxisValue(height(), button_size);
729  for (int i = 0; i < view_model_->view_size(); ++i) {
730    if (i < first_visible_index_) {
731      view_model_->set_ideal_bounds(i, gfx::Rect(x, y, 0, 0));
732      continue;
733    }
734
735    view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
736    if (i != last_button_index) {
737      x = layout_manager_->PrimaryAxisValue(x + w + button_spacing, x);
738      y = layout_manager_->PrimaryAxisValue(y, y + h + button_spacing);
739    }
740  }
741
742  if (is_overflow_mode()) {
743    UpdateAllButtonsVisibilityInOverflowMode();
744    return;
745  }
746
747  // To address Fitt's law, we make the first launcher button include the
748  // leading inset (if there is one).
749  if (!ash::switches::UseAlternateShelfLayout()) {
750    if (view_model_->view_size() > 0) {
751      view_model_->set_ideal_bounds(0, gfx::Rect(gfx::Size(
752          layout_manager_->PrimaryAxisValue(inset + w, w),
753          layout_manager_->PrimaryAxisValue(h, inset + h))));
754    }
755  }
756
757  // Right aligned icons.
758  int end_position = available_size - button_spacing;
759  x = layout_manager_->PrimaryAxisValue(end_position, 0);
760  y = layout_manager_->PrimaryAxisValue(0, end_position);
761  for (int i = view_model_->view_size() - 1;
762       i >= first_panel_index; --i) {
763    x = layout_manager_->PrimaryAxisValue(x - w - button_spacing, x);
764    y = layout_manager_->PrimaryAxisValue(y, y - h - button_spacing);
765    view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
766    end_position = layout_manager_->PrimaryAxisValue(x, y);
767  }
768
769  // Icons on the left / top are guaranteed up to kLeftIconProportion of
770  // the available space.
771  int last_icon_position = layout_manager_->PrimaryAxisValue(
772      view_model_->ideal_bounds(last_button_index).right(),
773      view_model_->ideal_bounds(last_button_index).bottom())
774      + button_size + inset;
775  if (!ash::switches::UseAlternateShelfLayout())
776      last_icon_position += button_size;
777  int reserved_icon_space = available_size * kReservedNonPanelIconProportion;
778  if (last_icon_position < reserved_icon_space)
779    end_position = last_icon_position;
780  else
781    end_position = std::max(end_position, reserved_icon_space);
782
783  bounds->overflow_bounds.set_size(
784      gfx::Size(layout_manager_->PrimaryAxisValue(w, width()),
785                layout_manager_->PrimaryAxisValue(height(), h)));
786
787  if (ash::switches::UseAlternateShelfLayout()) {
788    last_visible_index_ = DetermineLastVisibleIndex(
789        end_position - button_size);
790  } else {
791    last_visible_index_ = DetermineLastVisibleIndex(
792        end_position - inset - 2 * button_size);
793  }
794  last_hidden_index_ = DetermineFirstVisiblePanelIndex(end_position) - 1;
795  bool show_overflow =
796      ((ash::switches::UseAlternateShelfLayout() ? 0 : 1) +
797      last_visible_index_ < last_button_index ||
798      last_hidden_index_ >= first_panel_index);
799
800  // Create Space for the overflow button
801  if (show_overflow && ash::switches::UseAlternateShelfLayout() &&
802      last_visible_index_ > 0 && last_visible_index_ < last_button_index)
803    --last_visible_index_;
804  for (int i = 0; i < view_model_->view_size(); ++i) {
805    bool visible = i <= last_visible_index_ || i > last_hidden_index_;
806    // Always show the app list.
807    if (!ash::switches::UseAlternateShelfLayout())
808      visible |= (i == last_button_index);
809    // To receive drag event continously from |drag_view_| during the dragging
810    // off from the shelf, don't make |drag_view_| invisible. It will be
811    // eventually invisible and removed from the |view_model_| by
812    // FinalizeRipOffDrag().
813    if (dragged_off_shelf_ && view_model_->view_at(i) == drag_view_)
814      continue;
815    view_model_->view_at(i)->SetVisible(visible);
816  }
817
818  overflow_button_->SetVisible(show_overflow);
819  if (show_overflow) {
820    DCHECK_NE(0, view_model_->view_size());
821    if (last_visible_index_ == -1) {
822      x = layout_manager_->SelectValueForShelfAlignment(inset, 0, 0, inset);
823      y = layout_manager_->SelectValueForShelfAlignment(0, inset, inset, 0);
824    } else if (last_visible_index_ == last_button_index
825        && !ash::switches::UseAlternateShelfLayout()) {
826      x = view_model_->ideal_bounds(last_visible_index_).x();
827      y = view_model_->ideal_bounds(last_visible_index_).y();
828    } else {
829      x = layout_manager_->PrimaryAxisValue(
830          view_model_->ideal_bounds(last_visible_index_).right(),
831          view_model_->ideal_bounds(last_visible_index_).x());
832      y = layout_manager_->PrimaryAxisValue(
833          view_model_->ideal_bounds(last_visible_index_).y(),
834          view_model_->ideal_bounds(last_visible_index_).bottom());
835    }
836    // Set all hidden panel icon positions to be on the overflow button.
837    for (int i = first_panel_index; i <= last_hidden_index_; ++i)
838      view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
839
840    // Add more space between last visible item and overflow button.
841    // Without this, two buttons look too close compared with other items.
842    if (ash::switches::UseAlternateShelfLayout()) {
843      x = layout_manager_->PrimaryAxisValue(x + button_spacing, x);
844      y = layout_manager_->PrimaryAxisValue(y, y + button_spacing);
845    }
846
847    bounds->overflow_bounds.set_x(x);
848    bounds->overflow_bounds.set_y(y);
849    if (!ash::switches::UseAlternateShelfLayout()) {
850      // Position app list after overflow button.
851      gfx::Rect app_list_bounds = view_model_->ideal_bounds(last_button_index);
852
853      x = layout_manager_->PrimaryAxisValue(x + w + button_spacing, x);
854      y = layout_manager_->PrimaryAxisValue(y, y + h + button_spacing);
855      app_list_bounds.set_x(x);
856      app_list_bounds.set_y(y);
857      view_model_->set_ideal_bounds(last_button_index, app_list_bounds);
858    }
859    if (overflow_bubble_.get() && overflow_bubble_->IsShowing())
860      UpdateOverflowRange(overflow_bubble_->shelf_view());
861  } else {
862    if (overflow_bubble_)
863      overflow_bubble_->Hide();
864  }
865}
866
867int ShelfView::DetermineLastVisibleIndex(int max_value) const {
868  int index = model_->FirstPanelIndex() - 1;
869  while (index >= 0 &&
870         layout_manager_->PrimaryAxisValue(
871             view_model_->ideal_bounds(index).right(),
872             view_model_->ideal_bounds(index).bottom()) > max_value) {
873    index--;
874  }
875  return index;
876}
877
878int ShelfView::DetermineFirstVisiblePanelIndex(int min_value) const {
879  int index = model_->FirstPanelIndex();
880  while (index < view_model_->view_size() &&
881         layout_manager_->PrimaryAxisValue(
882             view_model_->ideal_bounds(index).right(),
883             view_model_->ideal_bounds(index).bottom()) < min_value) {
884    ++index;
885  }
886  return index;
887}
888
889void ShelfView::AddIconObserver(ShelfIconObserver* observer) {
890  observers_.AddObserver(observer);
891}
892
893void ShelfView::RemoveIconObserver(ShelfIconObserver* observer) {
894  observers_.RemoveObserver(observer);
895}
896
897void ShelfView::AnimateToIdealBounds() {
898  IdealBounds ideal_bounds;
899  CalculateIdealBounds(&ideal_bounds);
900  for (int i = 0; i < view_model_->view_size(); ++i) {
901    View* view = view_model_->view_at(i);
902    bounds_animator_->AnimateViewTo(view, view_model_->ideal_bounds(i));
903    // Now that the item animation starts, we have to make sure that the
904    // padding of the first gets properly transferred to the new first item.
905    if (i && view->border())
906      view->set_border(NULL);
907    else if (!i && !view->border())
908      UpdateFirstButtonPadding();
909  }
910  overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
911}
912
913views::View* ShelfView::CreateViewForItem(const LauncherItem& item) {
914  views::View* view = NULL;
915  switch (item.type) {
916    case TYPE_BROWSER_SHORTCUT:
917    case TYPE_APP_SHORTCUT:
918    case TYPE_WINDOWED_APP:
919    case TYPE_PLATFORM_APP:
920    case TYPE_APP_PANEL: {
921      ShelfButton* button = ShelfButton::Create(this, this, layout_manager_);
922      button->SetImage(item.image);
923      ReflectItemStatus(item, button);
924      view = button;
925      break;
926    }
927
928    case TYPE_APP_LIST: {
929      if (ash::switches::UseAlternateShelfLayout()) {
930        view = new AlternateAppListButton(this,
931                                          this,
932                                          layout_manager_->shelf_widget());
933      } else {
934        // TODO(dave): turn this into a ShelfButton too.
935        AppListButton* button = new AppListButton(this, this);
936        button->SetImageAlignment(
937            layout_manager_->SelectValueForShelfAlignment(
938                views::ImageButton::ALIGN_CENTER,
939                views::ImageButton::ALIGN_LEFT,
940                views::ImageButton::ALIGN_RIGHT,
941                views::ImageButton::ALIGN_CENTER),
942            layout_manager_->SelectValueForShelfAlignment(
943                views::ImageButton::ALIGN_TOP,
944                views::ImageButton::ALIGN_MIDDLE,
945                views::ImageButton::ALIGN_MIDDLE,
946                views::ImageButton::ALIGN_BOTTOM));
947        view = button;
948      }
949      break;
950    }
951
952    default:
953      break;
954  }
955  view->set_context_menu_controller(this);
956
957  DCHECK(view);
958  ConfigureChildView(view);
959  return view;
960}
961
962void ShelfView::FadeIn(views::View* view) {
963  view->SetVisible(true);
964  view->layer()->SetOpacity(0);
965  AnimateToIdealBounds();
966  bounds_animator_->SetAnimationDelegate(
967      view, new FadeInAnimationDelegate(view), true);
968}
969
970void ShelfView::PrepareForDrag(Pointer pointer, const ui::LocatedEvent& event) {
971  DCHECK(!dragging());
972  DCHECK(drag_view_);
973  drag_pointer_ = pointer;
974  start_drag_index_ = view_model_->GetIndexOfView(drag_view_);
975
976  if (start_drag_index_== -1) {
977    CancelDrag(-1);
978    return;
979  }
980
981  // If the item is no longer draggable, bail out.
982  ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate(
983      model_->items()[start_drag_index_].id);
984  if (!item_delegate->IsDraggable()) {
985    CancelDrag(-1);
986    return;
987  }
988
989  // Move the view to the front so that it appears on top of other views.
990  ReorderChildView(drag_view_, -1);
991  bounds_animator_->StopAnimatingView(drag_view_);
992}
993
994void ShelfView::ContinueDrag(const ui::LocatedEvent& event) {
995  // Due to a syncing operation the application might have been removed.
996  // Bail if it is gone.
997  int current_index = view_model_->GetIndexOfView(drag_view_);
998  DCHECK_NE(-1, current_index);
999
1000  ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate(
1001      model_->items()[current_index].id);
1002  if (!item_delegate->IsDraggable()) {
1003    CancelDrag(-1);
1004    return;
1005  }
1006
1007  // If this is not a drag and drop host operation and not the app list item,
1008  // check if the item got ripped off the shelf - if it did we are done.
1009  if (!drag_and_drop_launcher_id_ && ash::switches::UseDragOffShelf() &&
1010      RemovableByRipOff(current_index) != NOT_REMOVABLE) {
1011    if (HandleRipOffDrag(event))
1012      return;
1013    // The rip off handler could have changed the location of the item.
1014    current_index = view_model_->GetIndexOfView(drag_view_);
1015  }
1016
1017  // TODO: I don't think this works correctly with RTL.
1018  gfx::Point drag_point(event.location());
1019  ConvertPointToTarget(drag_view_, this, &drag_point);
1020
1021  // Constrain the location to the range of valid indices for the type.
1022  std::pair<int, int> indices(GetDragRange(current_index));
1023  int first_drag_index = indices.first;
1024  int last_drag_index = indices.second;
1025  // If the last index isn't valid, we're overflowing. Constrain to the app list
1026  // (which is the last visible item).
1027  if (first_drag_index < model_->FirstPanelIndex() &&
1028      last_drag_index > last_visible_index_)
1029    last_drag_index = last_visible_index_;
1030  int x = 0, y = 0;
1031  if (layout_manager_->IsHorizontalAlignment()) {
1032    x = std::max(view_model_->ideal_bounds(indices.first).x(),
1033                     drag_point.x() - drag_offset_);
1034    x = std::min(view_model_->ideal_bounds(last_drag_index).right() -
1035                 view_model_->ideal_bounds(current_index).width(),
1036                 x);
1037    if (drag_view_->x() == x)
1038      return;
1039    drag_view_->SetX(x);
1040  } else {
1041    y = std::max(view_model_->ideal_bounds(indices.first).y(),
1042                     drag_point.y() - drag_offset_);
1043    y = std::min(view_model_->ideal_bounds(last_drag_index).bottom() -
1044                 view_model_->ideal_bounds(current_index).height(),
1045                 y);
1046    if (drag_view_->y() == y)
1047      return;
1048    drag_view_->SetY(y);
1049  }
1050
1051  int target_index =
1052      views::ViewModelUtils::DetermineMoveIndex(
1053          *view_model_, drag_view_,
1054          layout_manager_->IsHorizontalAlignment() ?
1055              views::ViewModelUtils::HORIZONTAL :
1056              views::ViewModelUtils::VERTICAL,
1057          x, y);
1058  target_index =
1059      std::min(indices.second, std::max(target_index, indices.first));
1060  if (target_index == current_index)
1061    return;
1062
1063  // Change the model, the LauncherItemMoved() callback will handle the
1064  // |view_model_| update.
1065  model_->Move(current_index, target_index);
1066  bounds_animator_->StopAnimatingView(drag_view_);
1067}
1068
1069bool ShelfView::HandleRipOffDrag(const ui::LocatedEvent& event) {
1070  int current_index = view_model_->GetIndexOfView(drag_view_);
1071  DCHECK_NE(-1, current_index);
1072  std::string dragged_app_id =
1073      delegate_->GetAppIDForLauncherID(model_->items()[current_index].id);
1074
1075  gfx::Point screen_location = event.root_location();
1076  ash::wm::ConvertPointToScreen(GetWidget()->GetNativeWindow()->GetRootWindow(),
1077                                &screen_location);
1078
1079  // To avoid ugly forwards and backwards flipping we use different constants
1080  // for ripping off / re-inserting the items.
1081  if (dragged_off_shelf_) {
1082    // If the shelf/overflow bubble bounds contains |screen_location| we insert
1083    // the item back into the shelf.
1084    if (GetBoundsForDragInsertInScreen().Contains(screen_location)) {
1085      if (dragged_off_from_overflow_to_shelf_) {
1086        // During the dragging an item from Shelf to Overflow, it can enter here
1087        // directly because both are located very closly.
1088        main_shelf_->EndDrag(true);
1089        // Stops the animation of |drag_view_| and sets its bounds explicitly
1090        // becase ContinueDrag() stops its animation. Without this, unexpected
1091        // bounds will be set.
1092        bounds_animator_->StopAnimatingView(drag_view_);
1093        int drag_view_index = view_model_->GetIndexOfView(drag_view_);
1094        drag_view_->SetBoundsRect(view_model_->ideal_bounds(drag_view_index));
1095        dragged_off_from_overflow_to_shelf_ = false;
1096      }
1097      // Destroy our proxy view item.
1098      DestroyDragIconProxy();
1099      // Re-insert the item and return simply false since the caller will handle
1100      // the move as in any normal case.
1101      dragged_off_shelf_ = false;
1102      drag_view_->layer()->SetOpacity(1.0f);
1103      // The size of Overflow bubble should be updated immediately when an item
1104      // is re-inserted.
1105      if (is_overflow_mode())
1106        PreferredSizeChanged();
1107      return false;
1108    } else if (is_overflow_mode() &&
1109               main_shelf_->GetBoundsForDragInsertInScreen().Contains(
1110                   screen_location)) {
1111      if (!dragged_off_from_overflow_to_shelf_) {
1112        dragged_off_from_overflow_to_shelf_ = true;
1113        drag_image_->SetOpacity(1.0f);
1114        main_shelf_->StartDrag(dragged_app_id, screen_location);
1115      } else {
1116        main_shelf_->Drag(screen_location);
1117      }
1118    } else if (dragged_off_from_overflow_to_shelf_) {
1119      // Makes the |drag_image_| partially disappear again.
1120      dragged_off_from_overflow_to_shelf_ = false;
1121      drag_image_->SetOpacity(kDraggedImageOpacity);
1122      main_shelf_->EndDrag(true);
1123      bounds_animator_->StopAnimatingView(drag_view_);
1124      int drag_view_index = view_model_->GetIndexOfView(drag_view_);
1125      drag_view_->SetBoundsRect(view_model_->ideal_bounds(drag_view_index));
1126    }
1127    // Move our proxy view item.
1128    UpdateDragIconProxy(screen_location);
1129    return true;
1130  }
1131  // Check if we are too far away from the shelf to enter the ripped off state.
1132  // Determine the distance to the shelf.
1133  int delta = CalculateShelfDistance(screen_location);
1134  if (delta > kRipOffDistance) {
1135    // Create a proxy view item which can be moved anywhere.
1136    ShelfButton* button = static_cast<ShelfButton*>(drag_view_);
1137    CreateDragIconProxy(event.root_location(),
1138                        button->GetImage(),
1139                        drag_view_,
1140                        gfx::Vector2d(0, 0),
1141                        kDragAndDropProxyScale);
1142    drag_view_->layer()->SetOpacity(0.0f);
1143    dragged_off_shelf_ = true;
1144    if (RemovableByRipOff(current_index) == REMOVABLE) {
1145      // Move the item to the front of the first panel item and hide it.
1146      // LauncherItemMoved() callback will handle the |view_model_| update and
1147      // call AnimateToIdealBounds().
1148      if (current_index != model_->FirstPanelIndex() - 1) {
1149        model_->Move(current_index, model_->FirstPanelIndex() - 1);
1150        StartFadeInLastVisibleItem();
1151      } else if (is_overflow_mode()) {
1152        // Overflow bubble should be shrunk when an item is ripped off.
1153        PreferredSizeChanged();
1154      }
1155      // Make the item partially disappear to show that it will get removed if
1156      // dropped.
1157      drag_image_->SetOpacity(kDraggedImageOpacity);
1158    }
1159    return true;
1160  }
1161  return false;
1162}
1163
1164void ShelfView::FinalizeRipOffDrag(bool cancel) {
1165  if (!dragged_off_shelf_)
1166    return;
1167  // Make sure we do not come in here again.
1168  dragged_off_shelf_ = false;
1169
1170  // Coming here we should always have a |drag_view_|.
1171  DCHECK(drag_view_);
1172  int current_index = view_model_->GetIndexOfView(drag_view_);
1173  // If the view isn't part of the model anymore (|current_index| == -1), a sync
1174  // operation must have removed it. In that case we shouldn't change the model
1175  // and only delete the proxy image.
1176  if (current_index == -1) {
1177    DestroyDragIconProxy();
1178    return;
1179  }
1180
1181  // Set to true when the animation should snap back to where it was before.
1182  bool snap_back = false;
1183  // Items which cannot be dragged off will be handled as a cancel.
1184  if (!cancel) {
1185    if (dragged_off_from_overflow_to_shelf_) {
1186      dragged_off_from_overflow_to_shelf_ = false;
1187      main_shelf_->EndDrag(false);
1188      drag_view_->layer()->SetOpacity(1.0f);
1189    } else if (RemovableByRipOff(current_index) != REMOVABLE) {
1190      // Make sure we do not try to remove un-removable items like items which
1191      // were not pinned or have to be always there.
1192      cancel = true;
1193      snap_back = true;
1194    } else {
1195      // Make sure the item stays invisible upon removal.
1196      drag_view_->SetVisible(false);
1197      std::string app_id =
1198          delegate_->GetAppIDForLauncherID(model_->items()[current_index].id);
1199      delegate_->UnpinAppWithID(app_id);
1200    }
1201  }
1202  if (cancel || snap_back) {
1203    if (dragged_off_from_overflow_to_shelf_) {
1204      dragged_off_from_overflow_to_shelf_ = false;
1205      // Main shelf handles revert of dragged item.
1206      main_shelf_->EndDrag(true);
1207      drag_view_->layer()->SetOpacity(1.0f);
1208    } else if (!cancelling_drag_model_changed_) {
1209      // Only do something if the change did not come through a model change.
1210      gfx::Rect drag_bounds = drag_image_->GetBoundsInScreen();
1211      gfx::Point relative_to = GetBoundsInScreen().origin();
1212      gfx::Rect target(
1213          gfx::PointAtOffsetFromOrigin(drag_bounds.origin()- relative_to),
1214          drag_bounds.size());
1215      drag_view_->SetBoundsRect(target);
1216      // Hide the status from the active item since we snap it back now. Upon
1217      // animation end the flag gets cleared if |snap_back_from_rip_off_view_|
1218      // is set.
1219      snap_back_from_rip_off_view_ = drag_view_;
1220      ShelfButton* button = static_cast<ShelfButton*>(drag_view_);
1221      button->AddState(ShelfButton::STATE_HIDDEN);
1222      // When a canceling drag model is happening, the view model is diverged
1223      // from the menu model and movements / animations should not be done.
1224      model_->Move(current_index, start_drag_index_);
1225      AnimateToIdealBounds();
1226    }
1227    drag_view_->layer()->SetOpacity(1.0f);
1228  }
1229  DestroyDragIconProxy();
1230}
1231
1232ShelfView::RemovableState ShelfView::RemovableByRipOff(int index) {
1233  DCHECK(index >= 0 && index < model_->item_count());
1234  LauncherItemType type = model_->items()[index].type;
1235  if (type == TYPE_APP_LIST || !delegate_->CanPin())
1236    return NOT_REMOVABLE;
1237  std::string app_id =
1238      delegate_->GetAppIDForLauncherID(model_->items()[index].id);
1239  // Note: Only pinned app shortcuts can be removed!
1240  return (type == TYPE_APP_SHORTCUT && delegate_->IsAppPinned(app_id)) ?
1241      REMOVABLE : DRAGGABLE;
1242}
1243
1244bool ShelfView::SameDragType(LauncherItemType typea,
1245                             LauncherItemType typeb) const {
1246  switch (typea) {
1247    case TYPE_APP_SHORTCUT:
1248    case TYPE_BROWSER_SHORTCUT:
1249      return (typeb == TYPE_APP_SHORTCUT || typeb == TYPE_BROWSER_SHORTCUT);
1250    case TYPE_APP_LIST:
1251    case TYPE_PLATFORM_APP:
1252    case TYPE_WINDOWED_APP:
1253    case TYPE_APP_PANEL:
1254      return typeb == typea;
1255    case TYPE_UNDEFINED:
1256      NOTREACHED() << "LauncherItemType must be set.";
1257      return false;
1258  }
1259  NOTREACHED();
1260  return false;
1261}
1262
1263std::pair<int, int> ShelfView::GetDragRange(int index) {
1264  int min_index = -1;
1265  int max_index = -1;
1266  LauncherItemType type = model_->items()[index].type;
1267  for (int i = 0; i < model_->item_count(); ++i) {
1268    if (SameDragType(model_->items()[i].type, type)) {
1269      if (min_index == -1)
1270        min_index = i;
1271      max_index = i;
1272    }
1273  }
1274  return std::pair<int, int>(min_index, max_index);
1275}
1276
1277void ShelfView::ConfigureChildView(views::View* view) {
1278  view->SetPaintToLayer(true);
1279  view->layer()->SetFillsBoundsOpaquely(false);
1280}
1281
1282void ShelfView::ToggleOverflowBubble() {
1283  if (IsShowingOverflowBubble()) {
1284    overflow_bubble_->Hide();
1285    return;
1286  }
1287
1288  if (!overflow_bubble_)
1289    overflow_bubble_.reset(new OverflowBubble());
1290
1291  ShelfView* overflow_view =
1292      new ShelfView(model_, delegate_, layout_manager_);
1293  overflow_view->overflow_mode_ = true;
1294  overflow_view->Init();
1295  overflow_view->set_owner_overflow_bubble(overflow_bubble_.get());
1296  overflow_view->OnShelfAlignmentChanged();
1297  overflow_view->main_shelf_ = this;
1298  UpdateOverflowRange(overflow_view);
1299
1300  overflow_bubble_->Show(overflow_button_, overflow_view);
1301
1302  Shell::GetInstance()->UpdateShelfVisibility();
1303}
1304
1305void ShelfView::UpdateFirstButtonPadding() {
1306  if (ash::switches::UseAlternateShelfLayout())
1307    return;
1308
1309  // Creates an empty border for first launcher button to make included leading
1310  // inset act as the button's padding. This is only needed on button creation
1311  // and when shelf alignment changes.
1312  if (view_model_->view_size() > 0) {
1313    view_model_->view_at(0)->set_border(views::Border::CreateEmptyBorder(
1314        layout_manager_->PrimaryAxisValue(0, leading_inset_),
1315        layout_manager_->PrimaryAxisValue(leading_inset_, 0),
1316        0,
1317        0));
1318  }
1319}
1320
1321void ShelfView::OnFadeOutAnimationEnded() {
1322  AnimateToIdealBounds();
1323  StartFadeInLastVisibleItem();
1324}
1325
1326void ShelfView::StartFadeInLastVisibleItem() {
1327  // If overflow button is visible and there is a valid new last item, fading
1328  // the new last item in after sliding animation is finished.
1329  if (overflow_button_->visible() && last_visible_index_ >= 0) {
1330    views::View* last_visible_view = view_model_->view_at(last_visible_index_);
1331    last_visible_view->layer()->SetOpacity(0);
1332    bounds_animator_->SetAnimationDelegate(
1333        last_visible_view,
1334        new ShelfView::StartFadeAnimationDelegate(this, last_visible_view),
1335        true);
1336  }
1337}
1338
1339void ShelfView::UpdateOverflowRange(ShelfView* overflow_view) {
1340  const int first_overflow_index = last_visible_index_ + 1;
1341  const int last_overflow_index = last_hidden_index_;
1342  DCHECK_LE(first_overflow_index, last_overflow_index);
1343  DCHECK_LT(last_overflow_index, view_model_->view_size());
1344
1345  overflow_view->first_visible_index_ = first_overflow_index;
1346  overflow_view->last_visible_index_ = last_overflow_index;
1347}
1348
1349int ShelfView::GetButtonSize() const {
1350  return ash::switches::UseAlternateShelfLayout() ?
1351      kButtonSize : kLauncherPreferredSize;
1352}
1353
1354int ShelfView::GetButtonSpacing() const {
1355  return ash::switches::UseAlternateShelfLayout() ?
1356      kAlternateButtonSpacing : kButtonSpacing;
1357}
1358
1359bool ShelfView::ShouldHideTooltip(const gfx::Point& cursor_location) {
1360  gfx::Rect active_bounds;
1361
1362  for (int i = 0; i < child_count(); ++i) {
1363    views::View* child = child_at(i);
1364    if (child == overflow_button_)
1365      continue;
1366    if (!ShouldShowTooltipForView(child))
1367      continue;
1368
1369    gfx::Rect child_bounds = child->GetMirroredBounds();
1370    active_bounds.Union(child_bounds);
1371  }
1372
1373  return !active_bounds.Contains(cursor_location);
1374}
1375
1376gfx::Rect ShelfView::GetVisibleItemsBoundsInScreen() {
1377  gfx::Size preferred_size = GetPreferredSize();
1378  gfx::Point origin(GetMirroredXWithWidthInView(0, preferred_size.width()), 0);
1379  ConvertPointToScreen(this, &origin);
1380  return gfx::Rect(origin, preferred_size);
1381}
1382
1383gfx::Rect ShelfView::GetBoundsForDragInsertInScreen() {
1384  gfx::Size preferred_size;
1385  if (is_overflow_mode()) {
1386    DCHECK(owner_overflow_bubble_);
1387    gfx::Rect bubble_bounds =
1388        owner_overflow_bubble_->bubble_view()->GetBubbleBounds();
1389    preferred_size = bubble_bounds.size();
1390  } else {
1391    const int preferred_shelf_size = layout_manager_->GetPreferredShelfSize();
1392
1393    const int last_button_index = view_model_->view_size() - 1;
1394    gfx::Rect last_button_bounds =
1395        view_model_->view_at(last_button_index)->bounds();
1396    if (overflow_button_->visible() &&
1397        model_->GetItemIndexForType(TYPE_APP_PANEL) == -1) {
1398      // When overflow button is visible and shelf has no panel items,
1399      // last_button_bounds should be overflow button's bounds.
1400      last_button_bounds = overflow_button_->bounds();
1401    }
1402
1403    if (layout_manager_->IsHorizontalAlignment()) {
1404      preferred_size = gfx::Size(last_button_bounds.right() + leading_inset_,
1405                                 preferred_shelf_size);
1406    } else {
1407      preferred_size = gfx::Size(preferred_shelf_size,
1408                                 last_button_bounds.bottom() + leading_inset_);
1409    }
1410  }
1411  gfx::Point origin(GetMirroredXWithWidthInView(0, preferred_size.width()), 0);
1412
1413  // In overflow mode, we should use OverflowBubbleView as a source for
1414  // converting |origin| to screen coordinates. When a scroll operation is
1415  // occurred in OverflowBubble, the bounds of ShelfView in OverflowBubble can
1416  // be changed.
1417  if (is_overflow_mode())
1418    ConvertPointToScreen(owner_overflow_bubble_->bubble_view(), &origin);
1419  else
1420    ConvertPointToScreen(this, &origin);
1421
1422  return gfx::Rect(origin, preferred_size);
1423}
1424
1425int ShelfView::CancelDrag(int modified_index) {
1426  FinalizeRipOffDrag(true);
1427  if (!drag_view_)
1428    return modified_index;
1429  bool was_dragging = dragging();
1430  int drag_view_index = view_model_->GetIndexOfView(drag_view_);
1431  drag_pointer_ = NONE;
1432  drag_view_ = NULL;
1433  if (drag_view_index == modified_index) {
1434    // The view that was being dragged is being modified. Don't do anything.
1435    return modified_index;
1436  }
1437  if (!was_dragging)
1438    return modified_index;
1439
1440  // Restore previous position, tracking the position of the modified view.
1441  bool at_end = modified_index == view_model_->view_size();
1442  views::View* modified_view =
1443      (modified_index >= 0 && !at_end) ?
1444      view_model_->view_at(modified_index) : NULL;
1445  model_->Move(drag_view_index, start_drag_index_);
1446
1447  // If the modified view will be at the end of the list, return the new end of
1448  // the list.
1449  if (at_end)
1450    return view_model_->view_size();
1451  return modified_view ? view_model_->GetIndexOfView(modified_view) : -1;
1452}
1453
1454gfx::Size ShelfView::GetPreferredSize() {
1455  IdealBounds ideal_bounds;
1456  CalculateIdealBounds(&ideal_bounds);
1457
1458  const int preferred_size = layout_manager_->GetPreferredShelfSize();
1459
1460  int last_button_index = is_overflow_mode() ?
1461      last_visible_index_ : view_model_->view_size() - 1;
1462
1463  // When an item is dragged off from the overflow bubble, it is moved to last
1464  // position and and changed to invisible. Overflow bubble size should be
1465  // shrunk to fit only for visible items.
1466  // If |dragged_off_from_overflow_to_shelf_| is set, there will be no invisible
1467  // items in the shelf.
1468  if (is_overflow_mode() &&
1469      dragged_off_shelf_ &&
1470      !dragged_off_from_overflow_to_shelf_ &&
1471      RemovableByRipOff(view_model_->GetIndexOfView(drag_view_)) == REMOVABLE)
1472    last_button_index--;
1473
1474  const gfx::Rect last_button_bounds =
1475      last_button_index  >= first_visible_index_ ?
1476          view_model_->ideal_bounds(last_button_index) :
1477          gfx::Rect(gfx::Size(preferred_size, preferred_size));
1478
1479  if (layout_manager_->IsHorizontalAlignment()) {
1480    return gfx::Size(last_button_bounds.right() + leading_inset_,
1481                     preferred_size);
1482  }
1483
1484  return gfx::Size(preferred_size,
1485                   last_button_bounds.bottom() + leading_inset_);
1486}
1487
1488void ShelfView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
1489  LayoutToIdealBounds();
1490  FOR_EACH_OBSERVER(ShelfIconObserver, observers_,
1491                    OnShelfIconPositionsChanged());
1492
1493  if (IsShowingOverflowBubble())
1494    overflow_bubble_->Hide();
1495}
1496
1497views::FocusTraversable* ShelfView::GetPaneFocusTraversable() {
1498  return this;
1499}
1500
1501void ShelfView::GetAccessibleState(ui::AccessibleViewState* state) {
1502  state->role = ui::AccessibilityTypes::ROLE_TOOLBAR;
1503  state->name = l10n_util::GetStringUTF16(IDS_ASH_SHELF_ACCESSIBLE_NAME);
1504}
1505
1506void ShelfView::OnGestureEvent(ui::GestureEvent* event) {
1507  if (gesture_handler_.ProcessGestureEvent(*event))
1508    event->StopPropagation();
1509}
1510
1511void ShelfView::ShelfItemAdded(int model_index) {
1512  {
1513    base::AutoReset<bool> cancelling_drag(
1514        &cancelling_drag_model_changed_, true);
1515    model_index = CancelDrag(model_index);
1516  }
1517  views::View* view = CreateViewForItem(model_->items()[model_index]);
1518  AddChildView(view);
1519  // Hide the view, it'll be made visible when the animation is done. Using
1520  // opacity 0 here to avoid messing with CalculateIdealBounds which touches
1521  // the view's visibility.
1522  view->layer()->SetOpacity(0);
1523  view_model_->Add(view, model_index);
1524
1525  // Give the button its ideal bounds. That way if we end up animating the
1526  // button before this animation completes it doesn't appear at some random
1527  // spot (because it was in the middle of animating from 0,0 0x0 to its
1528  // target).
1529  IdealBounds ideal_bounds;
1530  CalculateIdealBounds(&ideal_bounds);
1531  view->SetBoundsRect(view_model_->ideal_bounds(model_index));
1532
1533  // The first animation moves all the views to their target position. |view|
1534  // is hidden, so it visually appears as though we are providing space for
1535  // it. When done we'll fade the view in.
1536  AnimateToIdealBounds();
1537  if (model_index <= last_visible_index_ ||
1538      model_index >= model_->FirstPanelIndex()) {
1539    bounds_animator_->SetAnimationDelegate(
1540        view, new StartFadeAnimationDelegate(this, view), true);
1541  } else {
1542    // Undo the hiding if animation does not run.
1543    view->layer()->SetOpacity(1.0f);
1544  }
1545}
1546
1547void ShelfView::ShelfItemRemoved(int model_index, LauncherID id) {
1548  if (id == context_menu_id_)
1549    launcher_menu_runner_.reset();
1550  {
1551    base::AutoReset<bool> cancelling_drag(
1552        &cancelling_drag_model_changed_, true);
1553    model_index = CancelDrag(model_index);
1554  }
1555  views::View* view = view_model_->view_at(model_index);
1556  view_model_->Remove(model_index);
1557
1558  // When the overflow bubble is visible, the overflow range needs to be set
1559  // before CalculateIdealBounds() gets called. Otherwise CalculateIdealBounds()
1560  // could trigger a LauncherItemChanged() by hiding the overflow bubble and
1561  // since the overflow bubble is not yet synced with the ShelfModel this
1562  // could cause a crash.
1563  if (overflow_bubble_ && overflow_bubble_->IsShowing()) {
1564    last_hidden_index_ = std::min(last_hidden_index_,
1565                                  view_model_->view_size() - 1);
1566    UpdateOverflowRange(overflow_bubble_->shelf_view());
1567  }
1568
1569  if (view->visible()) {
1570    // The first animation fades out the view. When done we'll animate the rest
1571    // of the views to their target location.
1572    bounds_animator_->AnimateViewTo(view, view->bounds());
1573    bounds_animator_->SetAnimationDelegate(
1574        view, new FadeOutAnimationDelegate(this, view), true);
1575  } else {
1576    // We don't need to show a fade out animation for invisible |view|. When an
1577    // item is ripped out from the shelf, its |view| is already invisible.
1578    AnimateToIdealBounds();
1579  }
1580
1581  // Close the tooltip because it isn't needed any longer and its anchor view
1582  // will be deleted soon.
1583  if (tooltip_->GetCurrentAnchorView() == view)
1584    tooltip_->Close();
1585}
1586
1587void ShelfView::ShelfItemChanged(int model_index,
1588                                 const LauncherItem& old_item) {
1589  const LauncherItem& item(model_->items()[model_index]);
1590  if (old_item.type != item.type) {
1591    // Type changed, swap the views.
1592    model_index = CancelDrag(model_index);
1593    scoped_ptr<views::View> old_view(view_model_->view_at(model_index));
1594    bounds_animator_->StopAnimatingView(old_view.get());
1595    // Removing and re-inserting a view in our view model will strip the ideal
1596    // bounds from the item. To avoid recalculation of everything the bounds
1597    // get remembered and restored after the insertion to the previous value.
1598    gfx::Rect old_ideal_bounds = view_model_->ideal_bounds(model_index);
1599    view_model_->Remove(model_index);
1600    views::View* new_view = CreateViewForItem(item);
1601    AddChildView(new_view);
1602    view_model_->Add(new_view, model_index);
1603    view_model_->set_ideal_bounds(model_index, old_ideal_bounds);
1604    new_view->SetBoundsRect(old_view->bounds());
1605    return;
1606  }
1607
1608  views::View* view = view_model_->view_at(model_index);
1609  switch (item.type) {
1610    case TYPE_BROWSER_SHORTCUT:
1611      // Fallthrough for the new Shelf since it needs to show the activation
1612      // change as well.
1613    case TYPE_APP_SHORTCUT:
1614    case TYPE_WINDOWED_APP:
1615    case TYPE_PLATFORM_APP:
1616    case TYPE_APP_PANEL: {
1617      ShelfButton* button = static_cast<ShelfButton*>(view);
1618      ReflectItemStatus(item, button);
1619      // The browser shortcut is currently not a "real" item and as such the
1620      // the image is bogous as well. We therefore keep the image as is for it.
1621      if (item.type != TYPE_BROWSER_SHORTCUT)
1622        button->SetImage(item.image);
1623      button->SchedulePaint();
1624      break;
1625    }
1626
1627    default:
1628      break;
1629  }
1630}
1631
1632void ShelfView::ShelfItemMoved(int start_index, int target_index) {
1633  view_model_->Move(start_index, target_index);
1634  // When cancelling a drag due to a launcher item being added, the currently
1635  // dragged item is moved back to its initial position. AnimateToIdealBounds
1636  // will be called again when the new item is added to the |view_model_| but
1637  // at this time the |view_model_| is inconsistent with the |model_|.
1638  if (!cancelling_drag_model_changed_)
1639    AnimateToIdealBounds();
1640}
1641
1642void ShelfView::ShelfStatusChanged() {
1643  if (ash::switches::UseAlternateShelfLayout())
1644    return;
1645  AppListButton* app_list_button =
1646      static_cast<AppListButton*>(GetAppListButtonView());
1647  if (model_->status() == ShelfModel::STATUS_LOADING)
1648    app_list_button->StartLoadingAnimation();
1649  else
1650    app_list_button->StopLoadingAnimation();
1651}
1652
1653void ShelfView::PointerPressedOnButton(views::View* view,
1654                                       Pointer pointer,
1655                                       const ui::LocatedEvent& event) {
1656  if (drag_view_)
1657    return;
1658
1659  int index = view_model_->GetIndexOfView(view);
1660  if (index == -1)
1661    return;
1662
1663  ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate(
1664      model_->items()[index].id);
1665  if (view_model_->view_size() <= 1 || !item_delegate->IsDraggable())
1666    return;  // View is being deleted or not draggable, ignore request.
1667
1668  drag_view_ = view;
1669  drag_offset_ = layout_manager_->PrimaryAxisValue(event.x(), event.y());
1670  UMA_HISTOGRAM_ENUMERATION("Ash.ShelfAlignmentUsage",
1671      layout_manager_->SelectValueForShelfAlignment(
1672          SHELF_ALIGNMENT_UMA_ENUM_VALUE_BOTTOM,
1673          SHELF_ALIGNMENT_UMA_ENUM_VALUE_LEFT,
1674          SHELF_ALIGNMENT_UMA_ENUM_VALUE_RIGHT,
1675          -1),
1676      SHELF_ALIGNMENT_UMA_ENUM_VALUE_COUNT);
1677}
1678
1679void ShelfView::PointerDraggedOnButton(views::View* view,
1680                                       Pointer pointer,
1681                                       const ui::LocatedEvent& event) {
1682  // To prepare all drag types (moving an item in the shelf and dragging off),
1683  // we should check the x-axis and y-axis offset.
1684  if (!dragging() && drag_view_ &&
1685      ((abs(event.x() - drag_offset_) >= kMinimumDragDistance) ||
1686       (abs(event.y() - drag_offset_) >= kMinimumDragDistance))) {
1687    PrepareForDrag(pointer, event);
1688  }
1689  if (drag_pointer_ == pointer)
1690    ContinueDrag(event);
1691}
1692
1693void ShelfView::PointerReleasedOnButton(views::View* view,
1694                                        Pointer pointer,
1695                                        bool canceled) {
1696  if (canceled) {
1697    CancelDrag(-1);
1698  } else if (drag_pointer_ == pointer) {
1699    FinalizeRipOffDrag(false);
1700    drag_pointer_ = NONE;
1701    AnimateToIdealBounds();
1702  }
1703  // If the drag pointer is NONE, no drag operation is going on and the
1704  // drag_view can be released.
1705  if (drag_pointer_ == NONE)
1706    drag_view_ = NULL;
1707}
1708
1709void ShelfView::MouseMovedOverButton(views::View* view) {
1710  if (!ShouldShowTooltipForView(view))
1711    return;
1712
1713  if (!tooltip_->IsVisible())
1714    tooltip_->ResetTimer();
1715}
1716
1717void ShelfView::MouseEnteredButton(views::View* view) {
1718  if (!ShouldShowTooltipForView(view))
1719    return;
1720
1721  if (tooltip_->IsVisible()) {
1722    tooltip_->ShowImmediately(view, GetAccessibleName(view));
1723  } else {
1724    tooltip_->ShowDelayed(view, GetAccessibleName(view));
1725  }
1726}
1727
1728void ShelfView::MouseExitedButton(views::View* view) {
1729  if (!tooltip_->IsVisible())
1730    tooltip_->StopTimer();
1731}
1732
1733base::string16 ShelfView::GetAccessibleName(const views::View* view) {
1734  int view_index = view_model_->GetIndexOfView(view);
1735  // May be -1 while in the process of animating closed.
1736  if (view_index == -1)
1737    return base::string16();
1738
1739  ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate(
1740      model_->items()[view_index].id);
1741  return item_delegate->GetTitle();
1742}
1743
1744void ShelfView::ButtonPressed(views::Button* sender, const ui::Event& event) {
1745  // Do not handle mouse release during drag.
1746  if (dragging())
1747    return;
1748
1749  if (sender == overflow_button_) {
1750    ToggleOverflowBubble();
1751    return;
1752  }
1753
1754  int view_index = view_model_->GetIndexOfView(sender);
1755  // May be -1 while in the process of animating closed.
1756  if (view_index == -1)
1757    return;
1758
1759  // If the previous menu was closed by the same event as this one, we ignore
1760  // the call.
1761  if (!IsUsableEvent(event))
1762    return;
1763
1764  {
1765    ScopedTargetRootWindow scoped_target(
1766        sender->GetWidget()->GetNativeView()->GetRootWindow());
1767    // Slow down activation animations if shift key is pressed.
1768    scoped_ptr<ui::ScopedAnimationDurationScaleMode> slowing_animations;
1769    if (event.IsShiftDown()) {
1770      slowing_animations.reset(new ui::ScopedAnimationDurationScaleMode(
1771            ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
1772    }
1773
1774    // Collect usage statistics before we decide what to do with the click.
1775    switch (model_->items()[view_index].type) {
1776      case TYPE_APP_SHORTCUT:
1777      case TYPE_WINDOWED_APP:
1778      case TYPE_PLATFORM_APP:
1779      case TYPE_BROWSER_SHORTCUT:
1780        Shell::GetInstance()->metrics()->RecordUserMetricsAction(
1781            UMA_LAUNCHER_CLICK_ON_APP);
1782        break;
1783
1784      case TYPE_APP_LIST:
1785        Shell::GetInstance()->metrics()->RecordUserMetricsAction(
1786            UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON);
1787        break;
1788
1789      case TYPE_APP_PANEL:
1790        break;
1791
1792      case TYPE_UNDEFINED:
1793        NOTREACHED() << "LauncherItemType must be set.";
1794        break;
1795    }
1796
1797    ShelfItemDelegate* item_delegate =
1798        item_manager_->GetShelfItemDelegate(model_->items()[view_index].id);
1799    if (!item_delegate->ItemSelected(event))
1800      ShowListMenuForView(model_->items()[view_index], sender, event);
1801  }
1802}
1803
1804bool ShelfView::ShowListMenuForView(const LauncherItem& item,
1805                                    views::View* source,
1806                                    const ui::Event& event) {
1807  scoped_ptr<ShelfMenuModel> menu_model;
1808  ShelfItemDelegate* item_delegate =
1809      item_manager_->GetShelfItemDelegate(item.id);
1810  menu_model.reset(item_delegate->CreateApplicationMenu(event.flags()));
1811
1812  // Make sure we have a menu and it has at least two items in addition to the
1813  // application title and the 3 spacing separators.
1814  if (!menu_model.get() || menu_model->GetItemCount() <= 5)
1815    return false;
1816
1817  ShowMenu(scoped_ptr<views::MenuModelAdapter>(
1818               new ShelfMenuModelAdapter(menu_model.get())),
1819           source,
1820           gfx::Point(),
1821           false,
1822           ui::GetMenuSourceTypeForEvent(event));
1823  return true;
1824}
1825
1826void ShelfView::ShowContextMenuForView(views::View* source,
1827                                       const gfx::Point& point,
1828                                       ui::MenuSourceType source_type) {
1829  int view_index = view_model_->GetIndexOfView(source);
1830  // TODO(simon.hong81): Create LauncherContextMenu for applist in its
1831  // ShelfItemDelegate.
1832  if (view_index != -1 && model_->items()[view_index].type == TYPE_APP_LIST) {
1833    view_index = -1;
1834  }
1835
1836  if (view_index == -1) {
1837    Shell::GetInstance()->ShowContextMenu(point, source_type);
1838    return;
1839  }
1840  scoped_ptr<ui::MenuModel> menu_model;
1841  ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate(
1842      model_->items()[view_index].id);
1843  menu_model.reset(item_delegate->CreateContextMenu(
1844      source->GetWidget()->GetNativeView()->GetRootWindow()));
1845  if (!menu_model)
1846    return;
1847
1848  base::AutoReset<LauncherID> reseter(
1849      &context_menu_id_,
1850      view_index == -1 ? 0 : model_->items()[view_index].id);
1851
1852  ShowMenu(scoped_ptr<views::MenuModelAdapter>(
1853               new views::MenuModelAdapter(menu_model.get())),
1854           source,
1855           point,
1856           true,
1857           source_type);
1858}
1859
1860void ShelfView::ShowMenu(scoped_ptr<views::MenuModelAdapter> menu_model_adapter,
1861                         views::View* source,
1862                         const gfx::Point& click_point,
1863                         bool context_menu,
1864                         ui::MenuSourceType source_type) {
1865  closing_event_time_ = base::TimeDelta();
1866  launcher_menu_runner_.reset(
1867      new views::MenuRunner(menu_model_adapter->CreateMenu()));
1868
1869  ScopedTargetRootWindow scoped_target(
1870      source->GetWidget()->GetNativeView()->GetRootWindow());
1871
1872  // Determine the menu alignment dependent on the shelf.
1873  views::MenuItemView::AnchorPosition menu_alignment =
1874      views::MenuItemView::TOPLEFT;
1875  gfx::Rect anchor_point = gfx::Rect(click_point, gfx::Size());
1876
1877  ShelfWidget* shelf = RootWindowController::ForLauncher(
1878      GetWidget()->GetNativeView())->shelf();
1879  if (!context_menu) {
1880    // Application lists use a bubble.
1881    ash::ShelfAlignment align = shelf->GetAlignment();
1882    anchor_point = source->GetBoundsInScreen();
1883
1884    // It is possible to invoke the menu while it is sliding into view. To cover
1885    // that case, the screen coordinates are offsetted by the animation delta.
1886    gfx::Vector2d offset =
1887        source->GetWidget()->GetNativeWindow()->bounds().origin() -
1888        source->GetWidget()->GetNativeWindow()->GetTargetBounds().origin();
1889    anchor_point.set_x(anchor_point.x() - offset.x());
1890    anchor_point.set_y(anchor_point.y() - offset.y());
1891
1892    // Shelf items can have an asymmetrical border for spacing reasons.
1893    // Adjust anchor location for this.
1894    if (source->border())
1895      anchor_point.Inset(source->border()->GetInsets());
1896
1897    switch (align) {
1898      case ash::SHELF_ALIGNMENT_BOTTOM:
1899        menu_alignment = views::MenuItemView::BUBBLE_ABOVE;
1900        break;
1901      case ash::SHELF_ALIGNMENT_LEFT:
1902        menu_alignment = views::MenuItemView::BUBBLE_RIGHT;
1903        break;
1904      case ash::SHELF_ALIGNMENT_RIGHT:
1905        menu_alignment = views::MenuItemView::BUBBLE_LEFT;
1906        break;
1907      case ash::SHELF_ALIGNMENT_TOP:
1908        menu_alignment = views::MenuItemView::BUBBLE_BELOW;
1909        break;
1910    }
1911  }
1912  // If this gets deleted while we are in the menu, the launcher will be gone
1913  // as well.
1914  bool got_deleted = false;
1915  got_deleted_ = &got_deleted;
1916
1917  shelf->ForceUndimming(true);
1918  // NOTE: if you convert to HAS_MNEMONICS be sure and update menu building
1919  // code.
1920  if (launcher_menu_runner_->RunMenuAt(
1921          source->GetWidget(),
1922          NULL,
1923          anchor_point,
1924          menu_alignment,
1925          source_type,
1926          context_menu ? views::MenuRunner::CONTEXT_MENU : 0) ==
1927      views::MenuRunner::MENU_DELETED) {
1928    if (!got_deleted) {
1929      got_deleted_ = NULL;
1930      shelf->ForceUndimming(false);
1931    }
1932    return;
1933  }
1934  got_deleted_ = NULL;
1935  shelf->ForceUndimming(false);
1936
1937  // If it is a context menu and we are showing overflow bubble
1938  // we want to hide overflow bubble.
1939  if (owner_overflow_bubble_)
1940    owner_overflow_bubble_->HideBubbleAndRefreshButton();
1941
1942  // Unpinning an item will reset the |launcher_menu_runner_| before coming
1943  // here.
1944  if (launcher_menu_runner_)
1945    closing_event_time_ = launcher_menu_runner_->closing_event_time();
1946  Shell::GetInstance()->UpdateShelfVisibility();
1947}
1948
1949void ShelfView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) {
1950  FOR_EACH_OBSERVER(ShelfIconObserver, observers_,
1951                    OnShelfIconPositionsChanged());
1952  PreferredSizeChanged();
1953}
1954
1955void ShelfView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
1956  if (snap_back_from_rip_off_view_ && animator == bounds_animator_) {
1957    if (!animator->IsAnimating(snap_back_from_rip_off_view_)) {
1958      // Coming here the animation of the ShelfButton is finished and the
1959      // previously hidden status can be shown again. Since the button itself
1960      // might have gone away or changed locations we check that the button
1961      // is still in the shelf and show its status again.
1962      for (int index = 0; index < view_model_->view_size(); index++) {
1963        views::View* view = view_model_->view_at(index);
1964        if (view == snap_back_from_rip_off_view_) {
1965          ShelfButton* button = static_cast<ShelfButton*>(view);
1966          button->ClearState(ShelfButton::STATE_HIDDEN);
1967          break;
1968        }
1969      }
1970      snap_back_from_rip_off_view_ = NULL;
1971    }
1972  }
1973}
1974
1975bool ShelfView::IsUsableEvent(const ui::Event& event) {
1976  if (closing_event_time_ == base::TimeDelta())
1977    return true;
1978
1979  base::TimeDelta delta =
1980      base::TimeDelta(event.time_stamp() - closing_event_time_);
1981  closing_event_time_ = base::TimeDelta();
1982  // TODO(skuhne): This time seems excessive, but it appears that the reposting
1983  // takes that long.  Need to come up with a better way of doing this.
1984  return (delta.InMilliseconds() < 0 || delta.InMilliseconds() > 130);
1985}
1986
1987const LauncherItem* ShelfView::LauncherItemForView(
1988    const views::View* view) const {
1989  int view_index = view_model_->GetIndexOfView(view);
1990  if (view_index == -1)
1991    return NULL;
1992  return &(model_->items()[view_index]);
1993}
1994
1995bool ShelfView::ShouldShowTooltipForView(const views::View* view) const {
1996  if (view == GetAppListButtonView() &&
1997      Shell::GetInstance()->GetAppListWindow())
1998    return false;
1999  const LauncherItem* item = LauncherItemForView(view);
2000  if (!item)
2001    return true;
2002  ShelfItemDelegate* item_delegate =
2003      item_manager_->GetShelfItemDelegate(item->id);
2004  return item_delegate->ShouldShowTooltip();
2005}
2006
2007int ShelfView::CalculateShelfDistance(const gfx::Point& coordinate) const {
2008  ShelfWidget* shelf = RootWindowController::ForLauncher(
2009      GetWidget()->GetNativeView())->shelf();
2010  ash::ShelfAlignment align = shelf->GetAlignment();
2011  const gfx::Rect bounds = GetBoundsInScreen();
2012  int distance = 0;
2013  switch (align) {
2014    case ash::SHELF_ALIGNMENT_BOTTOM:
2015      distance = bounds.y() - coordinate.y();
2016      break;
2017    case ash::SHELF_ALIGNMENT_LEFT:
2018      distance = coordinate.x() - bounds.right();
2019      break;
2020    case ash::SHELF_ALIGNMENT_RIGHT:
2021      distance = bounds.x() - coordinate.x();
2022      break;
2023    case ash::SHELF_ALIGNMENT_TOP:
2024      distance = coordinate.y() - bounds.bottom();
2025      break;
2026  }
2027  return distance > 0 ? distance : 0;
2028}
2029
2030}  // namespace internal
2031}  // namespace ash
2032